/ imgboard-test.php
imgboard-test.php
1 <?php 2 require_once 'lib/util.php'; 3 /* 4 if( isset( $_REQUEST["profile"] ) ) { 5 xhprof_enable( XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY ); 6 register_shutdown_function( "xhprof_save" ); 7 } 8 if ( isset($_REQUEST["sqlprofile"] )) { 9 $mysql_query_log = YES; 10 } 11 */ 12 require_once "yotsuba_config.php"; 13 14 require_once( "lib/ads.php" ); 15 16 if (TEST_BOARD) { 17 require_once 'lib/admin-test.php'; 18 require_once( "lib/postfilter-test.php" ); 19 require_once 'lib/captcha-test.php'; 20 require_once 'lib/auth-test.php'; 21 require_once 'lib/geoip2-test.php'; 22 require_once 'lib/userpwd-test.php'; 23 } 24 else { 25 require_once 'lib/admin.php'; 26 require_once( "lib/postfilter.php" ); 27 require_once 'lib/captcha.php'; 28 require_once 'lib/auth.php'; 29 require_once 'lib/geoip2.php'; 30 require_once 'lib/userpwd.php'; 31 } 32 33 require_once 'lib/phash.php'; 34 35 if (ENABLE_PAINTERJS) { 36 if (TEST_BOARD) { 37 require_once('lib/oekaki-test.php'); 38 } 39 else { 40 require_once('lib/oekaki.php'); 41 } 42 } 43 44 // HTML rendering unique to imgboard and upload board 45 if (UPLOAD_BOARD) { 46 require_once "views/upboard.php"; 47 } else if (TEST_BOARD) { 48 require_once "views/imgboard-test.php"; 49 } else { 50 require_once "views/imgboard.php"; 51 } 52 53 if( !function_exists( 'generate_catalog' ) ) { 54 if (TEST_BOARD) { 55 require_once 'catalog-test.php'; 56 } else { 57 require_once 'catalog.php'; 58 } 59 } 60 61 if( ENABLE_JSON ) { 62 $in_imgboard = 1; 63 64 if (TEST_BOARD) { 65 require_once 'json-test.php'; 66 } else { 67 require_once 'json.php'; 68 } 69 } 70 71 if (!defined('SQLLOGMOD')) { 72 define( 'SQLLOGBAN', 'banned_users' ); //Table (NOT DATABASE) used for holding banned users 73 define( 'SQLLOGMOD', 'mod_users' ); //Table (NOT DATABASE) used for holding mod users 74 } 75 76 define( 'SQLLOGDEL', 'del_log' ); //Table (NOT DATABASE) used for holding deletion log 77 78 auth_user(); 79 80 if (LOCKDOWN) { 81 if (($_POST['mode'] == 'usrdel' || $_POST['mode'] == 'arcdel' || $_GET['mode'] == 'latest') && has_level('janitor')) { 82 // Allow janitors to delete posts and update mod tools 83 } 84 else if (has_level('manager') || has_flag('developer')) { 85 // Allow the manager to do anything 86 } 87 else { 88 die('<span style="color: red;" id="errmsg">' . LOCKDOWN_MSG . '</span>'); 89 } 90 } 91 92 if (TEST_BOARD && (!has_level() || !has_flag('developer'))) { 93 die(''); 94 } 95 96 // test 97 if( TEST_BOARD || has_flag('developer') ) { 98 ini_set( 'display_errors', 1 ); 99 error_reporting(E_ALL & ~E_NOTICE); 100 } 101 102 extract( $_POST, EXTR_SKIP ); 103 extract( $_GET, EXTR_SKIP ); 104 extract( $_COOKIE, EXTR_SKIP ); 105 106 if (isset( $_COOKIE['4chan_pass']) && $_COOKIE['4chan_pass']) { 107 $pwdc = $_COOKIE['4chan_pass']; 108 } 109 else { 110 $pwdc = null; 111 } 112 113 // FIXME whitelist 114 unset( $dest ); 115 unset( $log ); 116 unset( $update_avg_secs ); 117 118 if( $argv[1] ) $mode = $argv[1]; 119 $id = intval( $id ); 120 121 if( $_SERVER["REQUEST_METHOD"] == "POST" ) { 122 // bust cache 123 header( 'Cache-Control: private, no-cache, must-revalidate' ); 124 header( 'Expires: -1' ); 125 header( 'Vary: *' ); 126 } 127 128 if( array_key_exists( 'upfile', $_FILES ) ) { 129 $upfile_name = $_FILES["upfile"]["name"]; 130 $upfile = $_FILES["upfile"]["tmp_name"]; 131 } else { 132 $upfile_name = $upfile = ''; 133 } 134 135 $fwritetimer = 0.0; 136 137 ignore_user_abort( true ); 138 139 $word_filters_enabled = false; 140 if (WORD_FILT) { 141 $word_filt_root = '/www/global/yotsuba/wordfilters/'; 142 143 if (file_exists($word_filt_root . BOARD_DIR . '.php')) { 144 include_once($word_filt_root . BOARD_DIR . '.php'); 145 $word_filters_enabled = true; 146 } 147 else if (file_exists($word_filt_root . 'global.php')) { 148 include_once($word_filt_root . 'global.php'); 149 $word_filters_enabled = true; 150 } 151 } 152 153 if( JANITOR_BOARD == 1 && !has_level( 'janitor' ) ) { 154 die( '' ); 155 } 156 157 if( JANITOR_BOARD == 1 ) 158 include_once 'plugins/broomcloset.php'; 159 160 // QENHANCE 161 if( META_BOARD ) { 162 include_once 'plugins/enhance_q.php'; 163 } 164 165 $mysql_connect_opts = 0; 166 mysql_board_connect(BOARD_DIR); 167 168 $board_flags_array = null; 169 170 if (ENABLE_BOARD_FLAGS) { 171 $_flags_type = (defined('BOARD_FLAGS_TYPE') && BOARD_FLAGS_TYPE) ? BOARD_FLAGS_TYPE : BOARD_DIR; 172 $_board_flags_path = '/www/global/yotsuba/lib/board_flags_' . $_flags_type . '.php'; 173 if (file_exists($_board_flags_path)) { 174 include_once($_board_flags_path); 175 $board_flags_array = get_board_flags_array(); 176 } 177 } 178 179 $thread_unique_ips = 0; 180 181 $index_rbl = PAGE_MAX; 182 $index_last_thread = 0; 183 $index_last_post = 0; 184 185 if (JANITOR_BOARD && PAGE_MAX == 0) { 186 $index_rbl = ceil(LOG_MAX / DEF_PAGES); 187 } 188 189 $valid_boards = "3|aco|adv|an|biz|diy|fa|fit|gd|gif|int|lit|hc|hr|a|b|ck|co|cm|c|d|e|f|g|h|i|k|lgbt|m|n|o|out|p|r|s|t|u|vp|vg|vr|v|w|x|y|wg|ic|cgl|hm|mlp|mu|pol|po|r9k|s4s|sci|soc|tg|tv|toy|trv|jp|sp|wsg|qa|qst|his|trash|news|wsr|vip|bant|vrpg|vmg|vst|vt|vm|pw|xs"; 190 191 $boards_matching_arr = array(); 192 193 $captcha_bypass = null; 194 $rangeban_bypass = false; 195 $passid = ''; 196 197 // FIXME, this should be put somewhere else. 198 function is_local() { 199 $longip = ip2long( $_SERVER[ 'REMOTE_ADDR' ] ); 200 201 return !$longip || cidrtest( $longip, "10.0.0.0/24" ) || cidrtest( $longip, "204.152.204.0/24" ) || cidrtest( $longip, "127.0.0.0/24" ); 202 } 203 204 /** 205 * Abbreviates posts on index pages. 206 * Truncate $str to $max_lines lines and return $str and $abbr 207 * where $abbr = whether or not $str was actually truncated. 208 * Expects well-formed HTML. 209 */ 210 function abbreviate($str, $max_lines = 20) { 211 $lines = explode('<br>', $str); 212 213 if (count($lines) > $max_lines) { 214 $abbr = 1; 215 $lines = array_slice($lines, 0, $max_lines); 216 $str = implode('<br>', $lines ); 217 218 $unpaired_tags = array( 219 'img' => true, 220 'br' => true, 221 'input' => true, 222 'hr' => true, 223 'param' => true 224 ); 225 226 preg_match_all('/<\/([^>]+)>/', $str, $closed_tags, PREG_SET_ORDER); 227 $closed_count = count($closed_tags); 228 229 $closed_map = array(); 230 231 foreach ($closed_tags as $m) { 232 if (!isset($closed_map[$m[1]])) { 233 $closed_map[$m[1]] = 1; 234 } 235 else { 236 $closed_map[$m[1]] += 1; 237 } 238 } 239 240 preg_match_all('/<([a-z0-9]+)(?: |>)/', $str, $open_tags, PREG_SET_ORDER); 241 $open_count = count($open_tags); 242 243 for ($i = 0; $i < $open_count; ++$i) { 244 $tag = $open_tags[$i][1]; 245 246 if (isset($unpaired_tags[$tag])) { 247 continue; 248 } 249 250 if (!isset($closed_map[$tag])) { 251 $str .= "</$tag>"; 252 } 253 else if ($closed_map[$tag] > 0) { 254 $closed_map[$tag] -= 1; 255 } 256 else if ($closed_map[$tag] <= 0) { 257 $str .= "</$tag>"; 258 } 259 } 260 } 261 else { 262 $abbr = 0; 263 } 264 265 return array($str, $abbr); 266 } 267 268 /** 269 * Currently only used on /archive 270 * strips html tags and replaces sjis art with [SJIS] placeholders 271 */ 272 function truncate_comment($str, $length, $keep_spoilers = false) { 273 // remove sjis 274 if (SJIS_TAGS && strpos($str, '<span class="sjis"') !== false) { 275 $str = preg_replace('/<span class="sjis".+?<\/span>/', '[SJIS]', $str); 276 } 277 278 $len = mb_strlen($str); 279 280 if ($len <= $length) { 281 return $str; 282 } 283 284 if (!$keep_spoilers) { 285 $str = strip_tags($str); 286 } 287 else { 288 $str = strip_tags($str, '<s>'); 289 } 290 291 if ($len <= $length) { 292 return $str; 293 } 294 295 $str = mb_substr($str, 0, $length); 296 297 // remove truncated html entities 298 $str = preg_replace('/&[^;]*$/', '', $str); 299 300 if ($keep_spoilers) { 301 $str = preg_replace('/<[^>]*$/', '', $str); 302 303 $oc = substr_count($str, '<s>'); 304 305 if ($oc) { 306 $cc = substr_count($str, '</s>'); 307 $dc = $oc - $cc; 308 if ($dc > 0) { 309 $str .= str_repeat('</s>', $dc); 310 } 311 } 312 } 313 314 $str .= '…'; 315 316 return $str; 317 } 318 319 function paranoid_rename( $src, $dest ) 320 { 321 $across_devices = false; //keep around for future use 322 $u = false; 323 324 if( $across_devices ) { 325 // rename to dest dir, then over dest 326 $dsrc = dirname( $dest ) . "/" . basename( $src ); 327 if( !@rename( $src, $dsrc ) ) $u = $src; 328 else if( !@rename( $dsrc, $dest ) ) $u = $dsrc; 329 } else { 330 if( !@rename( $src, $dest ) ) $u = $src; 331 } 332 333 if( $u ) 334 unlink( $u ); 335 } 336 337 function rename_across_device( $src, $dest ) 338 { 339 // FIXME: copy() does a chmod but we don't need that 340 copy($src, $dest); 341 unlink($src); 342 } 343 344 function getmypid_cached() 345 { 346 static $pid = -1; 347 348 if ($pid === -1) $pid = getmypid(); 349 350 return $pid; 351 } 352 353 // print $contents to $filename by using a temporary file and renaming it 354 // may destroy $contents in the process 355 // (makes *.html and *.gz if USE_GZIP is on) 356 function print_page( $filename, &$contents, $force_nogzip = 0, $trim_whitespace = 1 ) 357 { 358 global $fwritetimer; 359 360 $timestarted = microtime( true ); 361 362 if( NEW_HTML == 1 && $trim_whitespace ) { 363 $contents = str_replace( array("\r\n", "\n", "\t"), array('', '', ''), $contents ); 364 } 365 366 $gzip = ( USE_GZIP == 1 && !$force_nogzip ); 367 368 if( $gzip ) { 369 $tempname = dirname( $filename )."/gztmp".getmypid_cached(); 370 371 // FIXME: number of syscalls done by gzwrite is not optimal (it does a small one then 4KB writes after) 372 // for small files (how small?) do gzencode() and file_put_contents() instead. 373 374 $fp = gzopen($tempname, "wb9"); 375 if( $fp === false ) return; 376 gzwrite($fp, $contents); 377 gzclose($fp); 378 // chmod( $tempname, 0664 ); //it was created 0600 379 380 paranoid_rename( $tempname, $filename . ".gz" ); 381 } else { 382 $tempname = dirname( $filename )."/tmp".getmypid_cached(); 383 if( file_put_contents( $tempname, $contents ) === false ) return; 384 // chmod( $tempname, 0664 ); //it was created 0600 385 paranoid_rename( $tempname, $filename ); 386 } 387 388 $fwritetimer += ( microtime( true ) - $timestarted ); 389 } 390 391 function file_get_contents_cached( $filename ) 392 { 393 static $cache = array(); 394 if( isset( $cache[$filename] ) ) 395 return $cache[$filename]; 396 $cache[$filename] = @file_get_contents( $filename ); 397 398 return $cache[$filename]; 399 } 400 401 function file_array_cached( $filename ) 402 { 403 static $cache = array(); 404 if( isset( $cache[$filename] ) ) 405 return $cache[$filename]; 406 $cache[$filename] = @file( $filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); 407 408 return $cache[$filename]; 409 } 410 411 function get_blotter() { 412 if (!SHOW_BLOTTER) { 413 return ''; 414 } 415 416 $msg_limit = 3; 417 418 $blotter = <<<HTML 419 <table id="blotter" class="desktop"><thead><tr><td colspan="2"><hr class="aboveMidAd"></td></tr></thead><tbody id="blotter-msgs"> 420 HTML; 421 422 $query = <<<SQL 423 SELECT sql_cache `date`, content 424 FROM blotter_messages 425 ORDER BY id DESC LIMIT $msg_limit 426 SQL; 427 428 $res = mysql_global_call($query); 429 430 $mtime = 0; 431 432 if ($res && mysql_num_rows($res) > 0) { 433 while ($row = mysql_fetch_assoc($res)) { 434 if ($mtime === 0) { 435 $mtime = $row['date']; 436 } 437 438 $blotter .= '<tr><td data-utc="' . 439 $row['date'] . '" class="blotter-date">' . 440 date('m/d/y', $row['date']) . '</td><td class="blotter-content">' . 441 $row['content'] . '</td></tr>'; 442 } 443 } 444 else { 445 return ''; 446 } 447 448 $blotter .= '</tbody><tfoot><tr><td colspan="2">[<a data-utc="' . $mtime 449 . '" id="toggleBlotter" href="#">Hide</a>]<span> [<a href="//www.' 450 . L::d(BOARD_DIR) 451 . '/blotter" target="_blank">Show All</a>]</span></td></tr></tfoot></table>'; 452 453 return $blotter; 454 } 455 456 function blotter_contents() 457 { 458 static $cache; 459 if( isset( $cache ) ) return $cache; 460 $ret = ""; 461 $topN = 4; //how many lines to print 462 $bl_lines = file( BLOTTER_PATH ); 463 $bl_top = array_slice( $bl_lines, 0, $topN ); 464 $date = ""; 465 foreach( $bl_top as $line ) { 466 if( !$date ) { 467 $lineparts = explode( ' - ', $line ); 468 if( strpos( $lineparts[0], '<font' ) !== false ) { 469 $dateparts = explode( '>', $lineparts[0] ); 470 $date = $dateparts[1]; 471 $date = "<li><font color=\"red\">Blotter updated: $date</font>"; 472 } else { 473 $date = $lineparts[0]; 474 $date = "<li>Blotter updated: $date"; 475 } 476 } 477 $line = trim( $line ); 478 $line = str_replace( "\\", "\\\\", $line ); 479 $line = str_replace( "'", "\'", $line ); 480 $ret .= "'<li>$line'+\n"; 481 } 482 $ret .= "''"; 483 $cache = array($date, $ret); 484 485 return array($date, $ret); 486 } 487 488 function find_match_and_prefix( $regex, $str, $off, &$match ) 489 { 490 if( !preg_match( $regex, $str, $m, PREG_OFFSET_CAPTURE, $off ) ) return false; 491 492 $moff = $m[0][1]; 493 $match = array(substr( $str, $off, $moff - $off ), $m[0][0]); 494 495 return true; 496 } 497 498 // skip_on_spoilers will stop parsing and return the unmodified comment 499 // if spoiler tags are found inside the string to wrap. 500 // This is to avoid sjis.spoiler tags mixing mostly. 501 function parse_bbcode_one( $com, $tn, $st, $et, $nest_limit = 2, $skip_on_spoilers = false ) 502 { 503 if( !find_match_and_prefix( "/\[$tn\]/", $com, 0, $m ) ) return $com; 504 505 $bracket_tn = "[$tn]"; 506 $bl = strlen( $bracket_tn ); 507 $el = $bl + 1; 508 $ret = $m[0] . $st; 509 $lev = 1; 510 $off = strlen( $m[0] ) + $bl; 511 512 while( 1 ) { 513 if (!find_match_and_prefix( "@\[/?$tn\]@", $com, $off, $m)) break; 514 list( $txt, $tag ) = $m; 515 516 if (!$skip_on_spoilers || $tag === $bracket_tn) { 517 $ret .= $txt; 518 } 519 else if (preg_match('/\[\/?spoiler\]/', $txt)) { 520 return $com; 521 } 522 523 $off += strlen( $txt ) + strlen( $tag ); 524 525 if( $tag == $bracket_tn ) { 526 if( $lev < $nest_limit ) 527 $ret .= $st; 528 $lev++; 529 } else if( $lev ) { 530 if( $lev <= $nest_limit ) 531 $ret .= $et; 532 $lev--; 533 } 534 } 535 536 $tail = substr($com, $off, strlen($com) - $off); 537 $ret .= $tail; 538 539 $lev = min( $lev, $nest_limit ); 540 541 if ($lev > 0) { 542 if ($skip_on_spoilers && preg_match('/\[\/?spoiler\]/', $tail)) { 543 return $com; 544 } 545 546 $ret .= str_repeat( $et, $lev ); 547 } 548 549 return $ret; 550 } 551 552 function spoiler_parse( $com ) 553 { 554 return parse_bbcode_one( $com, 'spoiler', '<s>', '</s>' ); 555 } 556 557 function jsmath_parse( $com ) 558 { 559 $com = parse_bbcode_one( $com, "math", '<span class="math">', '</span>' ); 560 $com = parse_bbcode_one( $com, "eqn", '<div class="math">', '</div>' ); 561 562 return $com; 563 } 564 565 /* BBCode for bold, italic, and r/g/b color tags */ 566 function parse_op_markup($com) { 567 $com = parse_bbcode_one($com, 'b', '<span class="mu-s">', '</span>', 1); 568 $com = parse_bbcode_one($com, 'i', '<span class="mu-i">', '</span>', 1); 569 570 $com = parse_bbcode_one($com, 'red', '<span class="mu-r">', '</span>', 1); 571 $com = parse_bbcode_one($com, 'green', '<span class="mu-g">', '</span>', 1); 572 $com = parse_bbcode_one($com, 'blue', '<span class="mu-b">', '</span>', 1); 573 574 return $com; 575 } 576 577 function code_parse( $com ) 578 { 579 return parse_bbcode_one( $com, 'code', '<pre class="prettyprint">', '</pre>' ); 580 } 581 582 function sjis_parse($com) { 583 $skip_on_spoilers = strpos($com, '[spoiler]') !== false; 584 585 return parse_bbcode_one($com, 'sjis', '<span class="sjis">', '</span>', 1, $skip_on_spoilers); 586 } 587 588 // convenience function for wordfilters. 589 // text must be html escaped 590 function random_color( $str, $background = 1, $foreground = 1 ) 591 { 592 $style = ""; 593 594 if( $background ) { 595 $r = rand( 0, 255 ); 596 $g = rand( 0, 255 ); 597 $b = rand( 0, 255 ); 598 $style = $style . "background: #" . sprintf( "%02x%02x%02x", $r, $g, $b ) . "; "; 599 } 600 601 if( $foreground ) { 602 $r = rand( 0, 255 ); 603 $g = rand( 0, 255 ); 604 $b = rand( 0, 255 ); 605 $style = $style . "color: #" . sprintf( "%02x%02x%02x", $r, $g, $b ) . "; "; 606 } 607 608 if( $style ) { 609 return "<span style=\"$style\">$str</span>"; 610 } 611 612 return $str; 613 } 614 615 function append_ban( $board, $ip ) 616 { 617 $cmd = "nohup /usr/local/bin/suid_run_global bin/appendban $board $ip >/dev/null 2>&1 &"; 618 exec( $cmd ); 619 } 620 621 // check whether the current user can perform $action (on $no, for some actions) 622 // board-level access is cached in $valid_cache. 623 // FIXME move to lib/admin.php 624 function valid( $action = 'moderator', $no = 0 ) 625 { 626 static $valid_cache, $can_post_html; // the access level of the user 627 $access_level = array('none' => 0, 'janitor' => 1, 'janitor_this_board' => 2, 'moderator' => 5, 'manager' => 10, 'admin' => 20); 628 if( !isset( $valid_cache ) ) { 629 $valid_cache = $access_level['none']; 630 if( isset( $_COOKIE['4chan_auser'] ) && isset( $_COOKIE['apass'] ) ) { 631 $user = mysql_real_escape_string( $_COOKIE['4chan_auser'] ); 632 $pass = $_COOKIE['apass']; 633 } 634 if( $user && $pass ) { 635 $result = mysql_global_call( "SELECT allow,deny,password_expired,username,password FROM " . SQLLOGMOD . " WHERE username='$user' LIMIT 1" ); 636 list( $allow, $deny, $expired, $username, $password ) = mysql_fetch_row( $result ); 637 mysql_free_result( $result ); 638 639 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 640 641 if (!$admin_salt) { 642 die('Internal Server Error (s0)'); 643 } 644 645 $hashed_admin_password = hash('sha256', $username . $password . $admin_salt); 646 647 if ($hashed_admin_password !== $pass) { 648 return false; 649 } 650 651 if( $expired ) { 652 error( 'Your password has expired; check IRC for instructions on changing it.' ); 653 } 654 655 if( $allow ) { 656 $allows = explode( ',', $allow ); 657 $seen_janitor_token = false; 658 // each token can increase the access level, 659 // except that we only know that they're a moderator or a janitor for another board 660 // AFTER we read all the tokens 661 $cphtml = false; 662 663 foreach( $allows as $token ) { 664 if( $token == 'janitor' ) { 665 $seen_janitor_token = true; 666 } else if( $token == 'manager' && $valid_cache < $access_level['manager'] ) { 667 $valid_cache = $access_level['manager']; 668 } else if( $token == 'admin' && $valid_cache < $access_level['admin'] ) { 669 $valid_cache = $access_level['admin']; 670 } else if( ( $token == BOARD_DIR || $token == 'all' ) && $valid_cache < $access_level['janitor_this_board'] ) { 671 $valid_cache = $access_level['janitor_this_board']; // or could be moderator, will be increased in next step 672 } elseif( $token == 'html' ) { 673 $cphtml = true; 674 } 675 } 676 677 $can_post_html = $cphtml; 678 // now we can set moderator or janitor status 679 if( !$seen_janitor_token ) { 680 if( $valid_cache < $access_level['moderator'] ) 681 $valid_cache = $access_level['moderator']; 682 } else { 683 if( $valid_cache < $access_level['janitor'] ) 684 $valid_cache = $access_level['janitor']; 685 } 686 if( $deny ) { 687 $denies = explode( ',', $deny ); 688 if( in_array( BOARD_DIR, $denies ) ) { 689 $valid_cache = $access_level['none']; 690 } 691 } 692 } 693 } 694 } 695 { 696 // local rpc can do anything 697 $longip = ip2long( $_SERVER['REMOTE_ADDR'] ); 698 if( !$longip || cidrtest( $longip, "10.0.0.0/24" ) || 699 cidrtest( $longip, "204.152.204.0/24" ) || cidrtest( $longip, "127.0.0.0/24" ) 700 ) 701 return YES; 702 } 703 switch( $action ) { 704 case 'moderator': 705 return $valid_cache >= $access_level['moderator']; 706 case 'textonly': 707 return $valid_cache >= $access_level['moderator']; 708 case 'htmlnopw': 709 return $valid_cache >= $access_level['manager']; 710 case 'htmlpost': 711 return $can_post_html; 712 case 'janitor_board': 713 return $valid_cache >= $access_level['janitor']; 714 case 'delete': 715 if( $valid_cache >= $access_level['janitor_this_board'] ) { 716 return true; 717 } // if they're a janitor on another board, check for illegal post unlock 718 else if( $valid_cache >= $access_level['janitor'] ) { 719 $query = mysql_global_do( "SELECT COUNT(*) from reports WHERE board='" . BOARD_DIR . "' AND no=$no AND cat=2" ); 720 $illegal_count = mysql_result( $query, 0, 0 ); 721 mysql_free_result( $query ); 722 723 return $illegal_count >= 3; 724 } 725 case 'reportflood': 726 return $valid_cache >= $access_level['janitor']; 727 case 'floodbypass': 728 return $valid_cache >= $access_level['janitor']; 729 case 'rebuild': 730 return $valid_cache >= $access_level['janitor']; 731 case 'admin': 732 return $valid_cache >= $access_level['admin']; 733 default: // unsupported action 734 return false; 735 } 736 } 737 738 function iplog_add( $board, $no, $ip, $time, $is_thread, $tim, $had_image ) 739 { 740 mysql_global_call( "INSERT INTO user_actions (board,postno,ip,time,uploaded,action,had_image) VALUES ('%s',%d,%d,from_unixtime(%d),%d,'%s',%d)",$board, $no, ip2long($ip), $time, $tim, $is_thread ? "new_thread" : "new_reply", $had_image); 741 } 742 743 function clean_log_bool( &$row ) 744 { 745 static $bool_cols = array('sticky', 'permasage', 'closed', 'filedeleted', 'permaage', 'undead', 'archived'); 746 747 foreach ($bool_cols as $col) { 748 if (!isset($row[$col])) continue; // FIXME split this function up to avoid this test 749 $c = &$row[$col]; 750 settype($c, "bool"); 751 // NOTES: $c = $c ? TRUE : FALSE allocates new bools each time 752 // $c = $c ? &$itrue : &$ifalse causes them to be converted to strings(!) 753 // Put this back to TRUE : FALSE instead of a settype call sometime so we can look at the php bytecodes 754 } 755 } 756 757 function clean_log_int( &$row ) 758 { 759 static $int_cols = array('no', 'w', 'h', 'tn_w', 'tn_h', 'last_modified', 'time', 'fsize', 'resto'); 760 761 // turn columns into int (does this help?) 762 foreach( $int_cols as $col ) { 763 if (!isset($row[$col])) continue; 764 765 $c = &$row[$col]; 766 settype($c, "int"); 767 } 768 } 769 770 function clean_log_delreply( &$row ) 771 { 772 if (!$row['resto']) 773 return; 774 775 static $del_cols = array('sticky', 'permasage', 'closed', 'last_modified', 'root', 'undead', 'permaage'); 776 777 // delete fields not used for replies 778 foreach( $del_cols as $col ) { 779 unset($row[$col]); 780 } 781 } 782 783 function clean_log_intern( &$row ) 784 { 785 static $intern_cols = array('name', 'ext', 'capcode', 'country'); 786 787 // Intern repeated strings that are usually the same value. 788 // In PHP 6 this... doesn't seem to do anything? Let's try again in 7. 789 static $log_intern; 790 if( !isset( $log_intern ) ) { 791 $log_intern = array(); 792 foreach( $intern_cols as $col ) 793 $log_intern[$col] = array(); 794 } 795 796 foreach( $intern_cols as $col ) { 797 $intern_array = &$log_intern[$col]; 798 $c = &$row[$col]; 799 800 $v = $c; 801 if( !isset($intern_array[$v]) ) 802 $intern_array[$v] = $v; 803 $c = &$intern_array[$c]; 804 } 805 } 806 807 function clean_log_row( &$row ) 808 { 809 //static $rn = 0; 810 clean_log_delreply( $row ); 811 clean_log_bool( $row ); 812 clean_log_int( $row ); 813 //clean_log_intern( $row ); 814 //if (++$rn == 100) { 815 // debug_zval_dump($row); 816 //} 817 } 818 819 function log_bad_cache_entry($no) 820 { 821 global $log_cache_level; 822 global $log; 823 824 internal_error_log("logcache", "missing children for OP no $no, cache level $log_cache_level, cache contents ".count($log)); 825 } 826 827 function invalidate_log($thread) 828 { 829 global $log_cache_level; 830 global $log; 831 832 if (isset($log[$thread])) { 833 if ($log[$thread]['resto']) { 834 die(S_ASSERT); 835 } 836 unset($log[$thread]); 837 } 838 839 if ($log_cache_level==2) 840 $log_cache_level = 1; 841 } 842 843 // build a structure out of all the posts in the database. 844 // this lets us replace a LOT of queries with a simple array access. 845 // it only builds the first time it was called. 846 // rather than calling log_cache(1) to rebuild everything, 847 // you should just manipulate the structure directly. 848 // $thread may be any postno in a thread 849 // without a thread, $archive_mode fetches all live threads if 0 and all archived threads if 1 850 function log_cache($invalidate = 0, $thread = 0, $archive_mode = 0) { 851 global $log_cache_level; 852 global $log, $ipcount, $mysql_unbuffered_reads; 853 854 if (!isset($log) || $invalidate) { 855 $log = array(); 856 $log_cache_level = 0; 857 } 858 859 // Optimisation for index rebuilding when REPLIES_SHOWN is 0. 860 // No need to fetch the entire board in this case. 861 $optimised_indexes = !$thread && !$archive_mode && !REPLIES_SHOWN && IS_REBUILDD; 862 863 // Handle cache 864 // 1 = Live OPs are cached, 2 = Some threads are cached, 3 = Whole live board is cached 865 if ($optimised_indexes) { 866 $nlog_cache_level = 1; 867 } 868 else { 869 $nlog_cache_level = $thread ? 2 : 3; 870 } 871 872 // Whole board is cached, nothing to do. 873 if ($log_cache_level == 3 && $archive_mode === 0) { 874 return; 875 } 876 877 // Thread is cached, nothing to do. 878 if ($log_cache_level == 2 && isset($log[$thread])) { 879 return; 880 } 881 882 // Live OPs are cached, nothing to do. 883 if ($log_cache_level == 1 && $optimised_indexes) { 884 return; 885 } 886 887 if ($nlog_cache_level > $log_cache_level) { 888 $log_cache_level = $nlog_cache_level; 889 } 890 891 $ips = array(); 892 893 mysql_board_call( "SET read_buffer_size=1048576" ); 894 $mysql_unbuffered_reads = 1; 895 896 $query_archived = false; 897 898 if ($thread) { 899 if ($archive_mode === 0) { 900 $where = " WHERE archived = 0 AND (resto = $thread OR no = $thread)"; 901 } 902 else if ($archive_mode === 1) { 903 $where = " WHERE archived = 1 AND (resto = $thread OR no = $thread)"; 904 } 905 else { 906 $query_archived = true; 907 $where = " WHERE (archived = 0 AND resto = $thread) OR (archived = 1 AND resto = $thread) OR no = $thread"; 908 } 909 } 910 else { 911 if ($archive_mode === 1) { 912 $query_archived = true; 913 $where = ' WHERE archived = 1'; 914 } 915 else if ($optimised_indexes) { 916 $where = ' WHERE archived = 0 AND resto = 0'; 917 918 $_thread_ids = array(); 919 } 920 else { 921 $where = ' WHERE archived = 0'; 922 } 923 } 924 925 $fields = "no,sticky,permasage,closed,now,name,sub,com,host,pwd,filename,ext,w,h,tn_w,tn_h,tim,time,md5,fsize,last_modified,root,resto,filedeleted,id,capcode,country,undead,permaage,since4pass"; 926 927 if ($query_archived) { 928 $fields .= ",archived"; 929 } 930 931 if (MOBILE_IMG_RESIZE) { 932 $fields .= ",m_img"; 933 } 934 935 if (ENABLE_BOARD_FLAGS) { 936 $fields .= ",board_flag"; 937 } 938 939 $sql_cache = "sql_no_cache"; 940 941 $query = mysql_board_call( "SELECT $sql_cache $fields FROM `" . SQLLOG . "`" . $where ); 942 $offset = 0; 943 944 while( $row = mysql_fetch_assoc( $query ) ) { 945 if (!$query_archived) $row['archived'] = $archive_mode; 946 clean_log_row( $row ); 947 948 $row_no = $row['no']; 949 $row_resto = $row['resto']; 950 951 // IF mysql returns rows in order by default then replies come after OP 952 // so if OP doesn't exist, this post is orphaned and should be skipped 953 // TODO: let's not skip for now, it seems more likely to cause bugs than anything 954 //if ($fetching_whole_threads && $row_resto && !isset($log[$row_resto])) continue; 955 956 if ($optimised_indexes) { 957 $_thread_ids[] = (int)$row_no; 958 } 959 960 $ips[$row['host']] = TRUE; 961 962 if (!$row_resto) { 963 $row['children'] = array(); 964 $row['imgreplycount'] = 0; 965 $row['replycount'] = 0; 966 } 967 else { 968 if (isset($log[$row_resto])) { 969 $log[$row_resto]['children'][$row_no] = TRUE; 970 971 if ($row['fsize'] && !$row['filedeleted']) { 972 $log[$row_resto]['imgreplycount']++; 973 } 974 975 $log[$row_resto]['replycount']++; 976 } 977 } 978 979 $log[$row_no] = $row; 980 } 981 982 $query = null; 983 984 $nrows = count($log); 985 //if (BOARD_DIR=="b" && !$thread) quick_log_to("/www/perhost/logcaches.log", "inefficient all-board log_cache run t=$thread r=$nrows inv=$invalidate lev=$log_cache_level", true); 986 // if (!STATIC_REBUILD && $thread) quick_log_to("/www/perhost/logcaches.log", "inefficient? one-thread log_cache run t=$thread r=$nrows inv=$invalidate lev=$log_cache_level", true); 987 988 $is_single_thread = $thread && isset($log[$thread]['children']); 989 $ipcount = count( $ips ); 990 unset($ips); 991 992 $mysql_unbuffered_reads = 0; 993 mysql_board_call( "SET read_buffer_size=131072" ); 994 995 if (!$thread) { 996 if ($optimised_indexes) { 997 if (empty($_thread_ids)) { 998 return; 999 } 1000 1001 $_thread_ids = implode(',', $_thread_ids); 1002 1003 $query = mysql_board_call("SELECT resto, COUNT(*) AS r_count, SUM(IF(fsize > 0 AND filedeleted = 0, 1, 0)) AS i_count FROM `" . SQLLOG . "` WHERE resto IN($_thread_ids) GROUP BY resto"); 1004 1005 while ($row = mysql_fetch_assoc($query)) { 1006 $log[$row['resto']]['imgreplycount'] = (int)$row['i_count']; 1007 $log[$row['resto']]['replycount'] = (int)$row['r_count']; 1008 } 1009 1010 $query = mysql_board_call("SELECT no FROM `" . SQLLOG . "` WHERE no IN($_thread_ids) ORDER BY root DESC"); 1011 } 1012 else { 1013 if ($archive_mode === 1) { 1014 $archived = "archived = 1"; 1015 } else { 1016 $archived = "archived = 0"; 1017 } 1018 $query = mysql_board_call("SELECT no FROM `" . SQLLOG . "` WHERE $archived AND resto = 0 AND root > 0 ORDER BY root DESC"); 1019 } 1020 1021 $threads = array(); // IDs 1022 1023 while ($row = mysql_fetch_row($query)) { 1024 $no = (int)$row[0]; 1025 1026 if (isset($log[$no])) { 1027 $threads[] = $no; 1028 } 1029 } 1030 1031 $log['THREADS'] = $threads; 1032 1033 foreach( $threads as $thread ) { 1034 $this_thread = $log[$thread]; 1035 1036 if (!$this_thread['permaage'] && !$this_thread['sticky'] && $this_thread['replycount'] >= MAX_RES) { 1037 $log[$thread]['bumplimit'] = TRUE; 1038 } 1039 else { 1040 $log[$thread]['bumplimit'] = FALSE; 1041 } 1042 1043 if ($this_thread['archived']) { 1044 $log[$thread]['archived_on'] = strtotime($this_thread['root']); 1045 } 1046 1047 if (!$this_thread['permaage'] && !$this_thread['sticky'] && !$log[$thread]['undead'] && $this_thread['imgreplycount'] >= MAX_IMGRES) { 1048 $log[$thread]['imagelimit'] = TRUE; 1049 } 1050 else { 1051 $log[$thread]['imagelimit'] = FALSE; 1052 } 1053 1054 $log[$thread]['semantic_url'] = generate_href_context($this_thread['sub'], $this_thread['com']); 1055 } 1056 } 1057 else if ($is_single_thread) { 1058 if (!$log[$thread]['permaage'] && !$log[$thread]['sticky'] && $log[$thread]['replycount'] >= MAX_RES) { 1059 $log[$thread]['bumplimit'] = TRUE; 1060 } 1061 else { 1062 $log[$thread]['bumplimit'] = FALSE; 1063 } 1064 1065 if ($log[$thread]['archived']) { 1066 $log[$thread]['archived_on'] = strtotime($log[$thread]['root']); 1067 } 1068 1069 if (!$log[$thread]['permaage'] && !$log[$thread]['sticky'] && !$log[$thread]['undead'] && $log[$thread]['imgreplycount'] >= MAX_IMGRES) { 1070 $log[$thread]['imagelimit'] = TRUE; 1071 } 1072 else { 1073 $log[$thread]['imagelimit'] = FALSE; 1074 } 1075 1076 $log[$thread]['semantic_url'] = generate_href_context($log[$thread]['sub'], $log[$thread]['com']); 1077 } 1078 1079 // calculate old-status for PAGE_MAX mode 1080 //$threadcount = count( $threads ); 1081 1082 /*if(EXPIRE_NEGLECTED != 1) { 1083 rsort($threads, SORT_NUMERIC); 1084 1085 if(PAGE_MAX > 0) // the lowest 5% of maximum threads get marked old 1086 for($i = floor(0.95*PAGE_MAX*DEF_PAGES); $i < $threadcount; $i++) { 1087 if(!$log[$threads[$i]]['sticky']) 1088 $log[$threads[$i]]['old'] = 1; 1089 } 1090 else { // threads w/numbers below 5% of LOG_MAX get marked old 1091 foreach($threads as $thread) { 1092 if($lastno-LOG_MAX*0.95>$thread) 1093 if(!$log[$thread]['sticky']) 1094 $log[$thread]['old'] = 1; 1095 } 1096 } 1097 } else { 1098 $rthreads = array(); 1099 foreach ($threads as $t) { 1100 $root = $log[$t]['root']; 1101 $rthreads[$t] = $root; 1102 } 1103 1104 arsort($rthreads); 1105 $rthreads = array_keys($rthreads); 1106 1107 if (PAGE_MAX > 0) { 1108 $floor = (int)floor(0.95*PAGE_MAX*DEF_PAGES); 1109 for($i = $floor; $i < $threadcount; $i++) { 1110 if(!$log[$rthreads[$i]]['sticky']) 1111 $log[$rthreads[$i]]['old'] = 1; 1112 } 1113 } 1114 }*/ 1115 } 1116 1117 function rebuildallthumb($archiveonly = false) 1118 { 1119 global $log; 1120 if( !has_level() ) return; 1121 $starttime = microtime( true ); 1122 set_time_limit( 0 ); 1123 if (!$archiveonly) { 1124 log_cache(); 1125 $nposts = count($log); 1126 echo "Rebuilding $nposts live posts<br>\n"; 1127 1128 foreach( $log as $post ) { 1129 if( !$post["ext"] ) continue; 1130 1131 $ext = $post["ext"]; 1132 $tim = $post["tim"]; 1133 $resto = $post["resto"]; 1134 $fname = IMG_DIR . $tim . $ext; 1135 make_thumb( $fname, $tim, $ext, $resto, $TN_W, $TN_H, $tmd5 ); 1136 } 1137 $totaltime = microtime( true ) - $starttime; 1138 echo "Took $totaltime seconds for live posts<br>\n"; 1139 } 1140 1141 // Run again for archived thumbs 1142 $starttime = microtime( true ); 1143 mysql_check_connections(); 1144 log_cache(1, 0, 1); // fetch archives instead 1145 $nposts = count($log); 1146 echo "Rebuilding $nposts archived posts<br>\n"; 1147 1148 foreach( $log as $post ) { 1149 if( !$post["ext"] ) continue; 1150 1151 $ext = $post["ext"]; 1152 $tim = $post["tim"]; 1153 $resto = $post["resto"]; 1154 $fname = IMG_DIR . $tim . $ext; 1155 make_thumb( $fname, $tim, $ext, $resto, $TN_W, $TN_H, $tmd5 ); 1156 } 1157 $totaltime = microtime( true ) - $starttime; 1158 echo "Took $totaltime seconds for archived posts<br>\n"; 1159 } 1160 1161 /** 1162 * Displays some text on a lightweight page styled using the default stylesheet. 1163 * $message should be html-escaped 1164 */ 1165 function headless_message($message, $autoclose = false) { 1166 if (DEFAULT_BURICHAN) { 1167 $css = 'yotsubluenew'; 1168 $ws = 'ws'; 1169 } 1170 else { 1171 $css = 'yotsubanew'; 1172 $ws = ''; 1173 } 1174 1175 $css_ver = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION; 1176 1177 if ($error) { 1178 $err_css = 'color: red;'; 1179 } 1180 else { 1181 $err_css = ''; 1182 } 1183 1184 if ($autoclose) { 1185 $js = <<<JS 1186 <script>setTimeout(function() { self.close(); }, 3000);</script> 1187 JS; 1188 } 1189 else { 1190 $js = ''; 1191 } 1192 1193 $html = <<<HTML 1194 <!DOCTYPE html><html><head> 1195 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 1196 <meta http-equiv="pragma" content="no-cache"> 1197 <link rel="icon" type="image/x-icon" href="//s.4cdn.org/image/favicon-team$ws.ico"> 1198 <link rel="stylesheet" style="text/css" href="//s.4cdn.org/css/$css.$css_ver.css"> 1199 </head> 1200 <body> 1201 <table style="text-align: center; width: 100%; height: 150px; border: 0;"> 1202 <tr valign="middle"> 1203 <td align="center" style="font-size: x-large; font-weight: bold; border: 0;$err_css"> 1204 <span>$message</span> 1205 </td> 1206 </tr> 1207 </table>$js 1208 </body> 1209 </html> 1210 HTML; 1211 1212 return $html; 1213 } 1214 1215 function do_move_thread() { 1216 if (!isset($_POST['id']) || !isset($_POST['board'])) { 1217 updating_index(); 1218 } 1219 1220 if (!has_level()) { 1221 updating_index(); 1222 } 1223 1224 $del = isset($_POST['move_del']) && $_POST['move_del']; 1225 1226 $ret = move_thread($_POST['id'], $_POST['board'], $del); 1227 1228 if (is_array($ret)) { 1229 $message = 'Thread moved to <a target="_blank" href="//boards.' 1230 . L::d($ret[0]) . '/' . $ret[0] . '/thread/' . $ret[1] 1231 . '">/' . $ret[0] . '/' . $ret[1] . '</a>'; 1232 1233 echo headless_message($message, true); 1234 } 1235 else { 1236 echo headless_message("<span style=\"color:red\">$ret</span>"); 1237 } 1238 } 1239 1240 function do_copy_threads() { 1241 if (!has_level()) { 1242 updating_index(); 1243 return; 1244 } 1245 global $argv; 1246 1247 $to_board = $_REQUEST["board"]; 1248 if (!$to_board) $to_board = $argv[2]; 1249 1250 if (!$to_board) { 1251 updating_index(); 1252 return; 1253 } 1254 1255 set_time_limit( 0 ); 1256 header( "Pragma: no-cache" ); 1257 _print(fancystyle()); 1258 1259 if (UPLOAD_BOARD || JANITOR_BOARD) { 1260 $ret = "The current board doesn't support this feature."; 1261 } else if ($to_board === 'f' || $to_board === 'j') { 1262 $ret = "The destination board doesn't support this feature."; 1263 } else { 1264 $threads = mysql_column_array(mysql_board_call("SELECT no FROM `%s` WHERE resto = 0 AND archived = 0", BOARD_DIR)); 1265 foreach ($threads as $thread) {$ret = copy_thread($thread, $to_board); _print("$thread<br>\n");} 1266 } 1267 1268 if (is_array($ret)) { 1269 $message = 'Thread copied to <a target="_blank" href="//boards.' 1270 . L::d($ret[0]) . '/' . $ret[0] . '/thread/' . $ret[1] 1271 . '">/' . $ret[0] . '/' . $ret[1] . '</a>'; 1272 1273 echo headless_message($message, true); 1274 } 1275 else { 1276 echo headless_message("<span style=\"color:red\">$ret</span>"); 1277 } 1278 } 1279 1280 function copy_thread($thread_id, $to_board, $delete = false) { 1281 $thread_id = (int)$thread_id; 1282 1283 if (!$thread_id) { 1284 return "Invalid thread ID."; 1285 } 1286 1287 // Validate destination board 1288 if (!preg_match('/^[a-z0-9]+$/', $to_board)) { 1289 return 'Invalid destination board.'; 1290 } 1291 1292 $query = "SELECT COUNT(*) FROM boardlist WHERE dir = '%s' LIMIT 1"; 1293 1294 $res = mysql_global_call($query, $to_board); 1295 1296 if (!$res) { 1297 return "Database Error (1)"; 1298 } 1299 1300 if (mysql_num_rows($res) < 1) { 1301 return "Destination board doesn't exist."; 1302 } 1303 1304 // --- 1305 1306 // Fetch the whole thread 1307 $posts = array(); 1308 1309 $res = mysql_board_call("SELECT * FROM `%s` WHERE no = %d AND resto = 0", BOARD_DIR, $thread_id); 1310 if (!$res) { 1311 return "Database Error (3)"; 1312 } 1313 1314 $row = mysql_fetch_assoc($res); 1315 if (!$row) { 1316 return "Thread not found."; 1317 } 1318 1319 $posts[] = $row; 1320 $res = mysql_board_call("SELECT * FROM `%s` WHERE resto = %d", BOARD_DIR, $thread_id); 1321 if (!$res) { 1322 return "Database Error (4)"; 1323 } 1324 1325 while ($row = mysql_fetch_assoc($res)) { 1326 if (!$row) { 1327 continue; 1328 } 1329 $posts[] = $row; 1330 } 1331 1332 // Copy posts to the other board 1333 mysql_board_call('START TRANSACTION'); 1334 1335 $new_resto = 0; 1336 1337 $from_pids = array(); 1338 $to_pids = array(); 1339 1340 foreach ($posts as &$post) { 1341 $comment = str_replace($from_pids, $to_pids, $post['com']); 1342 1343 if ($new_resto === 0) { 1344 $root_time = $post['root']; 1345 } 1346 else { 1347 $root_time = 0; 1348 } 1349 1350 $query = "INSERT INTO `$to_board`(now,name,sub,com,host,pwd,filename,ext,w, 1351 h,tn_w,tn_h, tim,time,last_modified,md5,fsize,root,resto,capcode, 1352 4pass_id,since4pass,filedeleted,tmd5,id,sticky,closed,country) 1353 VALUE (" . 1354 "'" . $post['now'] . "'," . 1355 "'" . mysql_real_escape_string($post['name']) . "'," . 1356 "'" . mysql_real_escape_string($post['sub']) . "'," . 1357 "'" . mysql_real_escape_string($comment) . "'," . 1358 "'" . mysql_real_escape_string($post['host']) . "'," . 1359 "'" . mysql_real_escape_string($post['pwd']) . "'," . 1360 "'" . mysql_real_escape_string($post['filename']) . "'," . 1361 "'" . $post['ext'] . "'," . 1362 (int)$post['w'] . "," . 1363 (int)$post['h'] . "," . 1364 (int)$post['tn_w'] . "," . 1365 (int)$post['tn_h'] . "," . 1366 "'" . $post['tim'] . "'," . 1367 (int)$post['time'] . "," . 1368 (int)$post['time'] . "," . 1369 "'" . $post['md5'] . "'," . 1370 (int)$post['fsize'] . "," . 1371 "'" . $root_time . "'," . 1372 $new_resto . "," . 1373 "'" . $post['capcode'] . "'," . 1374 "'" . $post['4pass_id'] . "'," . 1375 (int)$post['since4pass'] . "," . 1376 (int)$post['filedeleted'] . "," . 1377 "'" . $post['tmd5'] . "'," . 1378 "'" . mysql_real_escape_string($post['id']) . "'," . 1379 (int)$post['sticky'] . "," . 1380 (int)$post['closed'] . "," . 1381 "'XX')"; 1382 1383 $res = mysql_board_call($query); 1384 if (!$res) { 1385 if ($new_resto === 0) { 1386 mysql_board_call('ROLLBACK'); 1387 return 'Database Error (5)'; 1388 } 1389 1390 $post['ext'] = null; 1391 1392 continue; 1393 } 1394 1395 $new_pid = mysql_board_insert_id(); 1396 1397 if ($new_resto === 0) { 1398 $new_resto = $new_pid; 1399 } 1400 1401 $from_pids[] = ">>{$post['no']}"; 1402 $to_pids[] = ">>$new_pid"; 1403 1404 $post['new_id'] = $new_pid; 1405 } 1406 1407 unset($post); 1408 1409 mysql_board_call('COMMIT'); 1410 1411 // Copy files 1412 // If the file already exists, update the database and set it as "deleted" 1413 $to_img_dir = preg_replace('/' . BOARD_DIR . '\/$/', '', IMG_ROOT) . $to_board . '/'; 1414 $to_thumb_dir = preg_replace('/' . BOARD_DIR . '\/$/', '', THUMB_ROOT) . $to_board . '/'; 1415 1416 $dup_pids = array(); 1417 1418 foreach ($posts as $post) { 1419 if (!$post['ext'] || $post['filedeleted']) { 1420 continue; 1421 } 1422 1423 $src_thumb = THUMB_DIR . $post['tim'] . 's.jpg'; 1424 $dest_thumb = $to_thumb_dir . $post['tim'] . 's.jpg'; 1425 1426 if (file_exists($dest_thumb)) { 1427 //$dup_pids[] = $post['new_id']; 1428 continue; 1429 } 1430 1431 $src_img = IMG_DIR . $post['tim'] . $post['ext']; 1432 $dest_img = $to_img_dir . $post['tim'] . $post['ext']; 1433 1434 @copy($src_thumb, $dest_thumb); 1435 @copy($src_img, $dest_img); 1436 } 1437 1438 if (!empty($dup_pids)) { 1439 $dup_clause = implode(',', $dup_pids); 1440 $query = "UPDATE `$to_board` SET filedeleted = 1, ext = '' WHERE no IN($dup_clause)"; 1441 $res = mysql_board_call($query); 1442 } 1443 1444 // Ask the destination board to build the new thread 1445 remote_rebuild_live_thread($to_board, $new_resto); 1446 1447 // Rebuild indexes 1448 if (!STATIC_REBUILD) { 1449 updatelog(0, 0); 1450 } 1451 1452 return array($to_board, $new_resto); 1453 } 1454 1455 function move_thread($thread_id, $to_board, $delete = false) { 1456 if (UPLOAD_BOARD || BOARD_DIR === 'b' || JANITOR_BOARD) { 1457 return "The current board doesn't support this feature."; 1458 } 1459 1460 if ($to_board === 'f' || $to_board === 'j') { 1461 return "The destination board doesn't support this feature."; 1462 } 1463 1464 if ($to_board === BOARD_DIR) { 1465 return "Invalid destination board."; 1466 } 1467 1468 $thread_id = (int)$thread_id; 1469 1470 if (!$thread_id) { 1471 return "Invalid thread ID."; 1472 } 1473 1474 // Validate destination board 1475 if (!preg_match('/^[a-z0-9]+$/', $to_board)) { 1476 return 'Invalid destination board.'; 1477 } 1478 1479 $query = "SELECT COUNT(*) FROM boardlist WHERE dir = '%s' LIMIT 1"; 1480 1481 $res = mysql_global_call($query, $to_board); 1482 1483 if (!$res) { 1484 return "Database Error (1)"; 1485 } 1486 1487 if (mysql_num_rows($res) < 1) { 1488 return "Destination board doesn't exist."; 1489 } 1490 1491 // --- 1492 1493 $board = mysql_real_escape_string(BOARD_DIR); 1494 1495 // Lock the thread immediately 1496 $query = "UPDATE `$board` SET closed = 1 WHERE no = $thread_id"; 1497 1498 $res = mysql_board_call($query); 1499 1500 if (!$res) { 1501 return "Database Error (2)"; 1502 } 1503 1504 if (mysql_affected_rows() !== 1) { 1505 return "This thread is locked."; 1506 } 1507 1508 // Fetch the whole thread 1509 $posts = array(); 1510 1511 $query = "SELECT * FROM `$board` WHERE no = $thread_id AND resto = 0"; 1512 1513 $res = mysql_board_call($query); 1514 1515 if (!$res) { 1516 return "Database Error (3)"; 1517 } 1518 1519 $row = mysql_fetch_assoc($res); 1520 1521 if (!$row) { 1522 return "Thread not found."; 1523 } 1524 1525 if ($row['archived'] !== '0') { 1526 return "You cannot move archived threads."; 1527 } 1528 1529 $posts[] = $row; 1530 1531 $query = "SELECT * FROM `$board` WHERE resto = $thread_id"; 1532 1533 $res = mysql_board_call($query); 1534 1535 if (!$res) { 1536 return "Database Error (4)"; 1537 } 1538 1539 while ($row = mysql_fetch_assoc($res)) { 1540 if (!$row) { 1541 continue; 1542 } 1543 $posts[] = $row; 1544 } 1545 1546 // Copy posts to the other board 1547 mysql_board_call('START TRANSACTION'); 1548 1549 $new_resto = 0; 1550 1551 $from_pids = array(); 1552 $to_pids = array(); 1553 1554 foreach ($posts as &$post) { 1555 $comment = str_replace($from_pids, $to_pids, $post['com']); 1556 1557 if ($new_resto === 0) { 1558 $root_time = 'NOW()'; 1559 } 1560 else { 1561 $root_time = 0; 1562 } 1563 1564 if (SHOW_COUNTRY_FLAGS && $post['board_flag'] == '') { 1565 $flag_val = $post['country']; 1566 } 1567 else { 1568 $flag_val = 'XX'; 1569 } 1570 1571 $query = "INSERT INTO `$to_board`(now,name,sub,com,host,pwd,email,filename,ext,w, 1572 h,tn_w,tn_h, tim,time,last_modified,md5,fsize,root,resto,capcode, 1573 4pass_id,since4pass,filedeleted,tmd5,id,country) 1574 VALUE (" . 1575 "'" . $post['now'] . "'," . 1576 "'" . mysql_real_escape_string($post['name']) . "'," . 1577 "'" . mysql_real_escape_string($post['sub']) . "'," . 1578 "'" . mysql_real_escape_string($comment) . "'," . 1579 "'" . mysql_real_escape_string($post['host']) . "'," . 1580 "'" . mysql_real_escape_string($post['pwd']) . "'," . 1581 "'" . mysql_real_escape_string($post['email']) . "'," . 1582 "'" . mysql_real_escape_string($post['filename']) . "'," . 1583 "'" . $post['ext'] . "'," . 1584 (int)$post['w'] . "," . 1585 (int)$post['h'] . "," . 1586 (int)$post['tn_w'] . "," . 1587 (int)$post['tn_h'] . "," . 1588 "'" . $post['tim'] . "'," . 1589 (int)$post['time'] . "," . 1590 (int)$post['time'] . "," . 1591 "'" . $post['md5'] . "'," . 1592 (int)$post['fsize'] . "," . 1593 $root_time . "," . 1594 $new_resto . "," . 1595 "'" . $post['capcode'] . "'," . 1596 "'" . $post['4pass_id'] . "'," . 1597 (int)$post['since4pass'] . "," . 1598 (int)$post['filedeleted'] . "," . 1599 "'" . $post['tmd5'] . "'," . 1600 "''," . 1601 "'" . $flag_val . "')"; 1602 1603 $res = mysql_board_call($query); 1604 1605 if (!$res) { 1606 if ($new_resto === 0) { 1607 mysql_board_call('ROLLBACK'); 1608 return 'Database Error (5)'; 1609 } 1610 1611 $post['ext'] = null; 1612 1613 continue; 1614 } 1615 1616 $new_pid = mysql_board_insert_id(); 1617 1618 if ($new_resto === 0) { 1619 $new_resto = $new_pid; 1620 } 1621 1622 $from_pids[] = ">>{$post['no']}"; 1623 $to_pids[] = ">>$new_pid"; 1624 1625 $post['new_id'] = $new_pid; 1626 } 1627 1628 unset($post); 1629 1630 mysql_board_call('COMMIT'); 1631 1632 // Log the action 1633 $thread = $posts[0]; 1634 1635 $log_com = "<b>From /$board/$thread_id to /$to_board/$new_resto</b>"; 1636 1637 if ($thread['com'] !== '') { 1638 $log_com = $thread['com'] . '<br><br>' . $log_com; 1639 } 1640 1641 $action_log_post = array( 1642 'no' => $thread['no'], 1643 'name' => $thread['name'], 1644 'sub' => $thread['sub'], 1645 'com' => $log_com, 1646 'filename' => $thread['filename'], 1647 'ext' => $thread['ext'] 1648 ); 1649 1650 log_mod_action(6, $action_log_post); 1651 1652 // Copy files 1653 // If the file already exists, update the database and set it as "deleted" 1654 $to_img_dir = preg_replace('/' . BOARD_DIR . '\/$/', '', IMG_ROOT) . $to_board . '/'; 1655 $to_thumb_dir = preg_replace('/' . BOARD_DIR . '\/$/', '', THUMB_ROOT) . $to_board . '/'; 1656 1657 $dup_pids = array(); 1658 1659 foreach ($posts as $post) { 1660 if (!$post['ext'] || $post['filedeleted']) { 1661 continue; 1662 } 1663 1664 $src_thumb = THUMB_DIR . $post['tim'] . 's.jpg'; 1665 $dest_thumb = $to_thumb_dir . $post['tim'] . 's.jpg'; 1666 1667 if (file_exists($dest_thumb)) { 1668 $dup_pids[] = $post['new_id']; 1669 continue; 1670 } 1671 1672 $src_img = IMG_DIR . $post['tim'] . $post['ext']; 1673 $dest_img = $to_img_dir . $post['tim'] . $post['ext']; 1674 1675 copy($src_thumb, $dest_thumb); 1676 copy($src_img, $dest_img); 1677 } 1678 1679 if (!empty($dup_pids)) { 1680 $dup_clause = implode(',', $dup_pids); 1681 $query = "UPDATE `$to_board` SET filedeleted = 1, ext = '' WHERE no IN($dup_clause)"; 1682 $res = mysql_board_call($query); 1683 } 1684 1685 // Insert notification post if deletion is not requested 1686 if (!$delete) { 1687 $msg = sprintf(S_THREAD_MOVED, ">>>/$to_board/$new_resto"); 1688 1689 $post_time = $_SERVER['REQUEST_TIME']; 1690 $tim = generate_tim(); 1691 1692 $query = "INSERT INTO `$board`(now,name,sub,com,host,pwd,filename,ext,w, 1693 h,tn_w,tn_h, tim,time,last_modified,md5,fsize,resto,capcode, 1694 4pass_id,tmd5,id) 1695 VALUE (" . 1696 "'" . date('m/d/y(D)H:i:s', $post_time) . "'," . 1697 "'" . S_ANONAME . "'," . 1698 "''," . 1699 "'" . mysql_real_escape_string($msg) . "'," . 1700 "'', '', '', '', 0, 0, 0, 0, '" . $tim . "'," . $post_time . "," . 1701 $post_time . ", '',0," . $thread_id . ", 'mod', '', '', '')"; 1702 1703 $res = mysql_board_call($query); 1704 } 1705 1706 // Ask the destination board to build the new thread 1707 remote_rebuild_live_thread($to_board, $new_resto); 1708 1709 // Delete source thread if deletion is requested 1710 if ($delete) { 1711 // id, pwd, imgonly, auto, die 1712 delete_post($thread_id, 'trim', 0, 2, 1, 0); 1713 } 1714 // Archive source thread if archiving is enabled 1715 else if (ENABLE_ARCHIVE) { 1716 archive_thread($thread_id); 1717 1718 if (!STATIC_REBUILD && ENABLE_JSON_THREADS) { 1719 generate_board_archived_json(); 1720 } 1721 } 1722 // Rebuild source thread and indexes if archiving is disabled 1723 else { 1724 updatelog($thread_id, 1); 1725 } 1726 1727 // Rebuild indexes 1728 if (!STATIC_REBUILD) { 1729 updatelog(0, 0); 1730 } 1731 1732 return array($to_board, $new_resto); 1733 } 1734 1735 function remote_rebuild_live_thread($board, $pid) { 1736 $post = array( 1737 'mode' => 'rebuildadmin', 1738 'no' => $pid 1739 ); 1740 1741 rpc_start_request("https://sys.int/$board/imgboard.php", $post, $_COOKIE, true); 1742 1743 return true; 1744 } 1745 1746 function archive_thread($thread_id) { 1747 global $log; 1748 1749 $thread_id = (int)$thread_id; 1750 1751 $board = mysql_real_escape_string(BOARD_DIR); 1752 1753 if (!$thread_id) { 1754 return; 1755 } 1756 1757 // Regenerate the user ID before clearing the IP 1758 $uid = ''; 1759 1760 if (DISP_ID && DISP_ID_PER_THREAD && !DISP_ID_RANDOM) { 1761 $th = null; 1762 1763 if (!IS_REBUILDD && isset($log[$thread_id])) { 1764 $th = $log[$thread_id]; 1765 } 1766 else { 1767 $query = 'SELECT id, host FROM `' . BOARD_DIR . "` WHERE no = $thread_id"; 1768 1769 $res = mysql_board_call($query); 1770 1771 if ($res) { 1772 $th = mysql_fetch_assoc($res); 1773 } 1774 } 1775 1776 if ($th && $th['id'] !== '' && $th['host']) { 1777 $uid = generate_uid($thread_id, $_SERVER['REQUEST_TIME'], $th['host']); 1778 $uid = ", id = '" . mysql_real_escape_string($uid) . "'"; 1779 } 1780 } 1781 1782 // Update the OP. "root" is used for archive pruning. 1783 $query = <<<SQL 1784 UPDATE `$board` 1785 SET archived = 1, closed = 1, sticky = 0, email = '', host = '', 4pass_id = '', pwd = '', root = NOW()$uid 1786 WHERE no = $thread_id 1787 LIMIT 1 1788 SQL; 1789 1790 $res = mysql_board_call($query); 1791 1792 if (!$res) { 1793 return; 1794 } 1795 1796 // Update replies 1797 $query = <<<SQL 1798 UPDATE `$board` 1799 SET archived = 1, email = '', host = '', 4pass_id = '', pwd = '' 1800 WHERE resto = $thread_id 1801 SQL; 1802 1803 $res = mysql_board_call($query); 1804 1805 // Update cached $log 1806 if (isset($log[$thread_id])) { 1807 $log[$thread_id]['archived'] = true; 1808 $log[$thread_id]['archived_on'] = time(); 1809 1810 $thread_key = array_search($thread_id, $log['THREADS']); 1811 1812 if ($thread_key !== false) { 1813 unset($log['THREADS'][$thread_key]); 1814 } 1815 } 1816 1817 // Rebuild the thread 1818 rebuild_archived_thread($thread_id); 1819 1820 /** 1821 * Clear reports (only posts with less than 3 "illegal" reports) 1822 */ 1823 // Get all post ids 1824 $query = "SELECT no FROM `$board` WHERE no = $thread_id OR resto = $thread_id"; 1825 1826 $res = mysql_board_call($query); 1827 1828 if (!$res || !mysql_num_rows($res)) { 1829 return; 1830 } 1831 1832 // Get ids of reported posts, with less than 3 "illegal" reports 1833 $post_ids = array(); 1834 1835 while ($row = mysql_fetch_row($res)) { 1836 $post_ids[] = $row[0]; 1837 } 1838 1839 $in_clause_all = implode(',', $post_ids); 1840 1841 $query = <<<SQL 1842 SELECT postid FROM reports_for_posts 1843 WHERE board = '$board' AND num_illegal < 3 AND postid IN($in_clause_all) 1844 SQL; 1845 1846 $res = mysql_global_call($query); 1847 1848 if (!$res || !mysql_num_rows($res)) { 1849 return; 1850 } 1851 1852 // Delete reports for posts with less than 3 "illegal" reports 1853 $post_ids = array(); 1854 1855 while ($row = mysql_fetch_row($res)) { 1856 $post_ids[] = $row[0]; 1857 } 1858 1859 $in_clause_reports = implode(',', $post_ids); 1860 1861 $query = "DELETE FROM reports WHERE board = '$board' AND no IN($in_clause_reports)"; 1862 mysql_global_call($query); 1863 1864 $query = "DELETE FROM reports_for_posts WHERE board = '$board' AND postid IN($in_clause_reports)"; 1865 mysql_global_call($query); 1866 1867 // Handle XFF entries 1868 if (SAVE_XFF) { 1869 $query = "UPDATE xff SET is_live = 0 WHERE board = '$board' AND postno IN($in_clause_all)"; 1870 mysql_global_call($query); 1871 } 1872 } 1873 1874 function thumb_url() 1875 { 1876 return "//" . THUMB_DIR2_PART; 1877 } 1878 1879 function display_no( $no ) 1880 { 1881 if (FAKE_DOUBLES) { 1882 $last_digit = $no % 10; 1883 return $no.$last_digit; 1884 } 1885 1886 return $no; 1887 } 1888 1889 function display_uid( $id, $capcode = '' ) 1890 { 1891 if( DISP_ID == 0 || !$id ) return ""; 1892 $normid = $id; // preg_replace( "#[^A-Za-z0-9]#", "_", $id ); 1893 1894 1895 if( $id == "Mod" ) { 1896 $id = '<span style="color: #800080; font-weight: bold;" class="posteruid id_mod">Mod</span>' . $capcode; 1897 } else if( $id == "Admin" ) { 1898 $id = '<span style="color: #F00000; font-weight: bold;" class="posteruid id_admin">Admin</span>' . $capcode; 1899 } else if( $id == 'Developer' ) { 1900 $id = '<span style="color: #0000F0; font-weight: bold;" class="posteruid id_developer">Developer</span>' . $capcode; 1901 } else if( $id == 'Manager' ) { 1902 $id = '<span style="color: #FF0080; font-weight: bold;" class="posteruid id_manager">Manager</span>' . $capcode; 1903 } else { 1904 $id = htmlspecialchars( $id ); 1905 } 1906 1907 return " <span class=\"posteruid id_$normid\">(ID: <span class=\"hand\" title=\"Highlight posts by this ID\">$id</span>)</span>"; 1908 } 1909 1910 function emailencode( $str ) 1911 { 1912 return str_replace( "%40", "@", rawurlencode( $str ) ); 1913 } 1914 1915 function renderPostHtml($no, $in_thread, $sorted_replies = null, $reply_count = null, $shown_replies = null, $is_archived = false) { 1916 global $log, $board_flags_array; 1917 1918 extract($log[$no]); 1919 1920 $namestyle = ''; 1921 1922 if( JANITOR_BOARD == 1 ) { 1923 $namestyle = broomcloset_style( $name ); 1924 $name = broomcloset_name( $name ); 1925 } 1926 1927 $mname = $name; 1928 $mname_truncated = ''; 1929 if( $capcode == 'none' && mb_strlen( $name ) > 30 ) { 1930 $mname = explode( '</span>', $name, 2 ); 1931 if( mb_strlen( $mname[0] ) > 30 ) { 1932 $mname[0] = htmlspecialchars_decode($mname[0], ENT_QUOTES); 1933 $mname[0] = mb_substr( $mname[0], 0, 30 ) . '(...)'; 1934 $mname[0] = htmlspecialchars($mname[0], ENT_QUOTES); 1935 $mname_truncated = ' data-tip data-tip-cb="mShowFull"'; 1936 } 1937 1938 $mname = implode( '</span>', $mname ); 1939 } 1940 1941 $hasCapcode = $capcode === 'none' ? '' : ' capcode'; 1942 1943 // NEW CAPCODE STUFF 1944 switch( $capcode ) { 1945 case 'admin': 1946 $capcodeStart = ' <strong class="capcode hand id_admin" title="Highlight posts by Administrators">## Admin</strong>'; 1947 $capcode_class = ' capcodeAdmin'; 1948 1949 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'adminicon.gif" alt="Admin Icon" title="This user is a 4chan Administrator." class="identityIcon retina">'; 1950 $highlight = ''; 1951 break; 1952 1953 case 'founder': 1954 $capcodeStart = ' <strong class="capcode hand" title="Highlight posts by the Founder">## Founder</strong>'; 1955 $capcode_class = ' capcodeFounder'; 1956 1957 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'foundericon.gif" alt="Founder Icon" title="This user is 4chan\'s Founder." class="identityIcon retina">'; 1958 $highlight = ''; 1959 break; 1960 1961 case 'admin_highlight': 1962 $capcodeStart = ' <strong class="capcode hand id_admin" title="Highlight posts by Administrators">## Admin</strong>'; 1963 $capcode_class = ' capcodeAdmin'; 1964 1965 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'adminicon.gif" alt="Admin Icon" title="This user is a 4chan Administrator." class="identityIcon retina">'; 1966 $highlight = ' highlightPost'; 1967 break; 1968 1969 case 'mod': 1970 $capcodeStart = ' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>'; 1971 $capcode_class = ' capcodeMod'; 1972 1973 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'modicon.gif" alt="Mod Icon" title="This user is a 4chan Moderator." class="identityIcon retina">'; 1974 $highlight = ''; 1975 break; 1976 1977 case 'developer': 1978 $capcodeStart = ' <strong class="capcode hand id_developer" title="Highlight posts by Developers">## Developer</strong>'; 1979 $capcode_class = ' capcodeDeveloper'; 1980 1981 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'developericon.gif" alt="Developer Icon" title="This user is a 4chan Developer." class="identityIcon retina">'; 1982 $highlight = ''; 1983 break; 1984 1985 case 'manager': 1986 $capcodeStart = ' <strong class="capcode hand id_manager" title="Highlight posts by Managers">## Manager</strong>'; 1987 $capcode_class = ' capcodeManager'; 1988 1989 $capcode = ' <img src="' . STATIC_IMG_DIR2 . 'managericon.gif" alt="Manager Icon" title="This user is a 4chan Manager." class="identityIcon retina">'; 1990 $highlight = ''; 1991 break; 1992 1993 case 'verified': 1994 $capcodeStart = ' <strong class="capcode hand id_verified" title="Highlight posts by Verified Users">## Verified</strong>'; 1995 $capcode_class = ' capcodeVerified'; 1996 1997 $capcode = ''; 1998 $highlight = ''; 1999 break; 2000 2001 default: 2002 $capcode = $capcodeStart = $highlight = $capcode_class = ''; 2003 break; 2004 } 2005 2006 $spoiler = 0; 2007 2008 if( strpos( $sub, 'SPOILER<>' ) === 0 ) { 2009 $sub = substr( $sub, strlen( 'SPOILER<>' ) ); 2010 $spoiler = 1; 2011 } 2012 2013 // Only OPs have subjects 2014 if ($sorted_replies !== null) { 2015 $subshortm = $sub; 2016 if( mb_strlen( $sub ) > 30 ) { 2017 $sub = str_replace( array(','), ',', $sub ); 2018 $cutsub = htmlspecialchars_decode( $sub, ENT_QUOTES ); 2019 $cutsub = mb_substr( $cutsub, 0, 30 ); 2020 $cutsub = htmlspecialchars( $cutsub, ENT_QUOTES ); 2021 2022 $subshortm = '<span data-tip data-tip-cb="mShowFull">' . $cutsub . '(...)</span>'; 2023 } 2024 2025 $subshortm = '<span class="subject">' . $subshortm . '</span> '; 2026 $sub = '<span class="subject">' . $sub . '</span> '; 2027 } 2028 else { 2029 $sub = $subshortm = ''; 2030 } 2031 2032 $com = auto_link( $com, $in_thread ); 2033 2034 if( !$in_thread ) { 2035 list( $com, $abbreviated ) = abbreviate( $com, MAX_LINES_SHOWN ); 2036 } 2037 2038 if( isset( $abbreviated ) && $abbreviated ) { 2039 $com .= '<br><br><span class="abbr">Comment too long. <a href="' . RES_DIR2 . ( $resto ? $resto : $no ) . PHP_EXT2 . '#p' . $no . '">Click here</a> to view the full text.</span>'; 2040 } 2041 2042 // Image tag creation 2043 $file = ''; 2044 if( $ext ) { 2045 $img = IMG_DIR . $tim . $ext; 2046 $displaysrc = IMG_DIR2 . $tim . $ext; 2047 2048 //if ($ext !== ".swf" && BOARD_DIR !== 'j') { 2049 //if ($no % 100 >= 74) { 2050 //$displaysrc = "//is2.4chan.org/" . BOARD_DIR . "/" . $tim . $ext; 2051 //} 2052 //} 2053 2054 $linksrc = ( ( USE_SRC_CGI == 1 ) ? ( str_replace( '.cgi', '', IMG_DIR2 ) . $tim . $ext ) : $displaysrc ); 2055 2056 if( defined( 'INTERSTITIAL_LINK' ) ) { 2057 $linksrc = str_replace( INTERSTITIAL_LINK, '', $linksrc ); 2058 } 2059 2060 // Original filename truncation 2061 $unescaped_filename = htmlspecialchars_decode($filename, ENT_QUOTES); 2062 if( mb_strlen( $unescaped_filename, 'UTF-8' ) > 30 ) { 2063 $shortname = mb_substr($unescaped_filename, 0, 25, 'UTF-8'); 2064 $shortname = htmlspecialchars($shortname, ENT_QUOTES). '(...)' . $ext; 2065 $longname = $filename . $ext; 2066 $need_file_tooltip = true; 2067 } 2068 else { 2069 $shortname = $longname = $filename . $ext; 2070 $need_file_tooltip = false; 2071 } 2072 2073 if( THREAD_AD == 1 ) { 2074 if( defined( 'THREAD_AD_TXT' ) && THREAD_AD_TXT ) { 2075 $ad = text_link_ad( THREAD_AD_TXT ); 2076 if( $ad ) 2077 $dat .= "<span class=\"filesize\">" . S_ADNAME . " : $ad</span><br>"; 2078 } 2079 } 2080 2081 $s_src = IMG_DIR . $tim . $ext; 2082 2083 // 32>24 byte ascii>base64 conversion 2084 $shortmd5 = base64_encode( pack( 'H*', $md5 ) ); 2085 if( $fsize >= 1048576 ) { 2086 $size = round( ( $fsize / 1048576 ), 2 ) . ' M'; 2087 } 2088 elseif( $fsize >= 1024 ) { 2089 $size = round( $fsize / 1024 ) . ' K'; 2090 } 2091 else { 2092 $size = $fsize . ' '; 2093 } 2094 2095 $ftype = strtoupper( substr( $ext, 1 ) ); 2096 $mFileInfo = '<div data-tip data-tip-cb="mShowFull" class="mFileInfo mobile">' . $size . 'B ' . $ftype . '</div>'; 2097 2098 if( !$tn_w && !$tn_h && $ext == '.gif' ) { 2099 $tn_w = $w; 2100 $tn_h = $h; 2101 } 2102 2103 $class = ''; 2104 if( $spoiler ) { 2105 $class = ' imgspoiler'; 2106 // Replace 3 image tags with one, makes it easier to change in the future 2107 $imgthumb_src = SPOILER_THUMB; 2108 $tn_w = '100'; 2109 $tn_h = '100'; 2110 } 2111 else { 2112 //$imgthumb_src = thumb_url() . $tim . 's.jpg'; 2113 $imgthumb_src = '//' . THUMB_DIR2_PART . $tim . 's.jpg'; 2114 } 2115 2116 if (MOBILE_IMG_RESIZE && $m_img) { 2117 $m_img_attr = ' data-m'; 2118 } 2119 else { 2120 $m_img_attr = ''; 2121 } 2122 2123 $imgsrc = '<a class="fileThumb' . $class . '" href="' . $displaysrc . '" target="_blank"' . $m_img_attr . '><img src="' . $imgthumb_src . '" alt="' . $size . 'B" data-md5="' . $shortmd5 . '" style="height: ' . $tn_h . 'px; width: ' . $tn_w . 'px;" loading="lazy">' . $mFileInfo . '</a>'; 2124 2125 if( $filedeleted ) { 2126 $fileinfo = '<span class="fileThumb"><img src="' . STATIC_IMG_DIR2 . 'filedeleted-res.gif" alt="File deleted." class="fileDeletedRes retina"></span>'; 2127 $imgsrc = ''; 2128 } 2129 else { 2130 $dimensions = ( $ext == '.pdf' ) ? 'PDF' : $w . 'x' . $h; 2131 if( !$spoiler ) { 2132 $fileinfo = '<div class="fileText" id="fT' . $no . '">' . S_PICNAME . ': <a' . ($need_file_tooltip ? (' title="' . $longname . '"') : '') . ' href="' . $linksrc . '" target="_blank">' . $shortname . '</a> (' . $size . 'B, ' . $dimensions . ')</div>'; 2133 } 2134 else { 2135 $fileinfo = '<div class="fileText" id="fT' . $no . '" title="' . $longname . '">' . S_PICNAME . ': <a href="' . $linksrc . '" target="_blank">Spoiler Image</a> (' . $size . 'B, ' . $dimensions . ')</div>'; 2136 } 2137 } 2138 2139 $file = <<<HTML 2140 <div class="file" id="f$no"> 2141 $fileinfo 2142 $imgsrc 2143 </div> 2144 HTML; 2145 } 2146 2147 /** 2148 * OP specific html 2149 */ 2150 if ($sorted_replies !== null) { 2151 if ($in_thread) { 2152 $href = ''; 2153 $postinfo_extra = ''; 2154 } 2155 else { 2156 $href = RES_DIR2 . $no . PHP_EXT2; 2157 2158 if ($semantic_url !== '') { 2159 $semantic_url = "/$semantic_url"; 2160 } 2161 2162 $postinfo_extra = ' <span>[<a href="' 2163 . $href . $semantic_url 2164 . '" class="replylink">' . S_REPLY . '</a>]</span>'; 2165 } 2166 2167 $oldtext = ''; 2168 $extra = ''; 2169 2170 // Marked for deletion (old) 2171 if (isset($log[$no]['old'])) { 2172 $oldtext .= '<span class="oldpost">' . S_OLD . '</span><br>'; 2173 } 2174 2175 $postInfo = ''; 2176 2177 // Count omitted replies and images 2178 if (!$in_thread) { 2179 $s = $reply_count - $shown_replies; 2180 2181 if ($shown_replies) { 2182 $t = 0; 2183 $total_t = 0; 2184 2185 $cur = 1; 2186 2187 while ($s >= $cur) { 2188 list($row) = each($sorted_replies); 2189 2190 if ($log[$row]['fsize'] && !$log[$row]['filedeleted']) { 2191 $t++; 2192 } 2193 2194 $cur++; 2195 } 2196 2197 $total_t = $t; 2198 2199 while ($reply_count >= $cur) { 2200 list($row) = each($sorted_replies); 2201 2202 if ($log[$row]['fsize'] && !$log[$row]['filedeleted']) { 2203 $total_t++; 2204 } 2205 2206 $cur++; 2207 } 2208 2209 if ($reply_count != 0) { 2210 reset($sorted_replies); 2211 } 2212 } 2213 else { 2214 $total_t = $t = $imgreplycount; 2215 } 2216 2217 // desktop 2218 $posts = ( $s < 2 ) ? ' reply' : ' replies'; 2219 $replies = ( $t < 2 ) ? ' image' : ' images'; 2220 2221 if (( $s > 0 ) && ( $t == 0 )) { 2222 $extra .= '<span class="summary desktop">' . $s . $posts . ' omitted. <a href="' . $href . '" class="replylink">Click here</a> to view.</span>'; 2223 } 2224 elseif (( $s > 0 ) && ( $t > 0 )) { 2225 $extra .= '<span class="summary desktop">' . $s . $posts . ' and ' . $t . $replies . ' omitted. <a href="' . $href . '" class="replylink">Click here</a> to view.</span>'; 2226 } 2227 2228 // mobile 2229 $posts = ( $reply_count < 2 ) ? ' Reply' : ' Replies'; 2230 $replies = ( $total_t < 2 ) ? ' Image' : ' Images'; 2231 2232 if (( $reply_count > 0 ) && ( $total_t == 0 )) { 2233 // Text replies only 2234 $info = '' . $reply_count . $posts . ''; 2235 } 2236 elseif (( $reply_count > 0 ) && ( $total_t > 0 )) { 2237 // Image replies 2238 $info = '' . $reply_count . $posts . ' / ' . $total_t . $replies . ''; 2239 } 2240 else { 2241 // nothing 2242 $info = ''; 2243 } 2244 2245 $postInfo = <<<HTML 2246 <div class="postLink mobile"> 2247 <span class="info"> 2248 $info 2249 </span> 2250 2251 <a href="$href" class="button">View Thread</a> 2252 </div> 2253 HTML; 2254 } 2255 else { 2256 $s = 0; 2257 } 2258 2259 // Sticky - Closed 2260 $threadmodes = ''; 2261 2262 if ($sticky == 1) { 2263 $threadmodes .= ' <img src="' . STATIC_IMG_DIR2 . 'sticky.gif" alt="Sticky" title="Sticky" class="stickyIcon retina">'; 2264 } 2265 2266 if ($closed == 1) { 2267 if ($archived) { 2268 $threadmodes .= ' <img src="' . STATIC_IMG_DIR2 . 'archived.gif" alt="Archived" title="Archived" class="archivedIcon retina">'; 2269 } 2270 else { 2271 $threadmodes .= ' <img src="' . STATIC_IMG_DIR2 . 'closed.gif" alt="Closed" title="Closed" class="closedIcon retina">'; 2272 } 2273 } 2274 2275 // Staff replies indicator 2276 if (META_BOARD) { 2277 $posts = meta_is_thread_flagged($sorted_replies); 2278 2279 // admin = 0 2280 // dev = 1 2281 // mod = 2 2282 // manager = 3 2283 2284 // array (css_class, text_name) 2285 $larr = array( 2286 0 => array('Admin', 'Administrator'), 2287 1 => array('Developer', 'Developer'), 2288 2 => array('Mod', 'Moderator'), 2289 3 => array('Manager', 'Manager') 2290 ); 2291 2292 if ($posts[0] || $posts[1] || $posts[2] || $posts[3]) { 2293 $com .= '<br><br><span class="capcodeReplies">'; 2294 2295 foreach ($posts as $key => $postlist) { 2296 if (!$postlist) { 2297 continue; 2298 } 2299 2300 $postlist = explode(',', $postlist); 2301 2302 $replies = count($postlist) > 1 ? 'Replies' : 'Reply'; 2303 $com .= '<span class="smaller"><span class="bold">' . $larr[$key][1] . ' ' . $replies . ':</span> '; 2304 2305 foreach ($postlist as $postnum) { 2306 $com .= '<a href="' . $href . '#p' . $postnum . '" class="quotelink">>>' . $postnum . '</a> '; 2307 } 2308 2309 $com .= '</span><br>'; 2310 } 2311 2312 $com .= '</span>'; 2313 2314 } 2315 } 2316 2317 if (DISP_ID && DISP_ID_PER_THREAD && !$is_archived && !DISP_ID_RANDOM && $id !== '') { 2318 $id = generate_uid($no, $time, $host); 2319 } 2320 2321 $reply_file = ''; 2322 $op_file = $file; 2323 $post_class = 'op'; 2324 $sidearrows = ''; 2325 } 2326 /** 2327 * Reply 2328 */ 2329 else { 2330 $href = $in_thread ? '' : RES_DIR2 . $resto . PHP_EXT2; 2331 $threadmodes = $postinfo_extra = $oldtext = $postInfo = $extra = $op_file = ''; 2332 $reply_file = $file; 2333 $post_class = 'reply'; 2334 $sidearrows = '<div class="sideArrows" id="sa' . $no . '">>></div>'; 2335 2336 $sub = ''; 2337 } 2338 2339 $dispuid = ''; 2340 $dispuid = display_uid( $id, $capcode ); 2341 if( $dispuid == '' || $capcode != '' ) { 2342 $dispuid = $capcode; 2343 } 2344 else { 2345 $capcodeStart = ''; 2346 if (FORCED_ANON) { 2347 $capcode_class = ''; 2348 } 2349 } 2350 2351 $countryFlag = ''; 2352 if ($capcode == '') { 2353 if (ENABLE_BOARD_FLAGS && $board_flag != '' && isset($board_flags_array[$board_flag])) { 2354 $cname = board_flag_code_to_name($board_flag); 2355 $countryFlag = ' <span title="' . $cname . '" class="bfl bfl-' . strtolower($board_flag) . '"></span>'; 2356 } 2357 else if (SHOW_COUNTRY_FLAGS) { 2358 $cname = country_code_to_name($country); 2359 $countryFlag = ' <span title="' . $cname . '" class="flag flag-' . strtolower($country) . '"></span>'; 2360 } 2361 } 2362 2363 $quote = $in_thread ? 'javascript:quote(\'' . $no . '\');' : $href . '#q' . $no; 2364 2365 $postM = ''; 2366 2367 // Forced anon on meta boards 2368 if (META_BOARD && $capcode_class != ' capcodeAdmin') { 2369 $name = $mname = S_ANONAME; 2370 } 2371 2372 if (FORCE_COM && !$capcode) { 2373 $com = FORCE_COM_TEXT; 2374 } 2375 2376 $display_no = display_no($no); 2377 2378 if ($since4pass && $capcode == '' && $since4pass < 10000) { 2379 $since4passTag = " <span title=\"Pass user since $since4pass\" class=\"n-pu\"></span>"; 2380 } 2381 else { 2382 $since4passTag = ''; 2383 } 2384 2385 // April 2024 2386 if ($since4pass && $capcode == '' && $since4pass >= 10000) { 2387 $since4passTag = april_2024_get_name_badge($since4pass); 2388 $_xa24_post_cls = april_2024_get_post_cls($since4pass); 2389 } 2390 else { 2391 $_xa24_post_cls = ''; 2392 } 2393 2394 return <<<HTML 2395 <div class="postContainer {$post_class}Container$postM$_xa24_post_cls" id="pc$no">$sidearrows 2396 <div id="p$no" class="post $post_class$highlight"> 2397 <div class="postInfoM mobile" id="pim$no"> 2398 <span class="nameBlock$capcode_class"> 2399 <span$mname_truncated class="name$hasCapcode"$namestyle>$mname</span>$since4passTag$capcodeStart$dispuid$countryFlag$threadmodes<br> 2400 $subshortm 2401 </span> 2402 2403 <span class="dateTime postNum" data-utc="$time">$now <a href="$href#p$no" title="Link to this post">No.</a><a href="$quote" title="Reply to this post">$display_no</a></span> 2404 </div> 2405 2406 $op_file 2407 2408 <div class="postInfo desktop" id="pi$no"> 2409 <input type="checkbox" name="$no" value="delete"> 2410 $sub 2411 <span class="nameBlock$capcode_class"> 2412 <span class="name$hasCapcode"$namestyle>$name</span>$since4passTag$capcodeStart $dispuid$countryFlag 2413 </span> 2414 2415 <span class="dateTime" data-utc="$time">$now</span> 2416 2417 <span class="postNum desktop"> 2418 <a href="$href#p$no" title="Link to this post">No.</a><a href="$quote" title="Reply to this post">$display_no</a>$threadmodes$postinfo_extra 2419 </span> 2420 2421 </div> 2422 $reply_file 2423 <blockquote class="postMessage" id="m$no">$com</blockquote> 2424 </div> 2425 $postInfo 2426 $oldtext 2427 </div> 2428 $extra 2429 HTML; 2430 } 2431 2432 // deletes a post from the database 2433 // imgonly: whether to just delete the file or to delete from the database as well 2434 // automatic: always delete regardless of password/admin (for self-pruning) 2435 // children: whether to delete just the parent post of a thread or also delete the children 2436 // die: whether to die on error 2437 // careful, setting children to 0 could leave orphaned posts. 2438 function delete_post($resno, $pwd, $imgonly = 0, $automatic = 0, $children = 1, $die = 1, $lazy_rebuild = false, $archived_deletion = false, $tool = null, $user_is_known = true) 2439 { 2440 global $log; 2441 2442 $resno = intval( $resno ); 2443 log_cache( 0, $resno, $archived_deletion ? 1 : 0 ); 2444 2445 $post_exists = true; 2446 2447 if (!isset( $log[$resno])) { 2448 if ($die) { 2449 //error( "Can't find the post $resno." ); 2450 updating_index(); 2451 die(); 2452 } 2453 else { 2454 if ($automatic) { 2455 return 0; 2456 } 2457 2458 $post_exists = false; 2459 } 2460 } 2461 2462 $row = $log[$resno]; 2463 2464 if (!$automatic && !has_level('janitor')) { 2465 $cant_del = $cant_del_old = false; 2466 2467 if ($row['resto']) { 2468 $cant_del = NO_DELETE_REPLY; 2469 } 2470 else { 2471 $cant_del = NO_DELETE_OP; 2472 } 2473 2474 if ($_SERVER['REQUEST_TIME'] - $log[$resno]['time'] >= RENZOKU_DEL_CANT_AFTER) { 2475 $cant_del = true; 2476 $cant_del_old = true; 2477 } 2478 2479 if ($cant_del) { 2480 error($cant_del_old ? S_RENZOKU_DEL_CANT_AFTER : S_MAYNOTDEL); 2481 } 2482 } 2483 2484 // if (!$row['pwd'] && BOARD_DIR=='c') { 2485 // echo "<!--"; 2486 // var_dump($log); 2487 // echo "-->"; 2488 // } 2489 2490 if ($archived_deletion) { 2491 // Only authed users can delete archived posts 2492 $pass_ok = false; 2493 $host_ok = false; 2494 } 2495 else { 2496 $pass_ok = $pwd && $pwd === $row['pwd']; 2497 $host_ok = $row['host'] == $_SERVER['REMOTE_ADDR']; 2498 } 2499 2500 $admin_ok = has_level() || can_delete( $resno ); 2501 $can_flood_delete = has_level( 'janitor' ); 2502 2503 $can_delete = $automatic || $pass_ok || $host_ok || $admin_ok; 2504 2505 // quick_log_to( "/www/perhost/del.log", date( "r" ) . " deletion of #$resno by " . $_SERVER['REMOTE_ADDR'] . " pwd \"$pwd\" auto " . (int)$automatic . " adminok " . (int)$admin_ok . " hostok " . (int)$host_ok . " passok " . (int)$pass_ok . " orig ip " . $row['host'] . " pwd " . $row['pwd'] . " com " . substr( $row["com"], 0, 50 ) . " " . ( $can_delete ? "succeeded" : "failed" )."\n" ); 2506 2507 if( !$can_delete ) error( S_BADDELPASS ); 2508 2509 if ($row['sticky']) { 2510 if (has_level() && !has_level('admin') && !$automatic && !$archived_deletion) { 2511 if ($die) { 2512 error(S_MAYNOTDELSTICKY); 2513 } 2514 else { 2515 return false; 2516 } 2517 } 2518 2519 if (!has_level()) { 2520 if ($die) { 2521 error(S_MAYNOTDEL); 2522 } 2523 else { 2524 return false; 2525 } 2526 } 2527 } 2528 2529 // FIXME: remove this and set NO_DELETE_OP on vg 2530 if( BOARD_DIR == 'vg' && !$row['resto'] && !$admin_ok && !$archived_deletion ) error( S_MAYNOTDEL ); 2531 2532 if( !$row['resto'] && !$automatic && !$admin_ok ) { 2533 foreach( $row['children'] as $child => $unused ) { 2534 if( $log[$child]['capcode'] != 'none' ) error( S_MAYNOTDEL ); 2535 } 2536 } 2537 2538 if( !$automatic && !$admin_ok && !$can_flood_delete ) { 2539 if ($user_is_known) { 2540 $_renzoku_del = RENZOKU_DEL; 2541 } 2542 else { 2543 $_renzoku_del = 600; // FIXME: 10 minutes 2544 } 2545 if( ( time() - (int)$row['time'] ) < $_renzoku_del ) { 2546 error(S_RENZOKU_DEL); 2547 } 2548 } 2549 2550 // User is authed staff 2551 if ($admin_ok && !IS_REBUILDD) { 2552 $auser = $_COOKIE['4chan_auser']; 2553 2554 // Use POSTed IP instead of the local one if the deletion was triggered via RPC 2555 if (isset($_POST['remote_addr']) && is_local()) { 2556 $remote_addr = $_POST['remote_addr']; 2557 } 2558 else { 2559 $remote_addr = $_SERVER['REMOTE_ADDR']; 2560 } 2561 2562 // Authed user is deleting a post that isn't his 2563 if (!$pass_ok && !$host_ok) { 2564 $adfsize = ( $row['fsize'] > 0 ) ? 1 : 0; 2565 $adname = str_replace( '</span> <span class="postertrip">!', '#', $row['name'] ); 2566 if( $imgonly ) { 2567 $imgonly = 1; 2568 } else { 2569 $imgonly = 0; 2570 } 2571 2572 if (isset($_POST['template_id'])) { 2573 $template_id = (int)$_POST['template_id']; 2574 } 2575 else { 2576 $template_id = 0; 2577 } 2578 2579 if ($post_exists && (!$automatic || $automatic === 2)) { 2580 validate_admin_cookies(); 2581 2582 if (!$tool || !in_array($tool, array('search', 'ban', 'ban-req', 'autopurge', 'threadban'))) { 2583 $tool = ''; 2584 } 2585 2586 mysql_global_do( "INSERT INTO " . SQLLOGDEL . " (imgonly,postno,resto,board,name,sub,com,img,filename,admin,admin_ip,template_id,tool) values('%s',%d, %d,'%s','%s','%s','%s','%s','%s','%s', '%s', %d, '%s')", $imgonly, $resno, $row['resto'], SQLLOG, $adname, $row["sub"], $row["com"], $adfsize, $row["filename"].$row["ext"], $auser, $remote_addr, $template_id, $tool ); 2587 } 2588 2589 // Clear the report queue if only the file is deleted 2590 if ($imgonly) { 2591 $query = "DELETE FROM reports WHERE board = '" . SQLLOG . "' AND no = " . (int)$resno; 2592 2593 $res = mysql_global_call($query); 2594 2595 $query = "DELETE FROM reports_for_posts WHERE board = '" . SQLLOG . "' AND postid = " . (int)$resno; 2596 2597 $res = mysql_global_call($query); 2598 } 2599 } 2600 // Staff member is deleting a post that is his, or other type of deletions 2601 else if (!$automatic || $automatic === 2) { 2602 if ($auser) { 2603 log_staff_event('staff_self_del', $auser, $remote_addr, $pwd, BOARD_DIR, $row); 2604 } 2605 else { 2606 log_staff_event('staff_auto_del', 'Auto-ban', $remote_addr, $pwd, BOARD_DIR, $row); 2607 } 2608 } 2609 } 2610 2611 $delete_children = $row['resto'] == 0 && $children && !$imgonly; 2612 2613 $restoq = $delete_children ? "OR (archived = 0 and resto=$resno) OR (archived = 1 and resto=$resno)" : ''; 2614 2615 if (UPLOAD_BOARD) { 2616 $up_col = ',filename'; 2617 } 2618 else { 2619 $up_col = ''; 2620 } 2621 2622 if (MOBILE_IMG_RESIZE) { 2623 $up_col .= ',m_img'; 2624 } 2625 2626 $result = mysql_board_call( "select no,resto,tim,ext$up_col from `" . SQLLOG . "` where no=$resno $restoq" ); 2627 2628 // Array of threads to update after one or more replies were deleted. 2629 $updated_threads = array(); 2630 // Array of post number for report and xff clearing 2631 $deleted_threads = array(); 2632 $deleted_replies = array(); 2633 2634 $purge_files = array(); 2635 2636 $img_webroot = 'http://i.4cdn.org/' . BOARD_DIR . '/'; 2637 2638 while( $delrow = mysql_fetch_array( $result ) ) { 2639 // delete 2640 if( $delrow['ext'] ) { 2641 if (UPLOAD_BOARD) { 2642 $delfile = IMG_DIR . $delrow['filename'] . $delrow['ext']; //path to delete 2643 @unlink($delfile); // delete image 2644 if( CLOUDFLARE_PURGE_ON_DEL ) { 2645 cloudflare_purge_url($img_webroot . rawurlencode($delrow['filename']) . $delrow['ext'], true); 2646 } 2647 } 2648 else { 2649 $delfile = IMG_DIR . $delrow['tim'] . $delrow['ext']; //path to delete 2650 $delthumb = THUMB_DIR . $delrow['tim'] . 's.jpg'; 2651 @unlink( $delfile ); // delete image 2652 @unlink( $delthumb ); // delete thumb 2653 2654 if (ENABLE_OEKAKI_REPLAYS && file_exists(IMG_DIR . $delrow['tim'] . '.tgkr')) { 2655 unlink(IMG_DIR . $delrow['tim'] . '.tgkr'); 2656 2657 if (CLOUDFLARE_PURGE_ON_DEL) { 2658 $purge_files[] = $img_webroot . $delrow['tim'] . '.tgkr'; 2659 } 2660 } 2661 2662 if (MOBILE_IMG_RESIZE && $delrow['m_img']) { 2663 @unlink(IMG_DIR . $delrow['tim'] . 'm.jpg'); // delete mobile 2664 } 2665 2666 if (CLOUDFLARE_PURGE_ON_DEL) { 2667 $purge_files[] = $img_webroot . $delrow['tim'] . $delrow['ext']; 2668 $purge_files[] = $img_webroot . $delrow['tim'] . 's.jpg'; 2669 2670 if (MOBILE_IMG_RESIZE && $delrow['m_img']) { 2671 $purge_files[] = $img_webroot . $delrow['tim'] . 'm.jpg'; 2672 } 2673 2674 } 2675 } 2676 } 2677 if( $imgonly ) { 2678 mysql_board_call( "UPDATE `" . SQLLOG . "` SET filedeleted=1,root=root,last_modified=%d WHERE no=%d", $_SERVER['REQUEST_TIME'], $delrow['no'] ); 2679 $log[$delrow['no']]['filedeleted'] = TRUE; 2680 2681 if ($delrow['resto']) { 2682 mysql_board_call( "UPDATE `" . SQLLOG . "` SET root=root,last_modified=%d WHERE no=%d", $_SERVER['REQUEST_TIME'], $delrow['resto'] ); 2683 if (isset($log[$delrow['resto']])) 2684 $log[$delrow['resto']]['last_modified'] = (int)$_SERVER['REQUEST_TIME']; 2685 } 2686 2687 //cloudflare_purge_by_basename(BOARD_DIR, $delrow['tim'] . $delrow['ext']); 2688 } 2689 else { 2690 // Thread 2691 if (!$delrow['resto']) { 2692 $thread_key = @array_search( $delrow['no'], $log['THREADS'] ); 2693 if( $thread_key !== false ) { 2694 unset( $log['THREADS'][$thread_key] ); 2695 } 2696 2697 if( USE_GZIP == 1 ) { 2698 @unlink( RES_DIR . $delrow['no'] . PHP_EXT . '.gz' ); 2699 @unlink( RES_DIR . $delrow['no'] . '.json.gz' ); 2700 } 2701 else { 2702 @unlink( RES_DIR . $delrow['no'] . PHP_EXT ); 2703 @unlink( RES_DIR . $delrow['no'] . '.json' ); 2704 } 2705 2706 update_json_tail_deletion($delrow['no'], true); 2707 2708 $deleted_threads[] = (int)$delrow['no']; 2709 } 2710 // Reply. Thread's last_modified field will need to be updated 2711 else if (!isset($updated_threads[$delrow['resto']])) { 2712 $updated_threads[$delrow['resto']] = true; 2713 2714 $deleted_replies[] = (int)$delrow['no']; 2715 } 2716 2717 unset( $log[$delrow['no']] ); 2718 } 2719 } 2720 2721 if (!empty($purge_files)) { 2722 cloudflare_purge_url($purge_files, true); 2723 } 2724 2725 // Updating last_modified field (threads) 2726 foreach ($updated_threads as $thread_id => $true) { 2727 mysql_board_call("UPDATE `".SQLLOG."` set root=root,last_modified=%d where no=%d", $_SERVER['REQUEST_TIME'], $thread_id); 2728 2729 if (isset($log[$thread_id])) 2730 $log[$thread_id]['last_modified'] = (int)$_SERVER['REQUEST_TIME']; 2731 2732 unset( $log[$thread_id]['children'][$delrow['no']] ); 2733 } 2734 2735 // Clearing reports and xff 2736 if ($deleted_replies) { 2737 $in_clause = 'IN(' . implode(',', $deleted_replies) . ')'; 2738 mysql_global_do("DELETE FROM reports WHERE board='" . BOARD_DIR . "' AND no " . $in_clause); 2739 mysql_global_do("DELETE FROM reports_for_posts WHERE board='" . BOARD_DIR . "' AND postid " . $in_clause); 2740 2741 if (SAVE_XFF) { 2742 mysql_global_do("UPDATE xff SET is_live = 0 WHERE board='" . BOARD_DIR . "' AND postno " . $in_clause); 2743 } 2744 } 2745 2746 if ($deleted_threads) { 2747 $in_clause = 'IN(' . implode(',', $deleted_threads) . ')'; 2748 mysql_global_do("DELETE FROM reports WHERE board='" . BOARD_DIR . "' AND (no $in_clause OR resto $in_clause)"); 2749 mysql_global_do("DELETE FROM reports_for_posts WHERE board='" . BOARD_DIR . "' AND (postid $in_clause OR threadid $in_clause)"); 2750 2751 if (SAVE_XFF) { 2752 mysql_global_do("UPDATE xff SET is_live = 0 WHERE board='" . BOARD_DIR . "' AND postno " . $in_clause); 2753 } 2754 } 2755 2756 // Halloween 2017 2757 /* 2758 if ($tool && defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky2017') { 2759 if ($tool === 'ban') { 2760 decrease_halloween_score($resno); 2761 } 2762 else if ($tool === 'ban-req') { 2763 decrease_halloween_score($resno, 0.90); 2764 } 2765 } 2766 */ 2767 2768 //delete from DB 2769 if( $delete_children ) // delete thread and children 2770 $result = mysql_board_call( "delete from `" . SQLLOG . "` where no=$resno or resto=$resno" ); 2771 elseif( !$imgonly ) // just delete the post 2772 $result = mysql_board_call( "delete from `" . SQLLOG . "` where no=$resno" ); 2773 2774 rpc_task(); 2775 if( $imgonly && $row['resto'] == 0 ) { 2776 return $resno; // return thread number to stop deletion silliness 2777 } 2778 2779 return $row['resto']; // so the caller can know what pages need to be rebuilt 2780 } 2781 2782 function rebuild_deletions($rebuild, $lazy_rebuild = false) 2783 { 2784 global $log; 2785 2786 foreach( $rebuild as $key => $val ) { 2787 log_cache( 0, $key ); 2788 if (!isset($log[$key]['children'])) { 2789 internal_error_log("rebuild_deletions", "missing children for OP /" . BOARD_DIR . "/$key"); 2790 continue; 2791 } 2792 updatelog( $key, 1 ); // leaving the second parameter as 0 rebuilds the index each time! 2793 update_json_tail_deletion($key); 2794 } 2795 2796 if( STATIC_REBUILD ) return; 2797 2798 updatelog(0, 0, $lazy_rebuild); // update the index page last 2799 } 2800 2801 /** 2802 * Removes old archived posts 2803 */ 2804 function trim_archive() { 2805 if (STATIC_REBUILD && !IS_REBUILDD) { 2806 return; 2807 } 2808 2809 if (!ARCHIVE_MAX_AGE) { 2810 return; 2811 } 2812 2813 $interval = (int)ARCHIVE_MAX_AGE; 2814 2815 $query = <<<SQL 2816 SELECT no FROM `%s` 2817 WHERE archived = 1 2818 AND resto = 0 2819 AND root < DATE_SUB(NOW(), INTERVAL $interval HOUR) 2820 SQL; 2821 2822 $res = mysql_board_call($query, BOARD_DIR); 2823 2824 if (!$res || !mysql_num_rows($res)) { 2825 return; 2826 } 2827 2828 while ($row = mysql_fetch_row($res)) { 2829 delete_post((int)$row[0], '', 0, 1, 1, 0, false, true); 2830 } 2831 } 2832 2833 // purge old posts 2834 // should be called whenever a new post is added. 2835 function trim_db() 2836 { 2837 global $mode; 2838 if( JANITOR_BOARD == 1 ) return; 2839 if( STATIC_REBUILD && !IS_REBUILDD ) return; 2840 2841 if (!IS_REBUILDD) { 2842 log_cache(); 2843 } 2844 2845 $maxposts = LOG_MAX; 2846 // max threads = max pages times threads-per-page 2847 $maxthreads = ( PAGE_MAX > 0 ) ? ( PAGE_MAX * DEF_PAGES ) : 0; 2848 2849 $threads = array(); 2850 2851 $rebuild_archive_list = false; 2852 2853 $rebuild_archive_json = false; 2854 2855 if (ENABLE_ARCHIVE) { 2856 if (IS_REBUILDD) { 2857 clearstatcache(true, INDEX_DIR . 'archive' . PHP_EXT . '.gz'); 2858 } 2859 2860 if (filemtime(INDEX_DIR . 'archive' . PHP_EXT . '.gz') < time() - (int)ARCHIVE_REBUILD_DELAY) { 2861 $rebuild_archive_list = true; 2862 } 2863 } 2864 2865 // New max-page method 2866 if( $maxthreads ) { 2867 $exp_order = 'no'; 2868 if( EXPIRE_NEGLECTED == 1 ) $exp_order = 'root'; 2869 //logtime( 'trim_db before select threads' ); 2870 $result = mysql_board_call( "SELECT no FROM `" . SQLLOG . "` WHERE archived=0 AND sticky=0 AND undead=0 AND resto=0 ORDER BY $exp_order ASC" ); 2871 //logtime( 'trim_db after select threads' ); 2872 $threadcount = mysql_num_rows( $result ); 2873 2874 if (!$threadcount && $rebuild_archive_list) { 2875 $rebuild_archive_list = false; 2876 } 2877 2878 while( $row = mysql_fetch_array( $result ) and $threadcount > $maxthreads ) { 2879 if (ENABLE_ARCHIVE) { 2880 $rebuild_archive_json = true; 2881 archive_thread($row['no']); 2882 } 2883 else { 2884 delete_post( $row['no'], 'trim', 0, 1 ); // imgonly=0, automatic=1, children=1 2885 } 2886 $threads[$row['no']] = 1; 2887 $threadcount--; 2888 } 2889 2890 mysql_free_result( $result ); 2891 2892 if (ENABLE_ARCHIVE) { 2893 if ($rebuild_archive_list) { 2894 rebuild_archive_list(); 2895 } 2896 2897 if ($rebuild_archive_json && ENABLE_JSON_THREADS) { 2898 generate_board_archived_json(); 2899 } 2900 } 2901 2902 // Original max-posts method (note: cleans orphaned posts later than parent posts) 2903 } else { 2904 // make list of stickies 2905 $stickies = array(); // keys are stickied thread numbers 2906 $undead = array(); 2907 // COMBINE FOR MAXIMUM EFFICIENCY! 2908 $result = mysql_board_call( "SELECT no from `" . SQLLOG . "` where (sticky=1 OR undead=1) and resto=0" ); 2909 while( $row = mysql_fetch_array( $result ) ) { 2910 if( $row['sticky'] ) $stickies[$row['no']] = 1; 2911 if( $row['undead'] ) $undead[$row['no']] = 1; 2912 } 2913 2914 // FIXME these if ... continue checks need to be SQL conditions! 2915 $result = mysql_board_call( "SELECT no,resto,sticky FROM `" . SQLLOG . "` ORDER BY no ASC" ); 2916 $postcount = mysql_num_rows( $result ); 2917 while( $row = mysql_fetch_array( $result ) and $postcount >= $maxposts ) { 2918 // don't delete if this is a sticky thread or is undeletable 2919 if( $row['sticky'] == 1 || $row['undead'] == 1 ) continue; 2920 // don't delete if this is a REPLY to a sticky or is in an undeletable thread 2921 if( $row['resto'] != 0 && ( $stickies[$row['resto']] == 1 || $undead[$row['resto']] == 1 ) ) continue; 2922 delete_post( $row['no'], 'trim', 0, 1, 0 ); // imgonly=0, automatic=1, children=0 2923 $threads[$row['no']] = 1; 2924 $postcount--; 2925 } 2926 mysql_free_result( $result ); 2927 } 2928 } 2929 2930 // FIXME archives 2931 // debug function, deletes all archived threads 2932 function purge_archive() { 2933 $query = "SELECT no FROM `test` WHERE archived = 1 AND resto = 0"; 2934 2935 $res = mysql_board_call($query); 2936 2937 if (!$res) { 2938 return; 2939 } 2940 2941 while ($thread = mysql_fetch_assoc($res)) { 2942 echo "Deleting {$thread['no']}<br>"; 2943 delete_post((int)$thread['no'], '', 0, 0, 1, true, false, true); 2944 } 2945 } 2946 2947 function rebuild_archived_thread($thread_id) { 2948 global $log; 2949 2950 log_cache(0, $thread_id, 1); 2951 2952 if (!isset($log[$thread_id])) { 2953 return false; 2954 } 2955 2956 // Build the JSON 2957 if (ENABLE_JSON) { 2958 $tailSize = get_json_tail_size($thread_id); 2959 2960 if ($tailSize) { 2961 generate_thread_json($thread_id, false, false, false, $tailSize); 2962 } 2963 else { 2964 update_json_tail_deletion($thread_id); 2965 } 2966 2967 generate_thread_json($thread_id); 2968 } 2969 2970 // Build the HTML 2971 $dat = ''; 2972 2973 head($dat, $thread_id); 2974 form($dat, $thread_id); 2975 2976 $dat .= '<hr> 2977 <form name="delform" id="delform" action="' . SELF_PATH_ABS . '" method="post"> 2978 <div class="board"> 2979 '; 2980 2981 $reply_count = $log[$thread_id]['replycount']; 2982 2983 // Open thread tag and render OP 2984 $sorted_replies = $log[$thread_id]['children']; 2985 ksort($sorted_replies); 2986 2987 $dat .= '<div class="thread" id="t' . $thread_id . '">' 2988 . renderPostHtml($thread_id, $thread_id, $sorted_replies, $reply_count, null, true); 2989 2990 // Render replies 2991 $repCount = 0; 2992 2993 while (list($resrow) = each($sorted_replies)) { 2994 if (!$log[$resrow]['no']) { 2995 break; 2996 } 2997 2998 $dat .= renderPostHtml($resrow, $thread_id, null, null, null, true); 2999 3000 $repCount++; 3001 } 3002 3003 // Close thread tag 3004 $dat .= ' 3005 </div> 3006 <hr> 3007 '; 3008 3009 $dat .= '<div class="navLinks navLinksBot desktop">[<a href="/' 3010 . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' 3011 . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#top">' 3012 . S_TOP . '</a>] </div><hr class="desktop">'; 3013 3014 // Close board tag 3015 $lang = S_FORM_REPLY; 3016 3017 $dat .= ' 3018 <div class="mobile center"><a class="mobilePostFormToggle button" href="#">' 3019 . $lang . '</a></div> 3020 </div>'; 3021 3022 $dat .= '<div class="navLinks mobile"><span class="mobileib button"><a href="/' 3023 . BOARD_DIR . '/" accesskey="a">' 3024 . S_RETURN . '</a></span> <span class="mobileib button"><a href="/' 3025 . BOARD_DIR . '/catalog">' 3026 . S_CATALOG . '</a></span> <span class="mobileib button"><a href="#top">' 3027 . S_TOP . '</a></span> <span class="mobileib button"><a href="#bottom_r" id="refresh_bottom">' 3028 . S_REFRESH . '</a></span></div><hr class="mobile">'; 3029 3030 /** 3031 * ADS 3032 */ 3033 3034 if (defined('AD_ADGLARE_BOTTOM') && AD_ADGLARE_BOTTOM) { 3035 $dat .= '<div class="adg-rects desktop"><div class="adg adp-90" id=zone' . AD_ADGLARE_BOTTOM . '></div><hr></div>'; 3036 } 3037 3038 if (defined('AD_ADGLARE_BOTTOM_MOBILE') && AD_ADGLARE_BOTTOM_MOBILE) { 3039 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" id=zone' . AD_ADGLARE_BOTTOM_MOBILE . '></div><hr></div>'; 3040 } 3041 3042 if (defined('AD_RC_BOTTOM') && AD_RC_BOTTOM) { 3043 $dat .= '<div class="adg-rects desktop"><div class="adg adp-228" data-rc="' . AD_RC_BOTTOM . '" id="rcjsload_bottom"></div><hr></div>'; 3044 } 3045 3046 if (defined('AD_BSA_BOTTOM') && AD_BSA_BOTTOM) { 3047 $dat .= AD_BSA_BOTTOM; 3048 } 3049 3050 if (defined('AD_RC_BOTTOM_MOBILE') && AD_RC_BOTTOM_MOBILE) { 3051 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-50" data-rc="' . AD_RC_BOTTOM_MOBILE . '" id="rcjsload_bottom_m"></div><hr></div>'; 3052 } 3053 3054 if (defined('AD_ADNIUM_BOTTOM_MOBILE') && AD_ADNIUM_BOTTOM_MOBILE) { 3055 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" id="adn-' . AD_ADNIUM_BOTTOM_MOBILE . '" data-adn></div><hr></div>'; 3056 } 3057 3058 if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 3059 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="' . AD_ABC_BOTTOM_MOBILE . '"></div><hr></div>'; 3060 } 3061 /* 3062 if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 3063 $dat .= '<div class="adc-resp-bg" data-ad-bg="' . AD_BIDGEAR_BOTTOM . '"></div>'; 3064 } 3065 else if (defined('AD_TERRA_BOTTOM_DESKTOP') && AD_TERRA_BOTTOM_DESKTOP) { 3066 $_ad_info = explode(',', AD_TERRA_BOTTOM_DESKTOP); 3067 $dat .= '<div class="adt-800" data-d="' . $_ad_info[0] . '" id="container-' . $_ad_info[1] . '" style="max-width:800px;margin:auto;"></div>'; 3068 } 3069 */ 3070 if (defined('ADS_DANBO') && ADS_DANBO) { 3071 $dat .= '<div id="danbo-s-b" class="danbo-slot"></div><div class="adl">[<a target="_blank" href="https://www.4chan.org/advertise">Advertise on 4chan</a>]</div><hr>'; 3072 } 3073 if (defined('AD_CUSTOM_BOTTOM') && AD_CUSTOM_BOTTOM) { 3074 $dat .= '<div>' . AD_CUSTOM_BOTTOM . '<hr></div>'; 3075 } 3076 3077 $resredir = '<input type="hidden" name="res" value="' . $thread_id . '">'; 3078 3079 // deletion mode is "arcdel" instead of "usrdel" 3080 $dat .= '<div class="bottomCtrl desktop"><span class="deleteform"><input type="hidden" name="mode" value="arcdel">' 3081 . S_REPDEL . $resredir . ' [<input type="checkbox" name="onlyimgdel" value="on">' 3082 . S_DELPICONLY . ']<input type="hidden" id="delPassword" name="pwd"> <input type="submit" value="' 3083 . S_DELETE . '"><input id="bottomReportBtn" type="button" value="Report"></span>'; 3084 3085 if (!defined('CSS_FORCE')) { 3086 $dat .= '<span class="stylechanger">Style: 3087 <select id="styleSelector"> 3088 <option value="Yotsuba New">Yotsuba</option> 3089 <option value="Yotsuba B New">Yotsuba B</option> 3090 <option value="Futaba New">Futaba</option> 3091 <option value="Burichan New">Burichan</option> 3092 <option value="Tomorrow">Tomorrow</option> 3093 <option value="Photon">Photon</option>'; 3094 3095 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3096 $dat .= '<option value="_special">Special</option>'; 3097 } 3098 3099 $dat .= '</select> 3100 </span>'; 3101 } 3102 3103 $dat .= '</div></form>'; 3104 3105 foot($dat); 3106 3107 // Write the page 3108 print_page(RES_DIR . $thread_id . PHP_EXT, $dat); 3109 } 3110 3111 function calculate_indexes_to_rebuild( $updated_thread ) 3112 { 3113 global $index_rbl; 3114 $query = mysql_board_call( "SELECT COUNT(no) FROM `%s` WHERE archived = 0 AND root > (SELECT root FROM `%s` WHERE no=%d)", SQLLOG, SQLLOG, $updated_thread ); 3115 $index_rbl = floor( mysql_result( $query, 0, 0 ) / DEF_PAGES ); 3116 } 3117 3118 function rebuild_indexes_daemon() 3119 { 3120 global $index_rbl, $index_last_thread, $index_last_post, $log; 3121 static $index_arr = array(); 3122 3123 $index_rbl = PAGE_MAX; 3124 3125 // Get latest thread 3126 $query = mysql_board_call( "SELECT max(no) last_post, max(resto) last_thread FROM `%s` WHERE archived = 0", SQLLOG ); 3127 $q = mysql_fetch_assoc( $query ); 3128 3129 $latest_thread = $q['last_thread']; 3130 $latest_post = $q['last_post']; 3131 3132 if( $index_last_thread != $latest_thread ) { 3133 // cry :( 3134 $index_last_thread = $latest_thread; 3135 $index_last_post = $latest_post; 3136 3137 updatelog(); 3138 3139 if (ENABLE_JSON_THREADS && ENABLE_ARCHIVE) { 3140 generate_board_archived_json(); 3141 } 3142 3143 return; 3144 } 3145 3146 if( $index_last_post != $latest_post ) { 3147 $index_last_thread = $latest_thread; 3148 $index_last_post = $latest_post; 3149 3150 $post_arr = $log['THREADS']; 3151 3152 // Now we know we're not going to have identical arrays. 3153 $count = count( $post_arr ); 3154 $last_seen_thread = 0; 3155 3156 for( $i = 0; $i < $count; $i++ ) { 3157 if( $post_arr[$i] != $index_arr[$i] ) $last_seen_thread = $i; 3158 } 3159 3160 $index_rbl = floor( $last_seen_thread / DEF_PAGES ); 3161 $index_arr = $post_arr; 3162 3163 updatelog(); 3164 3165 return; 3166 } 3167 3168 // YAY NOTHING TO UPDATE! 3169 return; 3170 } 3171 3172 function style_group() 3173 { 3174 return ( DEFAULT_BURICHAN == 1 ) ? "ws_style" : "nws_style"; 3175 } 3176 3177 function rebuildd_stats() 3178 { 3179 if (!IS_REBUILDD) return ""; 3180 3181 global $update_avg_secs; 3182 global $rpc_chs; 3183 3184 $avgtime = $update_avg_secs; 3185 3186 $memuse = (int)(memory_get_usage(true) / 1024); 3187 $peakmemuse = (int)(memory_get_peak_usage(true) / 1024); 3188 $rpccount = count($rpc_chs); 3189 3190 return "<!-- t $avgtime m $memuse $peakmemuse rc $rpccount -->"; 3191 } 3192 3193 // Changes relative board urls to absolute //board.4chan.org urls 3194 // mostly for /j/ and error pages on sys.4chan 3195 function fix_board_nav($nav, $fix_protocol = false) { 3196 if ($fix_protocol) { 3197 $protocol = (stripos($_SERVER["HTTP_REFERER"], "https") === 0) ? 'https:' : 'http:'; 3198 } 3199 else { 3200 $protocol = ''; 3201 } 3202 3203 return preg_replace('/href="\/([a-z0-9]+)\/"/', "href=\"$protocol//boards." . L::d(BOARD_DIR) . "/$1/\"", $nav); 3204 } 3205 3206 // Same but for /archive lmao 3207 function fix_board_nav_archive($nav) { 3208 $nav = preg_replace('/href="\/([a-z0-9]+)\/"/', 'href="/$1/archive"', $nav); 3209 $nav = preg_replace('/href="\/f\/archive"/', 'href="/f/"', $nav); 3210 $nav = preg_replace('/href="\/b\/archive"/', 'href="/b/"', $nav); 3211 3212 return $nav; 3213 } 3214 3215 function head( &$dat, $res, $error = 0, $page = 0, $npages = 0, $is_arclist = false ) 3216 { 3217 //( $dat, 0, 0, 0, 0, true ) 3218 global $log, $thread_unique_ips; 3219 3220 $titlepart = $rta = $favicon = $css = $rss = $subtitle = $extra = ''; 3221 3222 $includenav = file_get_contents_cached(NAV_TXT); 3223 3224 if( JANITOR_BOARD == 1 ) { 3225 $dat .= broomcloset_head( $dat ); 3226 $includenav = fix_board_nav($includenav); 3227 } 3228 else if ($error) { 3229 $includenav = fix_board_nav($includenav, true); 3230 } 3231 else if ($is_arclist) { 3232 $includenav = fix_board_nav_archive($includenav); 3233 } 3234 3235 if( TITLE_IMAGE_TYPE == 1 ) { 3236 $titleimg = rand_from_flatfile( YOTSUBA_DIR, 'title_banners.txt' ); 3237 //$titleimg = STATIC_SERVER . 'image/title/' . $titleimg; 3238 3239 $titlepart .= '<div id="bannerCnt" class="title desktop" data-src="' . $titleimg . '"></div>'; 3240 } elseif( TITLE_IMAGE_TYPE == 2 ) { 3241 $titlepart .= '<img class="title" src="' . TITLEIMG . '" onclick="this.src = this.src;">'; 3242 } 3243 3244 if( defined( 'SUBTITLE' ) ) { 3245 $subtitle = '<div class="boardSubtitle">' . SUBTITLE . '</div>'; 3246 } 3247 3248 // CSS Workings 3249 $cssVersion = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION; 3250 $defaultcss = ( DEFAULT_BURICHAN == 1 ) ? 'yotsubluenew' : 'yotsubanew'; 3251 $mobilecss = ( ( DEFAULT_BURICHAN == 1 ) ? 'yotsublue' : 'yotsuba' ) . 'mobile.' . $cssVersion . '.css'; 3252 3253 $styles = array( 3254 'Yotsuba New' => "yotsubanew.$cssVersion.css", 3255 //'Yotsuba' => "yotsuba.$cssVersion.css", 3256 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 3257 'Futaba New' => "futabanew.$cssVersion.css", 3258 'Burichan New' => "burichannew.$cssVersion.css", 3259 'Photon' => "photon.$cssVersion.css", 3260 'Tomorrow' => "tomorrow.$cssVersion.css" 3261 ); 3262 3263 // /j/ versioning fix 3264 if( BOARD_DIR == 'j' ) { 3265 $css = '<link rel="stylesheet" type="text/css" href="' . STATIC_SERVER . 'css/janichan.' . $cssVersion . '.css" title="Yotsuba New">'; 3266 $extra = <<<JJS 3267 <script type="text/javascript"> 3268 document.addEventListener('mousedown', function(e) { 3269 var t = e.target; 3270 if (t === document) { 3271 return; 3272 } 3273 if (/^>>>\//.test(t.textContent) && /sys\.4chan/.test(t.href)) { 3274 t.href = t.href.replace('sys.4chan', 'boards.4chan'); 3275 } 3276 }, false); 3277 </script> 3278 JJS; 3279 } else { 3280 if( defined( 'CSS_FORCE' ) ) { 3281 foreach( $styles as $style => $stylecss ) { 3282 $rel = ( $style == 'Yotsuba New' ) ? 'stylesheet' : 'alternate stylesheet'; 3283 $css .= '<link rel="' . $rel . '" type="text/css" href="' . CSS_FORCE . '" title="' . $style . '">'; 3284 } 3285 } 3286 else { 3287 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 3288 $css .= '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 3289 foreach( $styles as $style => $stylecss ) { 3290 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' . $stylecss . '" title="' . $style . '">'; 3291 } 3292 3293 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3294 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' 3295 . CSS_EVENT_NAME . '.' . $cssVersion . '.css" title="_special">'; 3296 } 3297 } 3298 3299 // Christmas 2021 3300 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'tomorrow') { 3301 $extra = <<<JJS 3302 <script src='//s.4cdn.org/js/snow.js'></script> 3303 <script> 3304 function fc_tomorrow_init() { 3305 if (window.matchMedia && window.matchMedia('(min-width: 481px)').matches) { 3306 fc_spawn_snow(Math.floor(Math.random() * 50) + 50); 3307 } 3308 } 3309 function fc_tomorrow_cleanup() { 3310 fc_remove_snow(); 3311 } 3312 </script> 3313 <style> 3314 .boardBanner, #delform, .navLinksBot.desktop { 3315 border-image-slice: 50 0 50 0; 3316 border-image-width: 40px 0px 0px 0px; 3317 border-image-outset: 0px 0px 0px 0px; 3318 border-image-repeat: repeat repeat; 3319 border-image-source: url('https://s.4cdn.org/image/temp/garland.png'); 3320 border-style: solid; 3321 padding-top: 50px; 3322 } 3323 </style> 3324 JJS; 3325 } 3326 3327 // Halloween spooky.css 3328 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky') { 3329 $extra = <<<JJS 3330 <script> 3331 function fc_skelrot(e) { 3332 var el, idx, thres, max; 3333 if (e && e.detail && e.detail.count) { 3334 thres = 0.33; 3335 } 3336 else { 3337 thres = 0.0; 3338 } 3339 max = 23; 3340 if (Math.random() < thres) { 3341 return; 3342 } 3343 if (el = document.getElementById('skellington')) { 3344 el.parentNode.removeChild(el); 3345 } 3346 idx = 1 + Math.floor(Math.random() * max); 3347 el = document.createElement('img'); 3348 el.id = 'skellington'; 3349 el.className = 'desktop' + (Math.random() < 0.25 ? ' topskel' : ''); 3350 el.alt = ''; 3351 if (Math.random() < 0.01) { 3352 el.src = '//s.4cdn.org/image/temp/dinosaur.gif'; 3353 } 3354 else { 3355 el.src = '//s.4cdn.org/image/skeletons/' + idx + '.gif'; 3356 } 3357 document.body.insertBefore(el, document.body.firstElementChild); 3358 } 3359 function fc_spooky_init() { 3360 if (window.matchMedia && window.matchMedia('(min-width: 481px)').matches) { 3361 document.addEventListener('4chanThreadUpdated', fc_skelrot, false); 3362 window.dark_captcha = true; 3363 fc_skelrot(); 3364 } 3365 } 3366 function fc_spooky_cleanup() { 3367 var el = document.getElementById('skellington'); 3368 window.dark_captcha = false; 3369 document.removeEventListener('4chanThreadUpdated', fc_skelrot, false); 3370 el && el.parentNode.removeChild(el); 3371 } 3372 </script> 3373 JJS; 3374 } 3375 } 3376 3377 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/' . $mobilecss . '">'; 3378 3379 // April 2024 3380 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/xa24extra.css">'; 3381 3382 if (SHOW_COUNTRY_FLAGS) { 3383 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/flags.' . CSS_VERSION_FLAGS . '.css">'; 3384 } 3385 3386 if (ENABLE_BOARD_FLAGS) { 3387 $_flags_type = (defined('BOARD_FLAGS_TYPE') && BOARD_FLAGS_TYPE) ? BOARD_FLAGS_TYPE : BOARD_DIR; 3388 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'image/flags/' . $_flags_type . '/flags.' . CSS_VERSION_BOARD_FLAGS . '.css">'; 3389 } 3390 3391 if( CODE_TAGS ) { 3392 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'js/prettify/prettify.' . CSS_VERSION . '.css">'; 3393 } 3394 3395 // Various optional tags 3396 if( USE_RSS == 1 ) { 3397 $rss = '<link rel="alternate" title="RSS feed" href="/' . BOARD_DIR . '/index.rss" type="application/rss+xml">'; 3398 } 3399 3400 if( RTA == 1 ) { 3401 $rta = '<meta name="rating" content="adult">'; 3402 } 3403 3404 if( defined( 'FAVICON' ) ) { 3405 $favicon = '<link rel="shortcut icon" href="' . FAVICON . '">'; 3406 } 3407 3408 $thread_unique_ips = 0; 3409 $jsUniqueIps = ''; 3410 3411 if (SHOW_THREAD_UNIQUES) { 3412 if ($res) { 3413 $thread_unique_ips = get_unique_ip_count($res); 3414 } 3415 3416 if ($thread_unique_ips) { 3417 $jsUniqueIps = 'var unique_ips = ' . $thread_unique_ips . ';'; 3418 } 3419 } 3420 3421 // js tags 3422 $jsVersion = TEST_BOARD ? JS_VERSION_TEST : JS_VERSION; 3423 $comLen = MAX_COM_CHARS; 3424 $styleGroup = style_group(); 3425 $maxFilesize = MAX_KB * 1024; 3426 $maxLines = MAX_LINES; 3427 $jsCooldowns = json_encode(array( 3428 'thread' => RENZOKU3, 3429 'reply' => RENZOKU, 3430 'image' => RENZOKU2 3431 )); 3432 3433 $tailSizeJs = ''; 3434 3435 if ($res) { 3436 $tailSize = get_json_tail_size($res); 3437 3438 if ($tailSize) { 3439 $tailSizeJs = ",tailSize = $tailSize"; 3440 } 3441 } 3442 3443 $title = TITLE; 3444 3445 $scriptjs = <<<JS 3446 <script type="text/javascript"> 3447 var style_group = "$styleGroup", 3448 cssVersion = $cssVersion, 3449 jsVersion = $jsVersion, 3450 comlen = $comLen, 3451 maxFilesize = $maxFilesize, 3452 maxLines = $maxLines, 3453 clickable_ids = 1, 3454 cooldowns = $jsCooldowns 3455 $tailSizeJs; 3456 $jsUniqueIps 3457 JS; 3458 3459 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3460 $scriptjs .= 'var css_event = "' . CSS_EVENT_NAME . '";'; 3461 3462 if (defined('CSS_EVENT_VERSION')) { 3463 $_css_event_version = (int)CSS_EVENT_VERSION; 3464 } 3465 else { 3466 $_css_event_version = 1; 3467 } 3468 3469 $scriptjs .= 'var css_event_v = ' . $_css_event_version . ';'; 3470 } 3471 3472 if ((int)MAX_WEBM_FILESIZE < (int)MAX_KB) { 3473 $scriptjs .= 'var maxWebmFilesize = ' . (MAX_WEBM_FILESIZE * 1024) . ';'; 3474 } 3475 3476 $_is_archived = false; 3477 3478 if (ENABLE_ARCHIVE) { 3479 $scriptjs .= 'var board_archived = true;'; 3480 3481 if ($res && $log[$res]['archived']) { 3482 $scriptjs .= 'var thread_archived = true;'; 3483 $_is_archived = true; 3484 } 3485 } 3486 3487 if (DISP_ID) { 3488 $scriptjs .= 'var user_ids = true;'; 3489 } 3490 3491 if (JSMATH) { 3492 $scriptjs .= 'var math_tags = true;'; 3493 } 3494 3495 if (SJIS_TAGS) { 3496 $scriptjs .= 'var sjis_tags = true;'; 3497 } 3498 3499 if (SPOILERS) { 3500 $scriptjs .= 'var spoilers = true;'; 3501 } 3502 3503 if (CAPTCHA_TWISTER) { 3504 $scriptjs .= 'var t_captcha = true;'; 3505 } 3506 3507 if( $res && $log[$res]['bumplimit'] ) { 3508 $scriptjs .= 'var bumplimit = 1;'; 3509 } 3510 3511 if( $res && $log[$res]['imagelimit'] ) { 3512 $scriptjs .= 'var imagelimit = 1;'; 3513 } 3514 3515 if( AD_PLEA ) $scriptjs .= 'var check_for_block = ' . (int)AD_PLEA . ';'; 3516 3517 if( $error ) $scriptjs .= 'is_error = "true";'; 3518 3519 // Danbo ads 3520 if (defined('ADS_DANBO') && ADS_DANBO) { 3521 if (DEFAULT_BURICHAN) { 3522 $scriptjs .= "var danbo_rating = '__SFW__';"; 3523 } 3524 else { 3525 $scriptjs .= "var danbo_rating = '__NSFW__';"; 3526 } 3527 3528 // Set up fallbacks 3529 $_danbo_fallbacks = []; 3530 3531 if (defined('AD_BIDGEAR_TOP') && AD_BIDGEAR_TOP) { 3532 $_danbo_fallbacks['t_bg'] = AD_BIDGEAR_TOP; 3533 } 3534 else { 3535 if (defined('AD_ABC_TOP_DESKTOP') && AD_ABC_TOP_DESKTOP) { 3536 $_danbo_fallbacks['t_abc_d'] = AD_ABC_TOP_DESKTOP; 3537 } 3538 3539 if (defined('AD_ABC_TOP_MOBILE') && AD_ABC_TOP_MOBILE) { 3540 $_danbo_fallbacks['t_abc_m'] = AD_ABC_TOP_MOBILE; 3541 } 3542 } 3543 3544 if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 3545 $_danbo_fallbacks['b_bg'] = AD_BIDGEAR_BOTTOM; 3546 } 3547 else if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 3548 $_danbo_fallbacks['b_abc_m'] = AD_ABC_BOTTOM_MOBILE; 3549 } 3550 3551 if (!$_danbo_fallbacks) { 3552 $_danbo_fallbacks = 'null'; 3553 } 3554 else { 3555 $_danbo_fallbacks = json_encode($_danbo_fallbacks); 3556 } 3557 3558 $scriptjs .= 'var danbo_fb = ' . $_danbo_fallbacks . ';'; 3559 3560 // Tag closed further below 3561 $scriptjs .= '</script><script src="https://static.danbo.org/publisher/q2g345hq2g534-4chan/js/preload.4chan.js" defer>'; 3562 } 3563 3564 // Close the main script tag /!\ 3565 $scriptjs .= '</script>'; 3566 3567 // PubFuture 3568 if (DEFAULT_BURICHAN) { 3569 $scriptjs .= '<script async data-cfasync="false" src="https://cdn.pubfuture-ad.com/v2/unit/pt.js"></script>'; 3570 } 3571 3572 $testjs = ( TEST_BOARD ) ? 'test/core-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'core.min.' . JS_VERSION_CORE . '.js'; 3573 $testextra = ( TEST_BOARD ) ? 'test/extension-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'extension.min.' . JS_VERSION_EXT . '.js'; 3574 3575 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testjs . '"></script>'; 3576 3577 if( !$error ) $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testextra . '"></script>'; 3578 3579 // April 2022 3580 //$scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/emotes2022.js?8"></script>'; 3581 3582 if (TEST_BOARD) { 3583 $stylejs = ''; 3584 } 3585 else { 3586 $stylejs = ''; 3587 } 3588 3589 if (ENABLE_PAINTERJS && $_GET['mode'] != 'oe_finish') { 3590 if (TEST_BOARD) { 3591 $scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/test/tegaki-8psvqAqszI.' . JS_VERSION_TEST . '.js"></script>'; 3592 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki-8psvqAqszI.' . CSS_VERSION_TEST . '.css">'; 3593 } 3594 else { 3595 $scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/tegaki.min.' . JS_VERSION_PAINTER . '.js"></script>'; 3596 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki.' . CSS_VERSION_PAINTER . '.css">'; 3597 } 3598 } 3599 /* 3600 if (!$is_arclist && defined('CSS_MATERIAL') && CSS_MATERIAL) { 3601 $css .= '<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type="text/css">'; 3602 } 3603 */ 3604 if( !$res ) { 3605 $prev = ( $page - DEF_PAGES ) / DEF_PAGES; 3606 $next = ( $page + DEF_PAGES ) / DEF_PAGES; 3607 3608 if( $prev == 0 ) { 3609 $prev_link = SELF_PATH2; 3610 } else if( $prev > 0 ) { 3611 $prev_link = $prev . PHP_EXT2; 3612 } 3613 3614 // maybe >= ? 3615 if( ( $npages - $page ) > DEF_PAGES ) { 3616 $next_link = $next . PHP_EXT2; 3617 } 3618 } 3619 3620 if ($is_arclist) { 3621 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR.'/archive">'; 3622 } 3623 else if (!$res) { 3624 if ($page > 0) { 3625 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR.'/' . (($page / DEF_PAGES) + 1) . '">'; 3626 } 3627 else { 3628 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' .BOARD_DIR.'/">'; 3629 } 3630 } 3631 elseif ($res) { 3632 $href_context = $log[$res]['semantic_url']; 3633 3634 if ($href_context !== '') { 3635 $href_context = "/$href_context"; 3636 } 3637 3638 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' .BOARD_DIR.'/thread/'.$res.$href_context . '">'; 3639 } 3640 else { 3641 $canonical = ''; 3642 } 3643 3644 $clean_title = strip_tags(TITLE); 3645 3646 if ($res) { 3647 $page_metatags = generate_page_metatags($log[$res]['sub'], $log[$res]['com']); 3648 3649 if ($page_metatags) { 3650 $page_description = $page_metatags[0] . ' - ' . META_DESCRIPTION; 3651 $page_keywords = META_KEYWORDS . $page_metatags[1]; 3652 } 3653 else { 3654 $page_description = META_DESCRIPTION; 3655 $page_keywords = META_KEYWORDS; 3656 } 3657 3658 $page_title = generate_page_title($res, $log[$res]['sub'], $log[$res]['com']) 3659 . ' - ' . preg_replace('/^[\[\/][a-z0-9]+[\]\/] - /i', '', $clean_title); 3660 } 3661 else { 3662 $page_description = META_DESCRIPTION; 3663 $page_keywords = META_KEYWORDS; 3664 3665 $page_title = $clean_title; 3666 3667 if ($is_arclist) { 3668 $page_title .= ' - Archive'; 3669 } 3670 else if ($page > 0) { 3671 $page_title .= ' - Page ' . (($page / DEF_PAGES) + 1); 3672 } 3673 } 3674 3675 $page_title .= ' - 4chan'; 3676 3677 if (!$_is_archived) { 3678 $_delegate_ch = '<meta http-equiv="Delegate-CH" content="Sec-CH-UA-Model https://sys.4chan.org">'; 3679 } 3680 else { 3681 $_delegate_ch = ''; 3682 } 3683 3684 $dat .= '<!DOCTYPE html> 3685 <html> 3686 <head> 3687 <meta charset="utf-8"> 3688 <meta name="robots" content="' . META_ROBOTS . '"> 3689 <meta name="description" content="' . $page_description . '"> 3690 <meta name="keywords" content="' . $page_keywords . '"> 3691 <meta name="viewport" content="width=device-width,initial-scale=1"> 3692 ' . $rta . ' 3693 ' . $favicon . ' 3694 ' . $css . ' 3695 ' . $canonical . ' 3696 ' . $rss . $_delegate_ch . ' 3697 <title>' . $page_title . '</title>' . $scriptjs . $extra; 3698 3699 $embedearly = EMBEDEARLY; 3700 3701 $adembedearly = AD_EMBEDEARLY; 3702 3703 if (AD_ADBLOCK_TEXT && (DEFAULT_BURICHAN || BOARD_DIR === 'pol' || BOARD_DIR === 'bant')) { 3704 $adembedearly .= file_get_contents_cached(AD_ADBLOCK_TEXT); 3705 } 3706 3707 $board_class = 'board_' . BOARD_DIR; 3708 3709 if (!$res) { 3710 if ($is_arclist) { 3711 $board_class = 'is_arclist ' . $board_class; 3712 } 3713 else { 3714 $board_class = 'is_index ' . $board_class; 3715 } 3716 } 3717 else { 3718 $board_class = 'is_thread ' . $board_class; 3719 } 3720 3721 if (TEXT_ONLY) { 3722 $board_class = 'text_only ' . $board_class; 3723 } 3724 3725 if (!$is_arclist) { 3726 $abovePostForm = '<hr class="abovePostForm">'; 3727 } 3728 else { 3729 $abovePostForm = ''; 3730 } 3731 3732 $dat .= <<<HTML 3733 $embedearly 3734 $adembedearly 3735 <noscript><style type="text/css">#postForm { display: table !important; }#g-recaptcha { display: none; }</style></noscript> 3736 </head> 3737 <body class="$board_class"> 3738 <span id="id_css"></span> 3739 $stylejs 3740 $includenav 3741 3742 <div class="boardBanner"> 3743 $titlepart 3744 <div class="boardTitle">$title</div> 3745 $subtitle 3746 </div> 3747 $abovePostForm 3748 HTML; 3749 3750 if (!$error && !$is_arclist) { 3751 /* 3752 if (defined('ADS_BIDGLASS_TOP_MOBILE') && ADS_BIDGLASS_TOP_MOBILE) { 3753 $dat .= '<div class="adg-rects mobile"><div class="ad-bgls adp-250 bidglass-unit-' . ADS_BIDGLASS_TOP_MOBILE . '" data-m data-u="' . ADS_BIDGLASS_TOP_MOBILE . '" style="pointer-events: none;"></div><div class="adl">[<a target="_blank" href="https://www.4chan.org/advertise">Advertise on 4chan</a>]</div><hr class="belowLeaderboard"></div>'; 3754 } 3755 else if (defined('AD_ABC_TOP_MOBILE') && AD_ABC_TOP_MOBILE) { 3756 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="' . AD_ABC_TOP_MOBILE . '"></div><hr class="belowLeaderboard"></div>'; 3757 }*/ 3758 } 3759 } 3760 3761 function delete_uploaded_files() 3762 { 3763 global $upfile_name, $upfile, $dest, $pchfile; 3764 if( $dest || $upfile ) { 3765 @unlink( $dest ); 3766 @unlink( $upfile ); 3767 } 3768 } 3769 3770 /* Footer */ 3771 function foot( &$dat, $error = false, $is_arclist = false ) 3772 { 3773 global $update_avg_secs; 3774 3775 $includenav = file_get_contents_cached(NAV2_TXT); 3776 3777 $dat .= $includenav; 3778 $dat .= rebuildd_stats(); 3779 3780 if( CODE_TAGS ) { 3781 $dat .= '<script type="text/javascript" src="' 3782 . STATIC_SERVER . 'js/prettify/prettify.' 3783 . JS_VERSION . '.js"></script><script type="text/javascript">prettyPrint();</script>'; 3784 } 3785 3786 $dat .= EMBEDLATE . '</body></html>'; 3787 } 3788 3789 function error($mes, $unused = '') { 3790 global $mode; 3791 3792 if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json') { 3793 error_json($mes); 3794 } 3795 3796 if( $mode == "report" ) fancydie( $mes ); 3797 3798 delete_uploaded_files(); 3799 3800 head( $dat, 0, 1 ); 3801 3802 $protocol = (stripos($_SERVER["HTTP_REFERER"], "https") === 0) ? 'https:' : 'http:'; 3803 3804 $dat .= '<table style="text-align: center; width: 100%; height: 300px;"><tr valign="middle"><td align="center" style="font-size: x-large; font-weight: bold;"><span id="errmsg" style="color: red;">' . $mes . '</span><br><br>[<a href='; 3805 3806 if (preg_match('#^' . $protocol . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/thread/([0-9]+)#', $_SERVER["HTTP_REFERER"], $m)) { 3807 $thread_part = 'thread/' . (int)$m[1]; 3808 } 3809 else { 3810 $thread_part = ''; 3811 } 3812 3813 $dat .= $protocol . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/' . $thread_part . ">" . S_RELOAD . "</a>]</td></tr></table><br><br><hr size=1>"; 3814 3815 foot( $dat, true ); 3816 3817 if (TEST_BOARD==1) { 3818 internal_error_log("post error", $mes); 3819 } 3820 3821 die( $dat ); 3822 } 3823 3824 function error_json($msg) { 3825 delete_uploaded_files(); 3826 3827 header('Content-Type: application/json'); 3828 3829 echo json_encode(['error' => $msg]); 3830 3831 die(); 3832 } 3833 3834 function error_redirect($mes, $redirect, $timeout = 3000) { 3835 delete_uploaded_files(); 3836 head( $dat, 0, 1 ); 3837 $dat .= <<<HTML 3838 <script type="text/javascript"> 3839 setTimeout(function() { window.location = "$redirect"; }, $timeout); 3840 </script> 3841 HTML; 3842 $dat .= '<table style="text-align: center; width: 100%; height: 300px;"><tr valign="middle"><td align="center" style="font-size: x-large; font-weight: bold;"><span id="errmsg" style="color: red;">' . $mes . '</span><br><br>[<a href='; 3843 $dat .= '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/>" 3844 . S_RELOAD . "</a>]</td></tr></table><br><br><hr size=1>"; 3845 foot( $dat ); 3846 3847 if (TEST_BOARD==1) { 3848 internal_error_log("post error", $mes); 3849 } 3850 3851 die( $dat ); 3852 } 3853 3854 /* Auto Linker */ 3855 function normalize_link_cb( $m ) 3856 { 3857 3858 $subdomain = $m[1]; 3859 $original = $m[0]; 3860 $board = strtolower( $m[2] ); 3861 $m[0] = $m[1] = $m[2] = ''; 3862 3863 $count = count( $m ) - 1; 3864 for( $i = $count; $i > 2; $i-- ) { 3865 if( $m[$i] ) { 3866 $no = $m[$i]; 3867 break; 3868 } 3869 } 3870 3871 if( $subdomain != 'boards') { 3872 return $original; 3873 } 3874 3875 if( stripos( $no, 'catalog' ) === 0 ) { 3876 3877 if( ( $pos = stripos( $no, '#s=' ) ) !== false ) { 3878 $term = substr( $no, $pos + 3 ); 3879 } else { 3880 $term = 'catalog'; 3881 } 3882 3883 return ">>>/$board/$term"; 3884 } 3885 3886 if( $board == BOARD_DIR && $no && $no != 'catalog' ) { 3887 return ">>$no"; 3888 } else { 3889 return ">>>/$board/$no"; 3890 } 3891 } 3892 3893 function normalize_links( $proto ) 3894 { 3895 // change http://xxx.4chan.org/board/res/no links into plaintext >># or >>>/board/# 3896 $proto = preg_replace_callback( '@https?://([a-z]*)[.](?:4chan|4channel)[.]org/(\w+)/(?:(res|thread)/(\d+)(?:\/[-a-z0-9]+)?(?:#[qp]?(\d*))?|(catalog(?:#s=[a-z0-9+]+)?)|\w+.php[?]res=(\d+)(?:#[qp]?(\d*))?|)(?=[\s.<!?,]|$)@i', 'normalize_link_cb', $proto ); 3897 3898 return $proto; 3899 } 3900 3901 // TODO merge with get_resto_for 3902 function post_resto($no) { 3903 global $log; 3904 3905 if (isset($log[$no])) { 3906 return $log[$no]['resto']; 3907 } 3908 3909 $q = mysql_board_call('SELECT resto FROM `%s` WHERE no=%d', SQLLOG, $no); 3910 if (!mysql_num_rows($q)) { 3911 $log[$no] = array('resto' => false); 3912 return false; 3913 } 3914 $r = (int)mysql_fetch_row($q)[0]; 3915 $log[$no] = array('resto' => $r); 3916 return $r; 3917 3918 return false; 3919 } 3920 3921 // FIXME 3922 // This might not be used anywhere anymore 3923 function intraboard_link_cb( $m ) 3924 { 3925 global $intraboard_cb_resno, $log; 3926 $no = $m[1]; 3927 $resno = $intraboard_cb_resno; 3928 $resto = post_resto( $no ); // doesn't like assignment in condition 3929 if( $resto !== false ) { 3930 $resdir = ( $resno ? '' : RES_DIR2 ); 3931 $ext = PHP_EXT2; 3932 $id = NEW_HTML ? "p$no" : "$no"; 3933 if( $resno && $resno == $resto ) // linking to a reply in the same thread 3934 return "<a href=\"#$id\" class=\"quotelink\">>>$no</a>"; 3935 elseif( $resto == 0 ) // linking to a thread 3936 return "<a href=\"$resdir$no$ext#$id\" class=\"quotelink\">>>$no</a>"; else // linking to a reply in another thread 3937 return "<a href=\"$resdir$resto$ext#$id\" class=\"quotelink\">>>$no</a>"; 3938 } 3939 3940 return '<span class="deadlink">' . $m[0] . '</span>'; 3941 } 3942 3943 // FIXME 3944 // This might not be used anywhere anymore, see parse_intraboard_link() 3945 function intraboard_links( $proto, $resno ) 3946 { 3947 global $intraboard_cb_resno; 3948 3949 $intraboard_cb_resno = $resno; 3950 3951 $proto = preg_replace_callback( '/>>([0-9]+)/', 'intraboard_link_cb', $proto ); 3952 3953 return $proto; 3954 } 3955 3956 function other_board_resto( $board, $resno ) 3957 { 3958 // this function is a little slow 3959 // board requirements - either is the current board (for /test/) or is public (in boardlist) 3960 // returns - 3961 // FALSE if resno does not exist 3962 // 0 if resno is a thread 3963 // an id if resno is a reply to a thread 3964 3965 static $boardlist = array(); 3966 3967 if( !$boardlist ) 3968 $boardlist = array_flip( mysql_column_array( mysql_global_call( "select sql_cache dir from boardlist" ) ) ); 3969 3970 if( $board != BOARD_DIR && !isset( $boardlist[$board] ) ) 3971 return false; 3972 3973 $q = mysql_board_call( "select resto from `%s` where no=%d", $board, $resno ); 3974 if( !mysql_num_rows( $q ) ) 3975 return false; 3976 $r = mysql_result( $q, 0 ); 3977 3978 return $r; 3979 } 3980 3981 function interboard_link_cb( $m ) 3982 { 3983 // on one hand, we can link to imgboard.php, using any old subdomain, 3984 // and let apache & imgboard.php handle it when they click on the link 3985 // on the other hand, we can use the database to fetch the proper subdomain 3986 // and even the resto to construct a proper link to the html file (and whether it exists or not) 3987 3988 // for now, we'll assume there's more interboard links posted than interboard links visited. 3989 $board = '/'; 3990 $otherboard = mb_strtolower( $m[1] ); 3991 if( $m[2] ) { 3992 $resto = other_board_resto( $otherboard, $m[2] ); 3993 $id = "#p"; 3994 3995 if( $resto === false ) 3996 $url = ""; 3997 else if( $resto ) 3998 $url = $board . $otherboard . '/thread/' . $resto . $id . $m[2]; 3999 else 4000 $url = $board . $otherboard . '/thread/' . $m[2] . $id . $m[2]; 4001 } else $url = $board . $otherboard . '/'; 4002 4003 $b = $m[1]; 4004 $mlp_hack = BOARD_DIR == 'mlp' && ( $b == 'b' || $b == 'co' ); 4005 $original = mb_strtolower( $m[0] ); 4006 if( !$url || $mlp_hack ) 4007 return '<span class="deadlink">' . $original . '</span>'; 4008 else 4009 return "<a href=\"$url\" class=\"quotelink\">{$original}</a>"; 4010 } 4011 4012 function interboard_catalog_link_cb( $m ) 4013 { 4014 $board = $m[1]; 4015 if( $board == 'f' ) return $m[0]; 4016 4017 $lsearchquery = strtolower( urlencode( urldecode( $m[2] ) ) ); 4018 $original = mb_strtolower( str_replace( ">", "> ", $m[0] ) ); 4019 4020 if( $lsearchquery == "catalog" ) { 4021 return "<a href=\"/$board/catalog\" class=\"quotelink\">$original</a>"; 4022 } elseif( $lsearchquery == 'rules' ) { 4023 return '<a href="//www.' . L::d($board) . '/rules#' . $board . '" class="quotelink">' . $original . '</a>'; 4024 } else { 4025 return "<a href=\"/$board/catalog#s=$lsearchquery\" class=\"quotelink\">$original</a>"; 4026 } 4027 } 4028 4029 function boards_matching_arr() 4030 { 4031 static $boards_matching_arr; 4032 global $valid_boards; 4033 4034 if( empty( $boards_matching_arr ) ) $boards_matching_arr = explode( '|', $valid_boards ); 4035 4036 return $boards_matching_arr; 4037 } 4038 4039 // Normalize and linkify internal and non-quote links 4040 // before inserting the post into the database. 4041 function normalize_and_linkify($proto) { 4042 4043 if (strpos($proto, "4chan") !== false || strpos($proto, "4cdn.org") !== false) { 4044 // normalize long links 4045 $proto = normalize_links($proto); 4046 4047 // linkify other internal links 4048 if ((strpos($proto, "4chan") !== false && strpos($proto, "/derefer") === false) || strpos($proto, "4cdn.org") !== false) { 4049 $proto = preg_replace_callback( '/(https?:\/\/(?:[A-Za-z]*\.)?)(4chan|4channel|4cdn)(\.org)(\/[\w\-\.,@?^=%&;:\/~\+#\(\)]*[\w\-\@?^=%&;\/~\+#])?/i', 'clean_internal_link', $proto ); 4050 $proto = preg_replace( '/([<][^>]*?)<a href="((https?:\/\/(?:[A-Za-z]*\.)?)(4chan|4channel|4cdn)(\.org)(\/[\w\-\.,@?^=%&:\/~\+#\(\)]*[\w\-\@?^=%&\/~\+#])?)" target="_blank">\\2<\/a>([^<]*?[>])/i', '\\1\\3\\4\\5\\6\\7\\8', $proto ); 4051 } 4052 } 4053 4054 if (strpos($proto, '>>>') !== false) { 4055 $proto = preg_replace_callback('#>>>/([a-z0-9]+)/([a-z0-9+/,l\-]*)#', 'auto_link_static_cb', $proto); 4056 } 4057 4058 return $proto; 4059 } 4060 4061 // Removes >> links from internal 4chan.org links 4062 function clean_internal_link($matches) { 4063 $link = preg_replace('/>>>|>>/', '', $matches[0]); 4064 return "<a href=\"$link\" target=\"_blank\">$link</a>"; 4065 } 4066 4067 function auto_link_static_cb($matches) { 4068 $post = $matches[0]; 4069 $inter_board = $matches[1]; 4070 $no = $matches[2]; 4071 4072 $boards_matching_arr = boards_matching_arr(); 4073 4074 $full_link = $post; 4075 4076 $inter_board = strtolower( $inter_board ); 4077 4078 $is_board_link = ($no == ''); 4079 4080 $resno = $no; 4081 4082 // Text boards 4083 // Catalog, rules, and /rs/ links 4084 if (!is_numeric( $resno ) || $is_board_link) { 4085 $url = urlencode( urldecode( $resno ) ); 4086 4087 $target = ''; 4088 4089 if( strpos( $resno, 'rules' ) === 0 ) { 4090 $ruleno = ''; 4091 $ruleloc = strpos( $resno, '/' ); 4092 4093 if( $ruleloc !== false ) { 4094 $ruleno = substr( $resno, $ruleloc + 1 ); 4095 } 4096 4097 $parsed_link = '//www.' . L::d($inter_board) . "/rules#$inter_board$ruleno"; 4098 $target = ' target="_blank"'; 4099 } 4100 else if (in_array($inter_board, $boards_matching_arr)) { 4101 $parsed_link = '//boards.' . L::d($inter_board) . "/$inter_board/"; 4102 4103 if( $inter_board == 'f' && $url == 'catalog' ) return $full_link; 4104 4105 if( !$is_board_link ) { 4106 $parsed_link .= ($url == 'catalog' ? 'catalog' : "catalog#s=$url"); 4107 } 4108 } 4109 else { 4110 return $full_link; 4111 } 4112 4113 return '<a href="' . $parsed_link . '" class="quotelink"' . $target . '>' . $full_link . '</a>'; 4114 } 4115 4116 return $full_link; 4117 } 4118 4119 function auto_link( $proto, $resno ) 4120 { 4121 global $current_resno; 4122 static $has_gen = 0; 4123 4124 if( !$has_gen ) { 4125 boards_matching_arr(); 4126 $has_gen = 1; 4127 } 4128 4129 // The majority of posts don't contain links, so don't go there and waste time on preg junk 4130 if( strpos( $proto, '>>' ) !== false ) { 4131 $current_resno = $resno; 4132 $proto = preg_replace_callback( '#(>>[0-9]+|>>>/[a-z0-9]+/[a-z0-9+/-]*)#', 'auto_link_cb', $proto ); 4133 } 4134 4135 return $proto; 4136 } 4137 4138 function auto_link_cb( $post ) 4139 { 4140 global $current_resno; 4141 4142 //var_dump($post); 4143 $is_inter = ( strpos( $post[0], '>>>/' ) === 0 ); 4144 $post = $post[0]; 4145 4146 if( $is_inter ) { 4147 preg_match( '#>>>/([a-z0-9]+)/(.*)#', $post, $match ); 4148 4149 return parse_interboard_link( $match[0], $match[1], $match[2] ); 4150 } else { 4151 $no = explode( '>>', $post ); 4152 4153 return parse_intraboard_link( $post, $no[1], $current_resno ); 4154 } 4155 } 4156 4157 function parse_intraboard_link( $post, $no, $resno ) 4158 { 4159 $full_link = $post; 4160 //$no = substr( $post, $i - $in_link_char, $in_link_char ); 4161 $resto = post_resto( $no ); 4162 4163 if( $resto === false ) { 4164 $parsed_link = '<span class="deadlink">' . $full_link . '</span>'; 4165 4166 return $parsed_link; 4167 } 4168 4169 $ext = PHP_EXT2; 4170 $id = NEW_HTML ? "p$no" : "$no"; 4171 4172 // linking to a reply or the OP in the same thread 4173 if ($resno && ($resno == $resto || $resno == $no)) { 4174 $parsed_link = "<a href=\"#$id\" class=\"quotelink\">>>$no</a>"; 4175 } 4176 // linking to an OP in another thread or from indexes 4177 elseif ($resto == 0) { 4178 $parsed_link = "<a href=\"/" . BOARD_DIR . "/" . RES_DIR2 . "$no$ext#$id\" class=\"quotelink\">>>$no</a>"; 4179 } 4180 // linking to a reply in another thread or from indexes 4181 else { 4182 $parsed_link = "<a href=\"/" . BOARD_DIR . "/" . RES_DIR2 . "$resto$ext#$id\" class=\"quotelink\">>>$no</a>"; 4183 } 4184 4185 return $parsed_link; 4186 } 4187 4188 function parse_interboard_link( $post, $inter_board, $no ) 4189 { 4190 global $valid_boards; 4191 4192 // make sure to account for > not > 4193 $full_link = $post; 4194 $inter_board = strtolower( $inter_board ); 4195 4196 // Are we a board link? 4197 $is_board_link = ( $no == '' ); 4198 4199 // ... to get resno! 4200 $resno = $no; 4201 4202 $valid_rule = $inter_board == 'global' && strpos( $resno, 'rules' ) !== false; 4203 4204 // Skip static links (boards, catalog) 4205 if ($is_board_link 4206 || $valid_rule 4207 || !is_numeric($resno) 4208 || !in_array($inter_board, boards_matching_arr()) 4209 ) { 4210 return $full_link; 4211 } 4212 4213 // Valid board, now check post number 4214 $resto = other_board_resto( $inter_board, $resno ); 4215 4216 if( $resto === false ) { // dead link 4217 $url = ''; 4218 } elseif( $resto ) { // different thread 4219 $url = '//boards.' . L::d($inter_board) . "/{$inter_board}/thread/$resto#p$resno"; 4220 } else { // same thread 4221 $url = '//boards.' . L::d($inter_board) . "/{$inter_board}/thread/$resno#p$resno"; 4222 } 4223 4224 $disable = BOARD_DIR == 'mlp' && ( $inter_board == 'b' || $inter_board == 'co' ); 4225 if( !$url || $disable ) { 4226 $parsed_link = '<span class="deadlink">' . $full_link . '</span>'; 4227 } else { 4228 $parsed_link = "<a href=\"$url\" class=\"quotelink\">$full_link</a>"; 4229 } 4230 4231 return $parsed_link; 4232 } 4233 4234 function trans_same_board_links( &$com ) 4235 { 4236 $match = '>>>/' . BOARD_DIR . '/'; 4237 $len = strlen( $match ); 4238 $i = stripos( $com, $match ); 4239 $boardlen = strlen( BOARD_DIR ) - 1; 4240 $dir = BOARD_DIR; 4241 $com .= '~'; 4242 4243 while( isset( $com{$i} ) ) { 4244 4245 if( is_numeric( $com{$i + $len} ) ) { 4246 // Match, replace out 4247 $com = substr_replace( $com, '>>', $i, $len ); 4248 4249 } 4250 4251 $i = stripos( $com, $match, $i + $len ); 4252 if( $i === false ) { 4253 $com = substr( $com, 0, strlen( $com ) - 1 ); 4254 4255 return; 4256 } 4257 } 4258 } 4259 4260 function auto_link_parser( $post, $resno ) 4261 { 4262 $post .= '<'; 4263 4264 $i = 0; 4265 $in_link = false; 4266 $in_link_char = 0; 4267 4268 $is_inter = false; 4269 $inter_found_board = false; 4270 $inter_board = ''; 4271 $inter_is_catalog = false; 4272 4273 $is_intra = false; 4274 $gt_count = 0; 4275 $mbcl = 10; // forgot about dis links :( 4276 4277 $dbg = ""; 4278 4279 while( isset( $post{$i} ) ) { 4280 $seen_gt_this = false; 4281 $c = $post{$i}; 4282 4283 if( !$in_link ) { 4284 // Not in a link, find > 4285 4286 if( $c == '&' ) { 4287 if( $post{$i + 1} == 'g' && $post{$i + 2} == 't' && $post{$i + 3} == ';' ) { 4288 if( $gt_count < 3 ) $gt_count++; 4289 4290 $i = $i + 4; 4291 continue; 4292 } 4293 } 4294 4295 if( ( $c == '/' && $gt_count == 3 ) || ( $gt_count > 1 && is_numeric( $c ) ) ) { 4296 $in_link_char = 0; 4297 $in_link = true; 4298 $i--; // shift us back a char to get the right match... 4299 } 4300 4301 $gt_count = 0; 4302 } else { 4303 // We can be sure we have a valid character for our link 4304 if( $in_link_char == 0 ) { 4305 4306 if( $c == '/' ) { 4307 $is_inter = true; 4308 $is_intra = false; 4309 } else { 4310 $is_intra = true; 4311 $is_inter = false; 4312 } 4313 } 4314 4315 if( $is_inter ) { 4316 4317 if( $in_link_char == 0 ) { 4318 $in_link_char++; 4319 $i++; 4320 continue; 4321 } 4322 4323 if( $in_link_char > $mbcl && $c != '/' && !$inter_found_board ) { 4324 // yup :( 4325 4326 $in_link = false; 4327 $is_inter = false; 4328 4329 $i++; 4330 continue; 4331 } 4332 4333 if( !$inter_found_board && $c == '/' ) { 4334 $inter_board = substr( $post, $i - ( $in_link_char - 1 ), $in_link_char - 1 ); 4335 4336 $inter_found_board = true; 4337 $in_link_char++; 4338 $i++; 4339 continue; 4340 } 4341 4342 if( $inter_found_board ) { 4343 $match = ( 4344 ctype_alnum( $c ) || 4345 $c == '+' || 4346 $c == '/' || 4347 $c == '-' 4348 ); 4349 4350 if( $match ) { 4351 $in_link_char++; 4352 $i++; 4353 continue; 4354 } 4355 } 4356 4357 if( !$inter_found_board ) { 4358 $in_link_char++; 4359 $i++; 4360 continue; 4361 } 4362 4363 4364 parse_interboard_link( $post, $i, $in_link_char, $inter_board ); 4365 4366 $is_inter = false; 4367 $in_link = false; 4368 $inter_found_board = false; 4369 } 4370 4371 if( $is_intra ) { 4372 if( is_numeric( $c ) ) { 4373 $in_link_char++; 4374 } else { 4375 // reached the end 4376 parse_intraboard_link( $post, $i, $in_link_char, $resno ); 4377 4378 $in_link = false; 4379 $is_intra = false; 4380 } 4381 } 4382 } 4383 4384 $i++; 4385 } 4386 4387 return substr( $post, 0, strlen( $post ) - 1 ) . $dbg; 4388 } 4389 4390 /** 4391 * New version of check_blacklist() 4392 * $post must be an array with the following fields: 4393 * resto, filename, name, tripcode, password, 4pass_id 4394 */ 4395 function check_md5_blacklist($md5, $original_md5, $post, $dest) { 4396 if (!$md5) { 4397 return false; 4398 } 4399 4400 $board = BOARD_DIR; 4401 4402 if (DEFAULT_BURICHAN) { 4403 $ws_clause = " OR boardrestrict = '_ws_'"; 4404 } 4405 else { 4406 $ws_clause = ''; 4407 } 4408 4409 $sql =<<<SQL 4410 SELECT SQL_NO_CACHE * FROM blacklist 4411 WHERE active = 1 AND (boardrestrict = '' OR boardrestrict = '$board'$ws_clause) 4412 AND field = 'md5' AND contents = '%s' LIMIT 1 4413 SQL; 4414 4415 // Check MD5 4416 $query = mysql_global_call($sql, $md5); 4417 4418 // Check original MD5 if provided 4419 if (!mysql_num_rows($query)) { 4420 if ($original_md5 && $original_md5 !== $md5) { 4421 $query = mysql_global_call($sql, $original_md5); 4422 4423 if (!mysql_num_rows($query)) { 4424 return false; 4425 } 4426 4427 $md5 = $original_md5; 4428 } 4429 else { 4430 return false; 4431 } 4432 } 4433 4434 $row = mysql_fetch_assoc($query); 4435 4436 if (!$row) { 4437 return false; 4438 } 4439 4440 // Private reason 4441 $private_reason = "Blacklisted md5 - " . htmlspecialchars($md5) 4442 . ' - Filename: ' . htmlspecialchars($post['filename']); 4443 4444 // Ban name 4445 if (isset($post['name'])) { 4446 $ban_name = $post['name']; 4447 } 4448 else { 4449 $ban_name = S_ANONAME; 4450 } 4451 4452 if (isset($post['tripcode'])) { 4453 $ban_name .= " #{$post['tripcode']}"; 4454 } 4455 4456 // Ban password 4457 if (isset($post['password']) && $post['password']) { 4458 $pwd = $post['password']; 4459 } 4460 else { 4461 $pwd = null; 4462 } 4463 4464 // Ban 4chan pass 4465 if (isset($post['4pass_id']) && $post['4pass_id']) { 4466 $pass_id = $post['4pass_id']; 4467 } 4468 else { 4469 $pass_id = null; 4470 } 4471 4472 // Thread id for redirection 4473 if (isset($post['resto'])) { 4474 $resto = (int)$post['resto']; 4475 } 4476 else { 4477 $resto = 0; 4478 } 4479 4480 // -------- 4481 4482 // Reject 4483 if (!$row['ban']) { 4484 if (TEST_BOARD) { 4485 error($private_reason, $dest); 4486 } 4487 } 4488 // Auto-ban 4489 else if ($row['ban'] == '1') { 4490 auto_ban_poster($ban_name, $row['banlength'], 1, $private_reason, $row['banreason'], false, $pwd, $pass_id); 4491 } 4492 // Show error (DMCA requests) 4493 else if ($row['ban'] == '2') { 4494 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4495 4496 $query = "SELECT ip FROM user_actions WHERE action = 'fail_dmca' AND ip = %d AND time >= DATE_SUB(NOW(), INTERVAL 1 DAY)"; 4497 4498 $res = mysql_global_call($query, $ip); 4499 4500 if ($res && mysql_num_rows($res) > 0) { 4501 $private_reason = "DMCA complaint from {$row['description']} (blacklist ID: {$row['id']})"; 4502 auto_ban_poster($ban_name, 3, 1, $private_reason, S_DMCABANREASON, true, $pwd, $pass_id); 4503 error_redirect(S_BANNED, 'https://www.' . L::d(BOARD_DIR) . '/banned'); 4504 } 4505 else { 4506 $query = "INSERT INTO user_actions (board,postno,ip,time,uploaded,action) VALUES ('%s', %d, %d, NOW(), 0, 'fail_dmca')"; 4507 mysql_global_call($query, $board, 0, $ip); 4508 } 4509 4510 error(S_DMCAFAIL, $dest); 4511 } 4512 4513 if ($row['quiet']) { 4514 show_post_successful_fake($resto); 4515 die(); 4516 } 4517 4518 error(S_FAILEDUPLOAD, $dest); 4519 } 4520 4521 function check_blacklist($post, $dest, $file_ext = '', $resto = 0, $pwd = null, $pass_id = null) { 4522 //if( has_level() ) return; 4523 4524 $board = BOARD_DIR; 4525 4526 if (DEFAULT_BURICHAN) { 4527 $ws_clause = " OR boardrestrict = '_ws_'"; 4528 } 4529 else { 4530 $ws_clause = ''; 4531 } 4532 4533 $querystr = "SELECT SQL_NO_CACHE * FROM blacklist WHERE active=1 AND (boardrestrict='' or boardrestrict='$board'$ws_clause) AND (0 "; 4534 foreach( $post as $field => $contents ) { 4535 if( $contents ) { 4536 $contents = mysql_real_escape_string( html_entity_decode( $contents ) ); 4537 $querystr .= "OR (field='$field' AND contents='$contents') "; 4538 } 4539 } 4540 $querystr .= ") LIMIT 1"; 4541 4542 $query = mysql_global_call( $querystr ); 4543 if( mysql_num_rows( $query ) == 0 ) return false; 4544 4545 $row = mysql_fetch_assoc( $query ); 4546 $prvreason = "Blacklisted ${row['field']} - " . htmlspecialchars( $row['contents'] ); 4547 4548 if ($row['field'] == 'md5') { 4549 $prvreason .= ' - Filename: ' . htmlspecialchars($post['filename']) . $file_ext; 4550 } 4551 4552 if (!$row['ban']) { 4553 if (TEST_BOARD) { 4554 error( "Blacklisted: " . $prvreason, $dest ); 4555 } 4556 } 4557 // Auto-ban 4558 else if ($row['ban'] == '1') { 4559 auto_ban_poster($post['trip'] ? $post['nametrip'] : $post['name'], $row['banlength'], 1, $prvreason, $row['banreason'], false, $pwd, $pass_id); 4560 } 4561 // Show error (DMCA requests) 4562 else if ($row['ban'] == '2') { 4563 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4564 4565 $query = "SELECT ip FROM user_actions WHERE action = 'fail_dmca' AND ip = %d AND time >= DATE_SUB(NOW(), INTERVAL 1 DAY)"; 4566 4567 $res = mysql_global_call($query, $ip); 4568 4569 if ($res && mysql_num_rows($res) > 0) { 4570 $prvreason = "DMCA complaint from {$row['description']} (blacklist ID: {$row['id']})"; 4571 4572 auto_ban_poster($post['trip'] ? $post['nametrip'] : $post['name'], 3, 1, $prvreason, S_DMCABANREASON, true, $pwd, $pass_id); 4573 4574 error_redirect(S_BANNED, 'https://www.' . L::d(BOARD_DIR) . '/banned'); 4575 } 4576 else { 4577 $query = "INSERT INTO user_actions (board,postno,ip,time,uploaded,action) VALUES ('%s',%d,%d,NOW(),0,'fail_dmca')"; 4578 mysql_global_call($query, $board, 0, $ip); 4579 } 4580 4581 error(S_DMCAFAIL, $dest); 4582 } 4583 /* 4584 { 4585 $ip = $_SERVER['REMOTE_ADDR']; 4586 quick_log_to("/www/perhost/blacklist.log", "IP $ip board /$board/: $prvreason"); 4587 } 4588 */ 4589 4590 if ($row['quiet']) { 4591 show_post_successful_fake($resto); 4592 die(); 4593 } 4594 4595 error(S_FAILEDUPLOAD, $dest); 4596 } 4597 4598 // we've already failed the floodcheck, check if they're a repeat offender and ban them 4599 function check_fail_floodcheck($info) 4600 { 4601 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4602 mysql_global_call("insert into user_actions (ip,board,action,time) values (%d,'%s','fail_floodcheck',now())", $ip, ''); 4603 $query = mysql_global_call("select count(*)>%d from user_actions where ip=%d and action='fail_floodcheck' and time >= subdate(now(), interval 1 hour)", LOGIN_FAIL_HOURLY, $ip); 4604 quick_log_to("/www/perhost/floodchecks.log", $info); 4605 if(mysql_result($query,0,0)) { 4606 auto_ban_poster("Anonymous", 1, 1, "got a flood check warning 5 times in an hour", "Sending an excessive number of server requests"); 4607 } 4608 } 4609 4610 // word-wrap without touching things inside of tags 4611 function wordwrap2( $str, $cols, $cut ) 4612 { 4613 // if there's no runs of $cols non-space characters, wordwrap is a no-op 4614 if( mb_strlen( $str ) < $cols || !preg_match( '/[^ <>]{' . $cols . '}/', $str ) ) { 4615 return $str; 4616 } 4617 $sections = preg_split( '/[<>]/', $str ); 4618 $str = ''; 4619 for( $i = 0; $i < count( $sections ); $i++ ) { 4620 if( $i % 2 ) { // inside a tag 4621 $str .= '<' . $sections[$i] . '>'; 4622 } else { // outside a tag 4623 $words = explode( ' ', $sections[$i] );/* 4624 $exclude = array( 4625 'http://', 4626 'https://', 4627 'www.' 4628 ); 4629 */ 4630 foreach( $words as &$word ) {/* 4631 foreach( $exclude as $match ) { 4632 if( stripos( $word, $match ) === 0 && stripos( $word, '4chan.org' ) !== false ) continue 2; 4633 }*/ 4634 4635 $word = htmlspecialchars_decode( $word, ENT_QUOTES ); 4636 $word = utf8_wordwrap( $word, $cols, $cut, true ); 4637 $word = htmlspecialchars( $word, ENT_QUOTES ); 4638 4639 } 4640 4641 $str .= implode( ' ', $words ); 4642 } 4643 } 4644 4645 return $str; 4646 } 4647 4648 function logtime( $desc ) 4649 { 4650 static $run = -1; 4651 if( !PROFILING ) return; 4652 if( $run == -1 ) { 4653 $run = getmypid_cached(); 4654 } 4655 $board = BOARD_DIR; 4656 $time = microtime( true ); 4657 mysql_global_call( "INSERT INTO profiling_times VALUES ('$board',$run,$time,'$desc')" ); 4658 } 4659 4660 function time_log($r) { 4661 if (TEST_BOARD && $_SERVER['HTTP_ACCEPT'] !== 'application/json') { 4662 echo "<!-- $r " . microtime(true) . " " . memory_get_usage(true) . " -->\n"; 4663 } 4664 } 4665 4666 function is_bad_xff( $xff ) 4667 { 4668 if ($xff === '8.8.8.8' || $xff === '62.210.138.29' || $xff === '212.129.0.228') { 4669 return true; 4670 } 4671 4672 list( $xffs ) = post_filter_get( "xffwhitelist" ); 4673 $ipnum = ip2long( $xff ); 4674 4675 if( !$ipnum ) return true; // text in xff field 4676 4677 return find_ipxff_in( 0, $ipnum, $xffs ); 4678 } 4679 4680 function has_doubles( $id ) 4681 { 4682 if( $id % 1000 == 0 ) return false; 4683 4684 $ones = $id % 10; 4685 $tens = ( $id / 10 ) % 10; 4686 4687 return $ones == $tens; 4688 } 4689 4690 function generate_uid($resto, $time, $ip = false) { 4691 if (DISP_ID_RANDOM) { 4692 $str = mt_rand(); 4693 } 4694 else { 4695 $str = !$ip ? $_SERVER["REMOTE_ADDR"] : $ip; 4696 4697 if (DISP_ID_PER_THREAD) { 4698 $str .= $resto ? $resto : date( 'Ymd', $time ); 4699 } else { 4700 $str .= 'hats'; // we will put a hat on it to confuse people :) 4701 } 4702 } 4703 4704 $salt = file_get_contents_cached( SALTFILE ); 4705 $hash = base64_encode( pack( "H*", sha1( $str . $salt ) ) ); 4706 4707 return substr( $hash, 0, 8 ); 4708 } 4709 4710 function parse_vip_capcode($capcode) { 4711 // Flood check 4712 $longip = ip2long($_SERVER['REMOTE_ADDR']); 4713 4714 $query = <<<SQL 4715 SELECT COUNT(*) FROM user_actions 4716 WHERE ip = %d AND action = 'fail_login' 4717 AND time >= SUBDATE(NOW(), INTERVAL 1 HOUR) 4718 SQL; 4719 4720 $res = mysql_global_call($query, $longip); 4721 4722 if (!$res) { 4723 return false; 4724 } 4725 4726 $count = mysql_fetch_row($res)[0]; 4727 4728 if ($count >= 3) { 4729 return false; 4730 } 4731 4732 // Now check the capcode 4733 list($_, $user_id, $user_key) = explode('!', $capcode, 3); 4734 4735 if (!$user_id || !$user_key) { 4736 return false; 4737 } 4738 4739 $query = "SELECT name, user_key FROM vip_capcodes WHERE active = 1 AND user_id = '%s' LIMIT 1"; 4740 4741 $res = mysql_global_call($query, $user_id); 4742 4743 if (!$res) { 4744 return false; 4745 } 4746 4747 $user = mysql_fetch_assoc($res); 4748 4749 if ($user && password_verify($user_key, $user['user_key'])) { 4750 $query = "UPDATE vip_capcodes SET last_used = %d, last_ip = '%s' WHERE user_id = '%s' LIMIT 1"; 4751 mysql_global_call($query, $_SERVER['REQUEST_TIME'], $_SERVER['REMOTE_ADDR'], $user_id); 4752 return $user['name']; 4753 } 4754 4755 // Log the failure 4756 $query = <<<SQL 4757 INSERT INTO user_actions (ip, board, action, time) 4758 VALUES (%d, '', 'fail_login', NOW()) 4759 SQL; 4760 4761 mysql_global_call($query, $longip); 4762 4763 return false; 4764 } 4765 4766 function parse_capcode($capcode, &$name = null) 4767 { 4768 if ($name !== null && strpos($capcode, 'capcode_!') === 0) { 4769 $vip_name = parse_vip_capcode($capcode); 4770 4771 if ($vip_name !== false) { 4772 $name = $vip_name; 4773 return 'verified'; 4774 } 4775 4776 return 'none'; 4777 } 4778 4779 if (!has_level()) { 4780 return 'none'; 4781 } 4782 4783 $user = strtolower( $_COOKIE['4chan_auser'] ); 4784 4785 $is_developer = has_flag( 'developer' ) && $capcode == 'capcode_dev'; 4786 $is_mod = ( has_level() && $capcode == 'capcode_mod' ); 4787 $is_manager = has_level('manager') && $capcode == 'capcode_manager'; 4788 4789 $is_admin = ( has_level( 'admin' ) && $capcode == 'capcode_admin' ); 4790 $is_founder = ( has_level( 'admin' ) && $capcode == 'capcode_founder' ); 4791 $highlight = ( has_level( 'admin' ) && $capcode == 'capcode_admin_hl' ); 4792 4793 if ($is_founder) { 4794 return 'founder'; 4795 } 4796 4797 if( $is_admin || $highlight ) { 4798 return ( $highlight ) ? 'admin_highlight' : 'admin'; 4799 } 4800 4801 if( $is_developer ) { 4802 return 'developer'; 4803 } 4804 4805 if ($is_manager) { 4806 return 'manager'; 4807 } 4808 4809 if (!has_flag('capcode') && !has_level('manager')) { 4810 if ($capcode !== '') { 4811 error(S_CANTCAPCODE); 4812 } 4813 } 4814 4815 if( $is_mod ) { 4816 return 'mod'; 4817 } 4818 4819 return 'none'; 4820 } 4821 4822 function generate_tim() { 4823 $time = $_SERVER['REQUEST_TIME']; 4824 $micro = substr(microtime(), 2, 4); 4825 $tail = mt_rand(0, 99); 4826 4827 if ($tail < 10) { 4828 $tail = "0$tail"; 4829 } 4830 4831 return "$time$micro$tail"; 4832 } 4833 4834 /* Regist */ 4835 function new_post( $name, $email, $sub, $com, $url, $pwd, $upfile, $upfile_name, $resto, $age, $filetag ) 4836 { 4837 global $pwdc, $textonly, $admin, $spoiler, $dest, $pchfile, $word_filters_enabled, $silent_reject; 4838 global $captcha_bypass, $passid; 4839 global $board_flags_array; 4840 4841 // Fro HTML and capcode posting 4842 $log_mod_action = false; 4843 $log_html_post = false; 4844 $log_capcode_post = false; 4845 4846 $mes = ""; 4847 4848 /* VARIOUS CHECKS BEFORE ANY POSTING TAKES PLACE */ 4849 4850 $oldbanbuster = ( $_SERVER['HTTP_USER_AGENT'] == 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' && $com == 'test' ); 4851 $newbanbuster = ( $_SERVER['HTTP_USER_AGENT'] == 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.19 (KHTML, like Gecko) Chrome/25.0.1324.0 Safari/537.19' && ( preg_match( '#^[a-zA-Z]{15}$#', $com ) || $com == 'test' ) && !isset( $_COOKIE['4chan_pass'] ) ); 4852 4853 if( BOARD_DIR == 'b' && ( $oldbanbuster || $newbanbuster ) ) { 4854 $msg = $com == 'test' ? 'com = test' : 'com = 15 length string'; 4855 auto_ban_poster( "", -1, 1, 'BanBuster proxy test match (Posting ' . $msg . ' from dodgy UA)', 'Proxy/Tor exit node.' ); 4856 error( S_NOCAPTCHA ); 4857 } 4858 4859 if( $_SERVER["REQUEST_METHOD"] != "POST" ) error( S_UNJUST ); 4860 4861 /* END CHECKS */ 4862 4863 $hasjs = isset( $_POST['hasjs'] ) && $_POST['hasjs'] === 'yes'; 4864 $stats_all = $hasjs ? 'with_js_all' : 'without_js_all'; 4865 $stats_ok = $hasjs ? 'with_js_success' : 'without_js_success'; 4866 4867 4868 $host = $_SERVER["REMOTE_ADDR"]; 4869 4870 time_log( "start" ); 4871 4872 // might be better to do this before the mysql connection 4873 if( !$upfile && !$resto ) { // allow textonly threads for moderators! 4874 if( has_level() ) $textonly = 1; 4875 } 4876 elseif( JANITOR_BOARD == 1 ) { // only allow mods/janitors to post, and textonly is always ok 4877 $textonly = 1; 4878 if( !has_level( 'janitor' ) ) 4879 die(); 4880 } 4881 4882 if (TEXT_ONLY && $upfile && $resto) { 4883 error(S_TEXT_ONLY); 4884 } 4885 4886 if (UPLOAD_BOARD) { 4887 if ($upfile && $resto) { 4888 error(S_FAILEDUPLOAD); 4889 } 4890 4891 $tags = upboard_tags(); 4892 4893 if( !$resto && !array_key_exists( (int)$filetag, $tags ) ) error( "Error: Invalid tag specified.", $filetag ); 4894 if( !$resto && !is_numeric( $filetag ) ) error( "Error: Invalid tag specified.", $filetag ); 4895 //if((int)$filetag==9999) error("No tag selected.",$filetag); 4896 if( !$resto ) $sub = (int)$filetag . '|' . $sub; 4897 4898 //OPs must have files 4899 if (!$upfile && !$resto) { 4900 error(S_NOUPLOAD); 4901 } 4902 } 4903 4904 // Password session 4905 $userpwd = new UserPwd($host, MAIN_DOMAIN, $pwdc); 4906 4907 $pass = $userpwd->getPwd(); 4908 4909 if (!$pass) { 4910 error(S_GENERICERROR, $dest); 4911 } 4912 4913 $resto = (int)$resto; 4914 4915 // time 4916 $time = $_SERVER['REQUEST_TIME']; 4917 $tim = generate_tim(); 4918 4919 $captcha_bypass_allow_credits = false; 4920 4921 $memcached = null; 4922 4923 if (isset($_POST['recaptcha_challenge_field'])) { 4924 error('Legacy captcha is no longer supported.'); 4925 } 4926 else if (isset($_POST['g-recaptcha-response'])) { 4927 if (CAPTCHA_TWISTER) { 4928 error('reCAPTCHA v2 is no longer supported.'); 4929 } 4930 4931 // Recaptcha v2 4932 start_auth_captcha(); 4933 4934 if (!$captcha_bypass) { 4935 $_c_ret = end_recaptcha_verify(); 4936 } 4937 } 4938 else { 4939 if (valid_captcha_bypass() !== true) { 4940 $memcached = create_memcached_instance(); 4941 4942 $_unsolved_count = 0; 4943 4944 // Captcha bypass credits 4945 if ($resto && isset($_POST['t-challenge']) && $_POST['t-challenge'] === 'noop') { 4946 if (use_twister_captcha_credit($memcached, $host, $userpwd) === false) { 4947 error(S_CAPTCHATIMEOUT); 4948 } 4949 } 4950 // Captcha verification failed 4951 else if (is_twister_captcha_valid($memcached, $host, $userpwd, BOARD_DIR, $resto, $_unsolved_count) === false) { 4952 // Silent captcha failure for new suspicious users 4953 //$_bad_actor = spam_filter_is_bad_actor(); 4954 //$_threat_score = spam_filter_get_threat_score($_SERVER['HTTP_X_GEO_COUNTRY'], !$resto, true); 4955 if (isset($_SERVER['HTTP_X_BOT_SCORE'])) { 4956 $_bot_score = (int)$_SERVER['HTTP_X_BOT_SCORE']; 4957 } 4958 else { 4959 $_bot_score = 100; 4960 } 4961 4962 if (!$userpwd || $userpwd->isUserKnownOrVerified(1440, 1) === false) { 4963 if ($_bot_score > 1 && $_bot_score < 80) { 4964 $_meta = spam_filter_format_http_headers(htmlspecialchars($com), $_SERVER['HTTP_X_GEO_COUNTRY'], $upfile_name, $_threat_score); 4965 if (isset($_POST['t-challenge']) && $_POST['t-response'] && isset($_COOKIE['_tcs'])) { 4966 log_failed_captcha($host, $userpwd, BOARD_DIR, $resto, true, $_meta); 4967 } 4968 show_post_successful_fake($resto, false); 4969 die(); 4970 } 4971 } 4972 error(S_BADCAPTCHA); 4973 } 4974 // Captcha verification succeeded 4975 else if ($_unsolved_count < 2 && CAPTCHA_ALLOW_BYPASS && spam_filter_is_bad_actor() === false) { 4976 $captcha_bypass_allow_credits = true; 4977 } 4978 } 4979 } 4980 /* 4981 if (spam_filter_is_bad_actor()) { 4982 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 4983 log_spam_filter_trigger('log_bad_actor', BOARD_DIR, $resto, $host, 1, $_bot_headers); 4984 } 4985 */ 4986 if (PASS_POST_ONLY && !$captcha_bypass) { 4987 error(S_PASS_POST_ONLY); 4988 } 4989 4990 // Validate 2FA if posting html 4991 if ((isset($_POST['html']) && $_POST['html']) && (has_level('manager') || has_flag('html') || has_flag('developer'))) { 4992 validate_otp(); 4993 $log_html_post = $log_mod_action = true; 4994 } 4995 4996 $locked_time = $time; 4997 // check closed 4998 if( $resto ) { 4999 if( !$cchk = mysql_board_call( "select closed,sticky,undead,archived,sub,com from `" . SQLLOG . "` where no=" . $resto ) ) { 5000 echo S_SQLFAIL; 5001 } 5002 list( $closed, $sticky, $undead, $is_archived, $_thread_sub, $_thread_com ) = mysql_fetch_row( $cchk ); 5003 if ($is_archived) { 5004 error(S_MAYNOTREPLY, $upfile); 5005 } 5006 $is_undead_sticky = $sticky == 1 && $undead == 1; 5007 if( $closed == 1 && !has_level() ) error( S_MAYNOTREPLY, $upfile ); 5008 mysql_free_result( $cchk ); 5009 5010 $sub = ''; 5011 } 5012 else { 5013 $is_undead_sticky = false; 5014 } 5015 5016 $has_image = $upfile && file_exists( $upfile ); 5017 5018 $md5 = null; 5019 $original_md5 = null; // MD5 before exif and other metadata stripping 5020 5021 if( $has_image ) { 5022 if (UPLOAD_BOARD) { 5023 if( file_exists( IMG_DIR . $upfile_name ) ) error( "Filename already exists.", $upfile ); 5024 5025 $dest = $upfile; 5026 5027 $upfile_name = sanitize_text( $upfile_name ); 5028 if( !is_file( $dest ) ) error( S_FAILEDUPLOAD, $dest ); 5029 if ($upfile_name[0] === '.') { 5030 error('Error: Invalid filename (first character cannot be a period).', $dest); 5031 } 5032 $size = getimagesize( $dest ); 5033 if( !is_array( $size ) ) error( S_NOREC, $dest ); 5034 5035 $W = $size[0]; 5036 $H = $size[1]; 5037 $fsize = filesize( $dest ); 5038 if( $fsize > MAX_KB * 1024 ) error( S_TOOLARGE, $dest ); 5039 if( $size[2] == 6 || $size[2] == 5 ) { 5040 error( S_FAILEDUPLOAD, $dest ); 5041 } 5042 switch( $size[2] ) { 5043 case 4 : 5044 case 13 : 5045 $ext = ".swf"; 5046 break; 5047 default : 5048 $ext = ".xxx"; 5049 error( S_FAILEDUPLOAD, $dest ); 5050 break; 5051 } 5052 5053 time_log( "sfpi" ); 5054 rpc_task(); 5055 5056 $len = strlen( $ext ); 5057 } 5058 else { 5059 // NOT upload board 5060 5061 // check image limit 5062 if( $resto && !$sticky && !$undead && !has_level() ) { 5063 if( !$result = mysql_board_call( "SELECT COUNT(*) FROM `" . SQLLOG . "` WHERE archived = 0 AND resto=$resto AND fsize!=0 AND filedeleted=0" ) ) { 5064 echo S_SQLFAIL; 5065 } 5066 $countimgres = mysql_result( $result, 0, 0 ); 5067 if( $countimgres >= MAX_IMGRES && !has_level() ) error(S_MAXIMAGESREACHED, $upfile ); 5068 mysql_free_result( $result ); 5069 } 5070 5071 //upload processing 5072 $dest = $upfile; 5073 5074 // TODO: what does that preg_replace do? those are probably utf8 codes 5075 $upfile_name = sanitize_text( preg_replace('/\xe2\x80(\xae|\xad|\x8f|\x8e)/', '', $upfile_name) ); 5076 5077 if (!is_file($dest)) { 5078 error(S_FAILEDUPLOAD, $dest); 5079 } 5080 5081 // Use filesize() later as it's possible to trick jpegtrans to generate a much bigger file 5082 $fsize = $_FILES['upfile']['size']; 5083 5084 if (!$fsize || $fsize > MAX_KB * 1024) { 5085 error( S_TOOLARGE, $dest ); 5086 } 5087 5088 $webm_sar = null; 5089 5090 // PDF processing 5091 if( ENABLE_PDF == 1 && strcasecmp( '.pdf', substr( $upfile_name, -4 ) ) == 0 ) { 5092 $ext = '.pdf'; 5093 $W = $H = 1; 5094 // run through ghostscript to check for validity 5095 if( pclose( popen( "/usr/local/bin/gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage $dest", 'w' ) ) ) { 5096 error( S_FAILEDUPLOAD, $dest ); 5097 } 5098 } 5099 // Webm / MP4 5100 else if (ENABLE_WEBM && preg_match('/\.(webm|mp4)$/i', $upfile_name)) { 5101 if ($fsize > MAX_WEBM_FILESIZE * 1024) { 5102 error(S_TOOLARGE, $dest); 5103 } 5104 5105 $original_md5 = md5_file($dest); 5106 5107 $ext = '.' . strtolower(pathinfo($upfile_name, PATHINFO_EXTENSION)); 5108 5109 //if ($ext == '.mp4' && BOARD_DIR != 'test') { 5110 // error(S_NOREC, $dest); 5111 //} 5112 5113 $size = validate_webm($dest, $ext); 5114 $W = $size[0]; 5115 $H = $size[1]; 5116 $webm_sar = $size[2]; 5117 } 5118 // PNG, GIF, JPEG 5119 else { 5120 $size = getimagesize( $dest ); 5121 if( !is_array( $size ) ) { 5122 quick_log_to( "/www/perhost/bad-upload.log", "unrecognized file $upfile_name"); 5123 5124 error( S_NOREC, $dest ); 5125 } 5126 5127 $W = $size[0]; 5128 $H = $size[1]; 5129 switch( $size[2] ) { 5130 case 1 : 5131 $ext = ".gif"; 5132 break; 5133 case 2 : 5134 $ext = ".jpg"; 5135 break; 5136 case 3 : 5137 $ext = ".png"; 5138 break; 5139 case 4 : 5140 $ext = ".swf"; 5141 error( S_FAILEDUPLOAD, $dest ); 5142 break; 5143 case 5 : 5144 $ext = ".psd"; 5145 error( S_FAILEDUPLOAD, $dest ); 5146 break; 5147 case 6 : 5148 $ext = ".bmp"; 5149 error( S_FAILEDUPLOAD, $dest ); 5150 break; 5151 case 7 : 5152 $ext = ".tiff"; 5153 error( S_FAILEDUPLOAD, $dest ); 5154 break; 5155 case 8 : 5156 $ext = ".tiff"; 5157 error( S_FAILEDUPLOAD, $dest ); 5158 break; 5159 case 9 : 5160 $ext = ".jpc"; 5161 error( S_FAILEDUPLOAD, $dest ); 5162 break; 5163 case 10 : 5164 $ext = ".jp2"; 5165 error( S_FAILEDUPLOAD, $dest ); 5166 break; 5167 case 11 : 5168 $ext = ".jpx"; 5169 error( S_FAILEDUPLOAD, $dest ); 5170 break; 5171 case 13 : 5172 $ext = ".swf"; 5173 error( S_FAILEDUPLOAD, $dest ); 5174 break; 5175 default : 5176 $ext = ".xxx"; 5177 error( S_FAILEDUPLOAD, $dest ); 5178 break; 5179 } 5180 if (GIF_ONLY == 1 && $size[2] != 1 && $ext != '.webm') error(S_FAILEDUPLOAD, $dest); 5181 } // end PDF processing -else 5182 5183 // This doesn't seem to use the $md5 arg 5184 if ($upfile_name === '') { 5185 error('Blank file names are not supported.', $dest); 5186 } 5187 5188 time_log( "sfpi" ); 5189 rpc_task(); 5190 5191 // Picture reduction 5192 if( !$resto ) { 5193 $maxw = MAX_W; 5194 $maxh = MAX_H; 5195 } else { 5196 $maxw = MAXR_W; 5197 $maxh = MAXR_H; 5198 } 5199 if( defined( 'MIN_W' ) && MIN_W > $W ) error( S_TOOSMALL, $dest ); 5200 if( defined( 'MIN_H' ) && MIN_H > $H ) error( S_TOOSMALL, $dest ); 5201 if( defined( 'MAX_DIMENSION' ) ) 5202 $maxdimension = MAX_DIMENSION; 5203 else 5204 $maxdimension = 5000; 5205 if( $W > $maxdimension || $H > $maxdimension ) { 5206 error( S_TOOLARGERES, $dest ); 5207 } elseif( $W > $maxw || $H > $maxh ) { 5208 $W2 = $maxw / $W; 5209 $H2 = $maxh / $H; 5210 ( $W2 < $H2 ) ? $key = $W2 : $key = $H2; 5211 $TN_W = ceil( $W * $key ) + 1; 5212 $TN_H = ceil( $H * $key ) + 1; 5213 } 5214 5215 // Strip metadata, exif, comments and other embeddded extra data 5216 if ($ext === '.jpg') { 5217 // Embed detection is done later below if STRIP_EXIF is disabled 5218 if (STRIP_EXIF) { 5219 $original_md5 = md5_file($dest); 5220 5221 if (strip_jpeg_exif($dest) === false) { 5222 error(S_FAILEDUPLOAD, $dest); 5223 } 5224 5225 clearstatcache(true, $dest); 5226 } 5227 } 5228 else if ($ext === '.png') { 5229 $original_md5 = md5_file($dest); 5230 $_ret = strip_png_chunks($dest, MAX_KB * 1024); 5231 if ($_ret < 0) { 5232 if ($_ret === -2) { 5233 error('APNG format not supported.', $dest); 5234 } 5235 else { 5236 error(S_FAILEDUPLOAD, $dest); 5237 } 5238 } 5239 else if ($_ret > 0) { 5240 clearstatcache(true, $dest); 5241 } 5242 } 5243 else if ($ext === '.gif') { 5244 $original_md5 = md5_file($dest); 5245 $_ret = strip_gif_extra_data($dest, $fsize); 5246 if ($_ret < 0) { 5247 error(S_FAILEDUPLOAD, $dest); 5248 } 5249 clearstatcache(true, $dest); 5250 } 5251 5252 // It should be safe to check the filesize now 5253 // clearstatcache() must be called if the file was modified 5254 $fsize = filesize($dest); 5255 5256 if (!$fsize) { 5257 error(S_TOOLARGE, $dest); 5258 } 5259 else if ($ext === '.webm' && $fsize > MAX_WEBM_FILESIZE * 1024) { 5260 error(S_TOOLARGE, $dest); 5261 } 5262 else if ($fsize > MAX_KB * 1024) { 5263 error(S_TOOLARGE, $dest); 5264 } 5265 5266 // Check for JPEG embedded data. jpegtran seems to remove unknown data 5267 // so this only needs to be done if STRIP_EXIF is disabled 5268 if (!STRIP_EXIF && $ext === '.jpg' && $fsize > 204800) { 5269 validate_jpeg_size($dest, $fsize); 5270 } 5271 } 5272 5273 $insfile = preg_replace('/\.[a-z0-9]+$/i', '', $upfile_name); 5274 5275 $md5 = md5_file( $dest ); 5276 $mes = $upfile_name . ' ' . S_UPGOOD; 5277 } 5278 5279 if( $_FILES["upfile"]["error"] > 0 ) { 5280 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_INI_SIZE ) 5281 error( S_TOOLARGE, $dest ); 5282 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_FORM_SIZE ) 5283 error( S_TOOLARGE, $dest ); 5284 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_PARTIAL ) 5285 error( S_FAILEDUPLOAD, $dest ); 5286 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_CANT_WRITE ) 5287 error( S_FAILEDUPLOAD, $dest ); 5288 } 5289 5290 if( $upfile_name && $_FILES["upfile"]["size"] == 0 ) { 5291 error( S_TOOLARGEORNONE, $dest ); 5292 } 5293 5294 if( ENABLE_EXIF == 1 ) { 5295 $exif = htmlspecialchars( shell_exec( "/usr/local/bin/exiftags $dest" ) ); 5296 } 5297 5298 $resto = (int)$resto; 5299 if( $resto ) { 5300 if( !mysql_result( mysql_board_call( "select count(no) from `" . SQLLOG . "` where root>0 and no=$resto" ), 0, 0 ) ) 5301 error( S_NOTHREADERR, $dest ); 5302 } 5303 5304 $pass_is_bannable = !$userpwd->isNew(); 5305 5306 // Most common errors checked, now check for post-block from ban requests 5307 if (BLOCK_ON_BR && !has_level()) { 5308 check_for_ban_request($host, $pass_is_bannable ? $pass : null); 5309 } 5310 5311 // Standardize new character lines 5312 $com = str_replace( "\r\n", "\n", $com ); 5313 $com = str_replace( "\r", "\n", $com ); 5314 5315 $comlim = has_level() ? MAX_COM_CHARS_AUTHED : MAX_COM_CHARS; 5316 $longlim = has_level() ? 255 : 100; 5317 5318 if( mb_strlen( $com ) > $comlim ) error( S_TOOLONG, $dest ); 5319 if( strlen( $name ) > $longlim ) error( S_TOOLONG, $dest ); 5320 if( strlen( $email ) > $longlim ) error( S_TOOLONG, $dest ); 5321 if( strlen( $sub ) > $longlim ) error( S_TOOLONG, $dest ); 5322 if( strlen( $resto ) > 10 ) error( S_GENERICERROR, $dest ); 5323 if( strlen( $url ) > 10 ) error( S_GENERICERROR, $dest ); 5324 5325 $sub = normalize_content( $sub ); 5326 5327 // start of some attempt to get rid of *all* zero width bollocks 5328 if( BOARD_DIR != 'jp' && BOARD_DIR != 'a' && !SJIS_TAGS) { 5329 $com = normalize_content( $com ); 5330 $com = strip_zerowidth( $com ); 5331 } 5332 5333 // strip no break spaces and soft hyphens 5334 $com = str_replace(array("\xC2\xAD", "\xC2\xA0"), '', $com); 5335 5336 // name/subject too! 5337 $sub = strip_zerowidth( $sub ); 5338 $name = strip_zerowidth( $name ); 5339 5340 // Strip unicode emoticons 5341 $name = strip_emoticons($name, SJIS_TAGS); 5342 5343 if ($sub !== '') { 5344 $sub = strip_emoticons($sub, SJIS_TAGS); 5345 } 5346 5347 if ($com !== '') { 5348 $com = strip_emoticons($com, SJIS_TAGS); 5349 } 5350 5351 // strip out ltr junk from name 5352 $name = preg_replace( '#([\x{2000}-\x{200F}]|[\x{2028}-\x{202F}])#u', '', $name ); 5353 5354 if ($sub !== '') { 5355 $sub = strip_fake_capcodes($sub); 5356 } 5357 5358 if( !strlen( $name ) || preg_match( "/^[ | |]*$/", $name ) ) $name = ""; 5359 if( !strlen( $com ) || preg_match( "/^[ | |\t]*$/", $com ) ) $com = ""; 5360 if( !strlen( $sub ) || preg_match( "/^[ | |]*$/", $sub ) ) $sub = ""; 5361 5362 //$name = str_replace( S_MANAGEMENT, "\"" . S_MANAGEMENT . "\"", $name ); 5363 //$name = str_replace( S_DELETION, "\"" . S_DELETION . "\"", $name ); 5364 5365 // Remove intra spoilers 5366 if (SPOILERS && stripos($com, '[spoiler]') !== false) { 5367 //$com = preg_replace( '/\[spoiler\]\s+\[\/spoiler\]/', '', $com ); 5368 $com = preg_replace('/(\S)\[spoiler\](.*?)\[\/spoiler\](\S)/', '\\1\\2\\3', $com); 5369 } 5370 5371 //lol /b/ 5372 $xff = get_request_xff(); 5373 //if( is_bad_xff( $xff ) ) $xff = ""; 5374 5375 $youbi = array(S_SUN, S_MON, S_TUE, S_WED, S_THU, S_FRI, S_SAT); 5376 $yd = $youbi[date( "w", $time )]; 5377 if( SHOW_SECONDS == 1 ) { 5378 $now = date( "m/d/y", $time ) . "(" . (string)$yd . ")" . date( "H:i:s", $time ); 5379 } else { 5380 $now = date( "m/d/y", $time ) . "(" . (string)$yd . ")" . date( "H:i", $time ); 5381 } 5382 5383 $c_name = $name; 5384 $c_email = $email; 5385 5386 if (JANITOR_BOARD == 1) { 5387 $name = get_hashed_mod_name($_COOKIE['4chan_auser']); 5388 $email = ''; 5389 } 5390 5391 // April 2023 5392 //$_has_xa23_content = $com && preg_match('/^[^>]{8,}/m', $com) > 0; 5393 5394 $com = preg_replace( '#>>>/' . BOARD_DIR . '/([0-9]+)#', '>>$1', $com ); 5395 5396 $sub = sanitize_text( $sub ); 5397 $sub = preg_replace( "/[\r\n]/", "", $sub ); 5398 $sub = strip_private_unicode($sub); 5399 $url = sanitize_text( $url ); 5400 $url = preg_replace( "/[\r\n]/", "", $url ); 5401 $resto = sanitize_text( $resto ); 5402 $resto = preg_replace( "/[\r\n]/", "", $resto ); 5403 $com = sanitize_text( $com, 1, true ); 5404 $com = strip_private_unicode($com); 5405 5406 if( FORCED_ANON == 1 ) { 5407 if( !has_level('admin') ) $name = S_ANONAME; 5408 $sub = ''; 5409 } 5410 5411 if (UPLOAD_BOARD) { 5412 if( NO_TEXTONLY == 1 ) { 5413 if( !$resto && !$has_image ) error( S_NOPIC, $dest ); 5414 } else { 5415 if( !$resto && !$textonly && !$has_image ) error( S_NOPIC, $dest ); 5416 } 5417 } else { 5418 if (!TEXT_ONLY) { 5419 if( NO_TEXTONLY == 1 && (!has_level() || $email === '') ) { 5420 if( !$resto && !$has_image ) error( S_NOPIC, $dest ); 5421 } else { 5422 if( !$resto && !$textonly && !$has_image ) error( S_NOPIC, $dest ); 5423 } 5424 } 5425 5426 if( REQUIRE_SUBJECT && !$resto && !strlen( $sub ) ) error( S_NOSUB, $dest ); 5427 } 5428 // Check for sage, nonoko and nonokosage 5429 $is_sage = false; 5430 5431 if (stripos($email, 'sage') !== false) { 5432 $is_sage = true; 5433 $email = str_ireplace('sage', '', $email); 5434 } 5435 5436 $email_lower = strtolower($email); 5437 5438 if ($email_lower === 'nonoko') { 5439 $is_nonoko = true; 5440 } 5441 5442 if( SPOILERS == 1 && $spoiler ) { 5443 $sub = "SPOILER<>$sub"; 5444 } 5445 5446 if( !has_level() ) { 5447 $match = array(); 5448 if( substr_count( $com, "\n" ) > 6 ) preg_match_all( '#([^\n]+\n+)\1{5,}#', $com, $match ); 5449 if( !empty( $match[0] ) ) { 5450 foreach( $match[1] as $key => $var ) { 5451 //auto_ban_poster( $name, 0, 1, 'Matched the same string 5 times or more in 1 post seperated by newlines (<b>Matched:</b> ' . $var . ')', 'Please do not spam.' ); 5452 error( S_REJECTTEXTBAN ); 5453 } 5454 } 5455 } 5456 5457 // FIXME sanitize_text() replaces repeated \n too, is this duplicate? 5458 // disable on code tag boards (we replace multiple brs instead) 5459 if (!CODE_TAGS && !SJIS_TAGS) { 5460 $com = preg_replace( "/\n(( | )*\n){3,}/", "\n", $com ); 5461 } 5462 5463 if( !has_level() && substr_count( $com, "\n" ) > MAX_LINES ) error(S_TOOMANYLINES, $dest ); 5464 5465 if( ENABLE_EXIF == 1 && $exif ) { 5466 //turn exif into a table 5467 $exiflines = explode( "\n", $exif ); 5468 $exif = "<table class=\"exif\" id=\"exif$tim\">"; 5469 foreach( $exiflines as $exifline ) { 5470 list( $exiftag, $exifvalue ) = explode( ': ', $exifline ); 5471 if( $exifvalue != '' ) 5472 $exif .= "<tr><td>$exiftag</td><td>$exifvalue</td></tr>"; 5473 else 5474 $exif .= "<tr><td colspan=\"2\"><b>$exiftag</b></td></tr>"; 5475 } 5476 $exif .= '</table>'; 5477 $exiftext .= '<br><br><span class="abbr">' . sprintf(S_EXIF_TOGGLE, $tim) . '</span><br>'; 5478 $exiftext .= "$exif"; 5479 } 5480 5481 $name = preg_replace( "/[\r\n]/", "", $name ); 5482 5483 $names = mb_convert_encoding($name, 'CP932', 'UTF-8'); // convert to Windows Japanese #kami 5484 5485 //start new tripcode crap 5486 list ( $name ) = explode( "#", $name ); 5487 5488 // Strip unicode point-of-interest and # characters 5489 if (preg_match('/[\x{2318}\x{ff03}\x{FE5F}]/u', $name)) { 5490 $name = preg_replace('/[\x{2318}\x{ff03}\x{FE5F}]/u', '', $name); 5491 } 5492 5493 $name = normalize_content( $name ); 5494 $name = sanitize_text( $name ); 5495 $name = strip_private_unicode($name); 5496 5497 if (preg_match('/^\s+$/', $name)) { 5498 $name = ''; 5499 } 5500 5501 $name = str_replace('!', '', $name); 5502 5503 if( preg_match( "/\#+$/", $names ) ) { 5504 $names = preg_replace( "/\#+$/", "", $names ); 5505 } 5506 if( preg_match( "/\#/", $names ) ) { 5507 5508 $names = str_replace( "&#", "&&", htmlspecialchars( $names, ENT_COMPAT | ENT_HTML401, 'Shift_JIS' ) ); # otherwise HTML numeric entities screw up explode()! 5509 5510 list ( $nametemp, $trip, $sectrip ) = str_replace( "&&", "&#", explode( "#", $names, 3 ) ); 5511 5512 if ($sectrip != '') { 5513 $trip = ''; 5514 } 5515 5516 $names = $nametemp; 5517 if( STRIP_TRIPCODE == 0 ) $name .= "</span>"; 5518 5519 if ($trip != "" && STRIP_TRIPCODE == 0) { 5520 $salt = strtr( preg_replace( "/[^\.-z]/", ".", substr( $trip . "H.", 1, 2 ) ), ":;<=>?@[\\]^_`", "ABCDEFGabcdef" ); 5521 $trip = substr( crypt( $trip, $salt ), -10 ); 5522 $name .= " <span class=\"postertrip\">!" . $trip; 5523 } 5524 5525 if( $sectrip != "" && STRIP_TRIPCODE == 0 ) { 5526 $salt = file_get_contents_cached( SALTFILE ); 5527 $sha = base64_encode( pack( "H*", sha1( $sectrip . $salt ) ) ); 5528 $sha = substr( $sha, 0, 11 ); 5529 $name .= " <span class=\"postertrip\">!!" . $sha; 5530 } 5531 } //end new tripcode crap 5532 5533 // Check the length of the name field again to prevent truncation in the middle of tripcode HTML 5534 if (strlen($name) > 255) { 5535 error(S_TOOLONG, $dest); 5536 } 5537 5538 //Cookies 5539 $cookie_domain = '.' . L::d(BOARD_DIR); 5540 setrawcookie( "4chan_name", rawurlencode( $c_name ), $time + ( $c_name ? ( 7 * 24 * 3600 ) : -3600 ), '/', $cookie_domain ); 5541 5542 if( !strlen( $name ) ) $name = S_ANONAME; 5543 if( !strlen( $com ) ) $com = S_ANOTEXT; 5544 if( !strlen( $sub ) ) $sub = S_ANOTITLE; 5545 5546 /* since4pass */ 5547 if ($captcha_bypass && $passid && $email && strpos(" $email ", ' since4pass ') !== false) { 5548 $since4pass = get_since_4chan($passid); 5549 } 5550 else { 5551 $since4pass = 0; 5552 } 5553 5554 // April 2024 5555 /* 5556 if ($email) { 5557 $_xa24_since4pass = april_2024_parse_email($email); 5558 $since4pass = $_xa24_since4pass; 5559 } 5560 else { 5561 $_xa24_since4pass = false; 5562 } 5563 */ 5564 if (FORTUNE_TRIP == 1 && $email == 'fortune') { 5565 $fortunes = array("Bad Luck", "Average Luck", "Good Luck", "Excellent Luck", "Reply hazy, try again", "Godly Luck", "Very Bad Luck", "Outlook good", "Better not tell you now", "You will meet a dark handsome stranger", "キタ━━━━━━(゚∀゚)━━━━━━ !!!!", "( ´_ゝ`)フーン ", "Good news will come to you by mail"); 5566 // Christmas 2021 5567 /* 5568 $fortunes = array("You're on the nice list!", "You're on the naughty list!", "Krampus is coming to your house!", "It's going to be a white Christmas!", "Merry Christmas!", "Happy Hanukkah!", "Happy Kwanzaa!", "Feliz Navidad!", "Happy Festivus!", "You're getting a lump of coal in your stocking!", "Blessed Yule!", "You're standing under the mistletoe!", "The poster above you has been very very naughty!", "You got an extra thick slice of fruitcake.", "You're on the Elf Watchlist.", "Your heart is two sizes too small.", "Your heart grew three sizes!", "Bah! Humbug."); 5569 */ 5570 $fortunenum = rand( 0, sizeof( $fortunes ) - 1 ); 5571 $fortcol = "#" . sprintf( "%02x%02x%02x", 5572 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) ), 5573 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) + 2 / 3 * M_PI ), 5574 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) + 4 / 3 * M_PI ) ); 5575 $com .= "<span class=\"fortune\" style=\"color:$fortcol\"><br><br><b>Your fortune: " . $fortunes[$fortunenum] . "</b></span>"; 5576 } 5577 5578 if (DICE_ROLL == 1) { 5579 if( $email ) { 5580 if( preg_match( "/dice[ +](\\d+)[ d+](\\d+)(([ +-]+?)(-?\\d+))?/", $email, $match ) ) { 5581 $dicetxt = S_DICE_PFX . ' '; 5582 $dicenum = min( 25, $match[1] ); 5583 $diceside = $match[2]; 5584 $diceaddexpr = $match[3]; 5585 $dicesign = $match[4]; 5586 $diceadd = intval( $match[5] ); 5587 5588 for( $i = 0; $i < $dicenum; $i++ ) { 5589 $dicerand = mt_rand( 1, $diceside ); 5590 if( $i ) $dicetxt .= ", "; 5591 $dicetxt .= $dicerand; 5592 $dicesum += $dicerand; 5593 } 5594 5595 if( $diceaddexpr ) { 5596 if( strpos( $dicesign, "-" ) > 0 ) $diceadd *= -1; 5597 $diceadd_formatted = ( $diceadd >= 0 ? " + " : " - " ) . abs( $diceadd ); 5598 $dicetxt .= $diceadd_formatted; 5599 $dicesum += $diceadd; 5600 } 5601 5602 if ($dicenum > 1) { 5603 $dicetxt .= " = $dicesum"; 5604 } 5605 5606 $dicetxt .= " ({$dicenum}d{$diceside}" . ($diceaddexpr ? $diceadd_formatted : "") . ")<br><br>"; 5607 5608 $com = "<b>$dicetxt</b>" . $com; 5609 } 5610 } 5611 } 5612 5613 // fixme: this is needed for bypassing the r9k filter. $email gets reset below. 5614 $options_field = $email; 5615 5616 $emails = $email; 5617 $admin_highlight = false; 5618 $uid = null; 5619 $capcode = 'none'; 5620 $delay_refresh = false; 5621 if (strpos($email, 'capcode_') === 0) { 5622 // are we trying to capcode? 5623 if (!has_level('admin') && !has_flag('capcodename')) { 5624 $name = S_ANONAME; 5625 } 5626 // Only pass and authed users can use VIP capcodes 5627 if ($captcha_bypass) { 5628 $capcode = parse_capcode($email, $name); 5629 } 5630 else { 5631 $capcode = parse_capcode($email); 5632 } 5633 5634 $ma = ( $capcode == 'admin_highlight') ? 'admin' : $capcode; 5635 $ma = ucfirst( $ma ); 5636 5637 if( DISP_ID == 1 && $ma != 'None' ) { 5638 $uid = $ma; 5639 } 5640 5641 if( $ma != 'None' && STRIP_EXIF_ON_CAPCODE && $ext == ".jpg" ) { 5642 system( "/usr/local/bin/jpegtran -copy none -outfile '$dest' '$dest'" ); 5643 $md5 = md5_file( $dest ); 5644 } 5645 5646 if ($capcode !== 'none') { 5647 $log_mod_action = $log_capcode_post = true; 5648 } 5649 5650 setcookie('options', $email, $time + (7 * 24 * 3600), '/', $cookie_domain); 5651 } 5652 else if (isset($_COOKIE['options'])) { 5653 setcookie('options', null, $time -3600, '/', $cookie_domain); 5654 } 5655 5656 $email = ''; 5657 5658 $nameparts = explode( '</span> <span class="postertrip">!', $name ); 5659 5660 time_log( "trip" ); 5661 5662 //logtime( "starting autoban checks" ); 5663 5664 /** 5665 * Ban check step 5666 */ 5667 $ban_fields = array(); 5668 5669 if ($pass_is_bannable) { 5670 $ban_fields['password'] = $pass; 5671 } 5672 /* 5673 if ($nameparts[1]) { 5674 $ban_fields['tripcode'] = $nameparts[1]; 5675 } 5676 */ 5677 if ($passid) { 5678 $ban_fields['4pass_id'] = $passid; 5679 } 5680 5681 $user_is_banned = check_for_ban($host, $ban_fields, $resto, $userpwd && $userpwd->verifiedLevel()); 5682 5683 if ($user_is_banned) { 5684 if (!$captcha_bypass) { 5685 mysql_global_call("INSERT INTO user_actions (board,ip,time,action) VALUES ('%s',%d,from_unixtime(%d),'%s')", BOARD_DIR, ip2long($host), $time, 'is_banned'); 5686 } 5687 5688 // Log banned phone ips 5689 if ($userpwd && $userpwd->isUserKnownOrVerified(10080) && $userpwd->postCount() > 20) { 5690 if (preg_match('/Android|iPhone/', $_SERVER['HTTP_USER_AGENT'])) { 5691 log_spam_filter_trigger('log_mobile_misban', BOARD_DIR, $resto, $host, 1); 5692 } 5693 } 5694 5695 $redirect = 'https://www.' . L::d(BOARD_DIR) . '/banned'; 5696 5697 if ($user_is_banned == 1) { 5698 // Banned 5699 error_redirect(S_BANNED, $redirect); 5700 } 5701 else if ($user_is_banned == 2) { 5702 // Warned 5703 error_redirect(S_WARNED, $redirect); 5704 } 5705 else { 5706 // Ban evasion 5707 error("Error: $user_is_banned"); 5708 } 5709 } 5710 5711 /** 5712 * Validate the maximum number of allowed threads per user 5713 */ 5714 if (!$resto) { 5715 validate_user_thread_limit( 5716 $_SERVER['REMOTE_ADDR'], 5717 isset($ban_fields['password']) ? $pass : null, 5718 $passid 5719 ); 5720 } 5721 5722 /* 5723 * Embedded data detection and banned re-post block 5724 */ 5725 if ($has_image) { 5726 // Check if the file contains embedded data. 5727 if (false && CLEANUP_UPLOADS) { 5728 // Update the size if the file was modified 5729 if (cleanup_uploaded_file($dest, $ext)) { 5730 clearstatcache(true, $dest); 5731 $fsize = filesize($dest); 5732 if (!$fsize) { 5733 error(S_TOOLARGE, $dest); 5734 } 5735 $md5 = md5_file($dest); 5736 } 5737 } 5738 5739 // Check if the uploaded file should be blocked because of previous bans 5740 if (check_for_banned_upload($md5)) { 5741 //log_spam_filter_trigger('block_banned_reupload', BOARD_DIR, $resto, $host, 1, $md5); 5742 error(S_FAILEDUPLOAD, $dest); 5743 } 5744 } 5745 5746 // --- 5747 5748 $autosage = false; 5749 5750 // See bellow wordwrap2() 5751 if (strpos($com, 'rep?~') !== false) { 5752 $com = str_replace(array('~?rep?~', '~?erep?~'), '', $com); 5753 } 5754 5755 if (!has_level() || $capcode === 'none' || BOARD_DIR == 'test') { 5756 $autosage = spam_filter_post_content_new(BOARD_DIR, $resto, $com, $sub, $name, $upfile_name, $pass, ($captcha_bypass && $passid) ? $passid : null); 5757 spam_filter_post_ip($userpwd, $resto, $has_image); 5758 } 5759 5760 if (!$capcode || $capcode == 'none') { 5761 check_md5_blacklist($md5, $original_md5, [ 5762 'resto' => $resto, 5763 'name' => $nameparts[0], 5764 'tripcode' => $nameparts[1], 5765 'filename' => "$insfile$ext", 5766 'password' => $pass, 5767 '4pass_id' => ($captcha_bypass && $passid) ? $passid : null 5768 ], $dest); 5769 } 5770 5771 spam_filter_post_trip( $name, $trip, $dest ); 5772 5773 time_log( "ab" ); 5774 5775 // Only process linebreaks for non-html posts 5776 if (!$log_html_post) { 5777 $com = nl2br($com, false); 5778 $com = str_replace("\n", "", $com); //\n is erased 5779 } 5780 5781 $com .= $exiftext; // must be done after spam filter, since it has a javascript: link 5782 5783 if (SJIS_TAGS) { 5784 $com = sjis_parse($com); 5785 } 5786 5787 if( SPOILERS == 1 ) { 5788 $com = spoiler_parse( $com ); 5789 if (stripos( $com, '<s>') !== false) { 5790 $com = preg_replace('/<s>(\s|<br>|(?R))*<\/s>/', '', $com); 5791 //$com = preg_replace('/(\S)<s>(.*?)<\/s>(\S)/', '\\1\\2\\3', $com); 5792 } 5793 } 5794 /* 5795 if( JSMATH == 1 ) { 5796 $com = jsmath_parse( $com ); 5797 } 5798 */ 5799 if( CODE_TAGS ) { 5800 $com = preg_replace( '#(\[code\](.{0,6})\[\/code\])#', '\\2', $com ); 5801 5802 $com = code_parse( $com ); 5803 5804 $com = str_replace( '<pre class="prettyprint"><br>', '<pre class="prettyprint">', $com ); 5805 $com = preg_replace( '#(<br>){4,}#', '<br><br><br>', $com ); 5806 } 5807 5808 if (OP_MARKUP && (!$resto || is_poster_op($host, $pass, $resto))) { 5809 $com = parse_op_markup($com); 5810 } 5811 5812 // pull this down to here to get rid of any shenanigans 5813 if (!$resto) { // new threads require subject or comment 5814 if ($sub === '' && ($com === '' || preg_match('/^(?:<br>|\s)+$/', $com))) { 5815 if ($options_field === '' || !$has_image || !has_level()) { 5816 error(S_NOTEXT_OP, $dest); 5817 } 5818 } 5819 else if (TEXT_ONLY && $sub === '') { 5820 error(S_NOSUB, $dest); 5821 } 5822 } 5823 else if (!$has_image && ($com === '' || preg_match('/^(?:<br>|\s)+$/', $com))) { // replies without image 5824 error(S_NOTEXT, $dest); 5825 } 5826 5827 if( WORD_FILT && $word_filters_enabled ) { 5828 $com = word_filter( $com, "com" ); 5829 if( $sub ) 5830 $sub = word_filter( $sub, "sub" ); 5831 $namearr = explode( '</span> <span class="postertrip">', $name ); 5832 if( strstr( $name, '</span> <span class="postertrip">' ) ) { 5833 $nametrip = '</span> <span class="postertrip">' . $namearr[1]; 5834 } else { 5835 $nametrip = ""; 5836 } 5837 if( $namearr[0] != S_ANONAME ) 5838 $name = word_filter( $namearr[0], "name" ) . $nametrip; 5839 } 5840 5841 /*if( $html != 1 || ( !has_level('manager') ) ) { 5842 $com = wordwrap2( $com, 35, "{{w_br}}" ); 5843 }*/ 5844 5845 // April 2022 5846 /* 5847 if (strpos($com, ':') !== false) { 5848 $com = april_process_post_emotes($com, $_COOKIE['xa_sid']); 5849 } 5850 */ 5851 $com = normalize_and_linkify($com); 5852 5853 //$html = isset( $_POST['html'] ) && $_POST['html'] == 1; 5854 $html = 0; 5855 if( (!has_level('manager') && !has_flag('html')) || $html != 1 ) { 5856 $com = wordwrap2( $com, 35, "{{w_br}}" ); 5857 } 5858 5859 $com = preg_replace( '#(>>>/[a-z0-9]+/[^ <$]*|>>[0-9]+)#', '~?rep?~\\1~?erep?~', $com ); 5860 $com = preg_replace( "!(^|r>|r> )(>[^<]*)!", "\\1<span class=\"quote\">\\2</span>", $com ); 5861 $com = preg_replace( '#~?rep?~<span class="quote">(.+?)</span>~?erep?~#', '\\1', $com ); 5862 5863 $com = str_replace( array( '~?rep?~', '~?erep?~' ), '', $com ); 5864 5865 $com = str_replace( '{{w_br}}', '<wbr>', $com ); 5866 5867 $admin_style = "padding: 5px;margin-left: .5em;border-color: #faa;border: 2px dashed rgba(255,0,0,.1);border-radius: 2px"; //FIXME make it easier to edit css so this can go in it 5868 if( $admin_highlight ) { 5869 $com = "<div style=\"$admin_style\">$com</div>"; 5870 } 5871 5872 if( DISP_ID == 1 && !$uid ) { 5873 $uid = $is_sage && !META_BOARD && !DISP_ID_NO_HEAVEN ? "Heaven" : generate_uid( $resto, $time ); 5874 } 5875 5876 // Validate comment for OPs by regex 5877 if (!$resto && COM_REGEX) { 5878 if (preg_match(COM_REGEX, $com) === 0) { 5879 error(S_INVALID_COM); 5880 } 5881 } 5882 5883 //post text is now completely created, thumbnail not 5884 5885 if( !$silent_reject ) { 5886 //logtime( "Before flood check" ); 5887 $may_flood = has_level( 'janitor' ); 5888 5889 if (!$may_flood || (!has_level() && (META_BOARD || $_POST['name'] != ''))) { 5890 if( $com ) { 5891 // Check for duplicate comments 5892 $query = "select sql_no_cache max(time) from `%s` where com='%s' " . 5893 "and host='%s' " . 5894 "and time>%d"; 5895 $result = mysql_board_call( $query, SQLLOG, $com, $host, $time - RENZOKU_DUPE ); 5896 if( $ltime = mysql_result( $result, 0, 0 ) ) { 5897 //check_fail_floodcheck($com); 5898 $str = sprintf(S_RENZOKU_DUP, sec2hms( ( $ltime + RENZOKU_DUPE ) - $time, false, true ) ); 5899 error( $str, $dest ); 5900 } 5901 mysql_free_result( $result ); 5902 } 5903 5904 /** 5905 * Posting cooldowns 5906 */ 5907 if (!$resto) { 5908 /** 5909 * New threads 5910 */ 5911 $query = "select max(time) from `%s` where time>%d " . 5912 "and host='%s' and root>0"; //root>0 == non-sticky 5913 $result = mysql_board_call( $query, SQLLOG, ( $time - RENZOKU3 ), $host ); 5914 if( $ltime = mysql_result( $result, 0, 0 ) ) { 5915 $str = sprintf(S_RENZOKU3, sec2hms( ( $ltime + RENZOKU3 ) - $time, false, true ) ); 5916 error( $str, $dest ); 5917 } 5918 mysql_free_result( $result ); 5919 // Cross-board cooldown 5920 $query = "SELECT 1 FROM user_actions WHERE ip = %d AND action = 'new_thread' AND board != '%s' AND time >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)"; 5921 $result = mysql_global_call($query, ip2long($host), BOARD_DIR); 5922 if (mysql_num_rows($result) > 0) { 5923 error( S_RENZOKU3, $dest ); // You must wait longer before posting another thread 5924 } 5925 } 5926 else { 5927 /** 5928 * Replies 5929 * Pass users have lower cooldowns 5930 */ 5931 5932 // Check for same image flood first 5933 if ($has_image && $resto) { 5934 $query = "SELECT time FROM `%s` WHERE host = '%s' AND md5 = '%s' AND resto != %d ORDER BY no DESC LIMIT 1"; 5935 5936 $result = mysql_board_call($query, SQLLOG, $host, $md5, $resto); 5937 5938 if ($flood_row = mysql_fetch_assoc($result)) { 5939 $last_time = (int)$flood_row['time']; 5940 5941 $cooldown = RENZOKU_DUPE; 5942 $cooldown_error = S_RENZOKU2_DUP; 5943 5944 if ($captcha_bypass) { 5945 $cooldown = ceil((int)$cooldown / 2); 5946 } 5947 else { 5948 $cooldown_error .= S_RENZOKU_PASS; 5949 } 5950 5951 if ($last_time > $time - $cooldown) { 5952 error(sprintf($cooldown_error, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 5953 } 5954 } 5955 } 5956 5957 // Now the standard cooldown 5958 $query = "SELECT time, resto, fsize FROM `%s` WHERE host = '%s' AND resto > 0 ORDER BY no DESC LIMIT 1"; 5959 5960 $result = mysql_board_call($query, SQLLOG, $host); 5961 5962 if ($flood_row = mysql_fetch_assoc($result)) { 5963 $last_time = (int)$flood_row['time']; 5964 5965 if ($has_image) { 5966 $cooldown = RENZOKU2; 5967 $cooldown_error = S_RENZOKU2; 5968 } 5969 else { 5970 $cooldown = RENZOKU; 5971 $cooldown_error = S_RENZOKU; 5972 } 5973 5974 if ($captcha_bypass) { 5975 $cooldown = ceil((int)$cooldown / 2); 5976 } 5977 else { 5978 $cooldown_error .= S_RENZOKU_PASS; 5979 } 5980 5981 if ($last_time > $time - $cooldown) { 5982 error(sprintf($cooldown_error, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 5983 } 5984 } 5985 } 5986 /* 5987 if (SAVE_XFF == 1 && $xff) { 5988 // Check for multiple ips with same xff 5989 $result = mysql_global_call( "select count(distinct ip)>2 from xff where xff='%s' and is_live=1", $xff ); 5990 if( mysql_result( $result, 0, 0 ) ) { 5991 auto_ban_poster( $name, 14, 1, "Detected 3 proxies for same IP", "Proxy/Tor exit node." ); 5992 error( S_GENERICERROR, $dest ); 5993 } 5994 // Check for multiple xffs with same ip? 5995 } 5996 */ 5997 // Check for OP bump limiting 5998 if ($resto && RENZOKU_OP) { 5999 $query = 'SELECT host, time FROM `%s` WHERE no = %d'; 6000 $query = mysql_board_call($query, SQLLOG, $resto); 6001 if ($query) { 6002 $result = mysql_fetch_assoc($query); 6003 // Poster is OP 6004 if ($result && $result['host'] === $host) { 6005 // OP can only bump his thread RENZOKU_OP_TIME seconds after its creation 6006 if ($result['time'] > ($time - RENZOKU_OP_TIME)) { 6007 $is_sage = 1; 6008 } 6009 // OP can only bump his thread every RENZOKU_OP_TIME2 seconds 6010 else { 6011 $query2 = "SELECT time FROM `%s` WHERE host = '%s' AND resto = %d ORDER BY no DESC LIMIT 1"; 6012 $query2 = mysql_board_call($query2, SQLLOG, $host, $resto); 6013 if ($query2) { 6014 $result = mysql_fetch_assoc($query2); 6015 if ($result && $result['time'] > ($time - RENZOKU_OP_TIME2)) { 6016 $is_sage = 1; 6017 } 6018 } 6019 mysql_free_result($query2); 6020 } 6021 } 6022 } 6023 mysql_free_result($query); 6024 } 6025 } 6026 6027 // Minimal cooldowns for authed users (3s) 6028 if ($may_flood) { 6029 $query = "SELECT time FROM `%s` WHERE host = '%s' ORDER BY no DESC LIMIT 1"; 6030 6031 $result = mysql_board_call($query, SQLLOG, $host); 6032 6033 if ($flood_row = mysql_fetch_assoc($result)) { 6034 $last_time = (int)$flood_row['time']; 6035 6036 $cooldown = 5; 6037 6038 if ($last_time > $time - $cooldown) { 6039 error(sprintf(S_RENZOKU, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 6040 } 6041 } 6042 } 6043 6044 time_log( "fc" ); 6045 6046 $tensorchan_score = 0; 6047 6048 // thumbnail 6049 $image_path = ""; 6050 $m_img = false; 6051 if ($has_image) { 6052 if( USE_THUMB && !UPLOAD_BOARD ) { 6053 // Detect and block NSFW content 6054 $_need_inference = tensorchan_is_needed($userpwd, $resto, $W, $H, $ext); 6055 6056 if ($_need_inference) { 6057 $_tensor_png = false; 6058 } 6059 else { 6060 $_tensor_png = null; 6061 } 6062 6063 $ret = make_thumb( $dest, $tim, $ext, $resto, $TN_W, $TN_H, $tmd5, $webm_sar, $_tensor_png); 6064 6065 if (!$ret && $ext != ".pdf") { 6066 error(S_IMGFAIL, $dest); 6067 } 6068 6069 if ($_need_inference && $_tensor_png) { 6070 $tensorchan_score = tensorchan_check_nsfw($_tensor_png); 6071 unset($_tensor_png); 6072 } 6073 } 6074 6075 $name_part = UPLOAD_BOARD ? $insfile : $tim; 6076 $image_path = IMG_DIR . $name_part . $ext; 6077 if( move_uploaded_file( $dest, $image_path ) === false ) { 6078 error( S_FAILEDUPLOAD, $dest ); 6079 } 6080 chmod( $image_path, 0664 ); 6081 6082 if (MOBILE_IMG_RESIZE) { 6083 $m_img = resize_mobile_image($image_path, $W, $H, $fsize, $tim, $ext); 6084 } 6085 6086 // Oekaki 6087 if (ENABLE_PAINTERJS) { 6088 // Replays 6089 if (ENABLE_OEKAKI_REPLAYS) { 6090 $oe_replay_path = null; 6091 6092 if (isset($_FILES['oe_replay']) && $_FILES['oe_replay']['name'] === 'tegaki.tgkr' && $insfile === 'tegaki') { 6093 if (oekaki_validate_replay($_FILES['oe_replay']) === true) { 6094 $oe_replay_path = IMG_DIR . $tim . '.tgkr'; 6095 6096 if (move_uploaded_file($_FILES['oe_replay']['tmp_name'], $oe_replay_path) === false) { 6097 error(S_FAILEDUPLOAD); 6098 } 6099 6100 chmod($oe_replay_path, 0664); 6101 } 6102 } 6103 6104 // Oekaki meta 6105 if (isset($_POST['oe_time'])) { 6106 if (isset($_POST['oe_src']) && $resto) { 6107 $oe_src_pid = oekaki_get_valid_src_pid($_POST['oe_src'], BOARD_DIR, $resto); 6108 } 6109 else { 6110 $oe_src_pid = null; 6111 } 6112 6113 $com .= oekaki_format_info( 6114 $_POST['oe_time'], 6115 $oe_replay_path ? $tim : null, 6116 $oe_src_pid 6117 ); 6118 } 6119 } 6120 } 6121 } 6122 6123 //logtime( "Thumbnail created" ); 6124 time_log( "t" ); 6125 6126 // Infrequent flood check (dupe image) 6127 if( $has_image && (!$capcode || $capcode === 'none')) { 6128 if ($resto) { 6129 $result = mysql_board_call("SELECT sql_no_cache `no`,`resto` FROM `" . SQLLOG . "` WHERE archived = 0 and (resto = %d OR no = %d) AND `md5`='%s' AND filedeleted=0 limit 1", $resto, $resto, $md5); 6130 } 6131 else { 6132 $result = mysql_board_call("SELECT sql_no_cache `no`,`resto` FROM `" . SQLLOG . "` WHERE archived = 0 AND resto = 0 AND `md5`='%s' AND filedeleted=0 limit 1", $md5); 6133 } 6134 6135 if( mysql_num_rows( $result ) ) { 6136 list( $dupeno, $duperesto ) = mysql_fetch_row( $result ); 6137 if( !$duperesto ) $duperesto = $dupeno; 6138 error( '' . S_DUPE . ' <a href="//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $duperesto . PHP_EXT2 . '#p' . $dupeno . '">here</a>.', $dest ); 6139 } 6140 6141 if ($resto && MAX_IMG_REPOST_COUNT > 0) { 6142 $_query = 'SELECT COUNT(*) FROM `' . SQLLOG . "` WHERE archived = 0 AND resto != 0 AND `md5` = '%s' AND filedeleted = 0"; 6143 6144 $result = mysql_board_call($_query, $md5); 6145 6146 if ($result) { 6147 $_count = (int)mysql_fetch_row($result)[0]; 6148 6149 if ($_count >= MAX_IMG_REPOST_COUNT) { 6150 error(S_DUPE); 6151 } 6152 } 6153 } 6154 6155 if ( defined('SQLLOGMD5') ) { 6156 // TODO: There's a race here. This should just be INSERT and check for failure! 6157 $result = mysql_board_call("SELECT sql_no_cache * FROM `%s` WHERE md5='%s' AND now > DATE_SUB(NOW(), INTERVAL 1 DAY) limit 1", SQLLOGMD5, $md5); 6158 if ( mysql_num_rows( $result ) ) { 6159 list( $dc_now, $dc_filename, $dc_md5p ) = mysql_fetch_row( $result ); 6160 6161 if( $dc_now ) { 6162 error('Error: You must wait longer before reposting this file.', $dest ); 6163 } 6164 } 6165 } 6166 } 6167 6168 $rootpredicate = $resto ? "0" : "now()"; 6169 6170 // ROBOT9000 6171 if (defined('ROBOT9000') && ROBOT9000) { 6172 // Logged in uses can bypass r9k by using the "bypass_r9k" command in the Options field. 6173 // Capcoded posts always bypass r9k. 6174 if (($options_field !== 'bypass_r9k' || !has_level('janitor')) && $capcode === 'none') { 6175 require_once 'plugins/robot9000.php'; 6176 $r9k_status = r9k_process($com, $md5, ip2long($host)); 6177 if ($r9k_status !== R9K_OK) { 6178 error($r9k_status, $dest ); 6179 } 6180 } 6181 } 6182 6183 // FIX ME, comments with html get truncated and break the layout, but only mods and janitors can bypass the regular limits 6184 if (strlen($com) > 65536) { 6185 error(S_TOOLONG, $dest); 6186 } 6187 6188 //logtime( "Before insertion" ); 6189 6190 //find sticky & autosage 6191 // auto-sticky 6192 //$sticky = false; 6193 // autosagin is now done in spam_filter_post_content 6194 //$autosage = spam_filter_should_autosage( $com, $sub, $name, $fsize, $resto, $W, $H, $dest, $insertid ); 6195 6196 //old auto-sticky code -- disabled 6197 // if(defined('AUTOSTICKY') && AUTOSTICKY) { 6198 // $autosticky = preg_split("/,\s*/", AUTOSTICKY); 6199 // if($resto == 0) { 6200 // if($insertid % 1000000 == 0 || in_array($insertid,$autosticky)) 6201 // $sticky = true; 6202 // } 6203 // } 6204 6205 $flag_cols = ""; 6206 $flag_vals = ""; 6207 6208 if( $captcha_bypass ) { 6209 $flag_cols .= ',4pass_id'; 6210 $flag_vals .= ",'" . $passid . "'"; 6211 } 6212 6213 if ($since4pass) { 6214 $flag_cols .= ',since4pass'; 6215 $flag_vals .= ",$since4pass"; 6216 } 6217 /* 6218 if( $sticky ) { 6219 $flag_cols .= ",sticky"; 6220 $flag_vals .= ",1"; 6221 } 6222 */ 6223 //permasage just means "is sage" for replies 6224 if( $resto ? $is_sage : $autosage ) { 6225 $flag_cols .= ",permasage"; 6226 $flag_vals .= ",1"; 6227 } 6228 6229 if( $capcode ) { 6230 $flag_cols .= ',capcode'; 6231 $flag_vals .= ",'$capcode'"; 6232 } 6233 6234 if ($m_img) { 6235 $flag_cols .= ',m_img'; 6236 $flag_vals .= ",1"; 6237 } 6238 6239 //$country = geoip_country_code_by_addr( $_SERVER['REMOTE_ADDR'] ); 6240 //if( !$country ) $country = 'XX'; 6241 $geo_data = GeoIP2::get_country($_SERVER['REMOTE_ADDR']); 6242 6243 if ($geo_data && isset($geo_data['country_code'])) { 6244 $country = $geo_data['country_code']; 6245 6246 // FIXME: football cups 6247 /* 6248 if (BOARD_DIR === 'sp' && $country === 'GB' && isset($geo_data['sub_code'])) { 6249 if ($geo_data['sub_code'] === 'WLS') { 6250 $country = 'XW'; 6251 } 6252 else if ($geo_data['sub_code'] === 'SCT') { 6253 $country = 'XS'; 6254 } 6255 else if ($geo_data['sub_code'] !== 'NIR') { 6256 $country = 'XE'; 6257 } 6258 } 6259 */ 6260 } 6261 else { 6262 $country = 'XX'; 6263 } 6264 6265 // User Agent ID 6266 $browser_id = spam_filter_get_browser_id(); 6267 6268 // Request Signature 6269 $_req_sig = spam_filter_get_req_sig(); 6270 6271 /** 6272 * Flood checks 6273 */ 6274 $_threat_score = 0; 6275 6276 if (true || !$captcha_bypass) { 6277 $_pwd_known = $userpwd && $userpwd->isUserKnownOrVerified(360); 6278 $_pwd_trusted = $userpwd && ($userpwd->postCount() > 10 || $userpwd->verifiedLevel()); 6279 $_pwd_verified = $userpwd && $userpwd->verifiedLevel(); 6280 6281 if (!$_pwd_known) { 6282 $_threat_score = spam_filter_get_threat_score($country, !$resto, true); 6283 } 6284 6285 // Sample the user agent of known users 6286 if (false && $userpwd && $userpwd->isUserKnownOrVerified(4320) && $userpwd->postCount() > 10) { 6287 if (mt_rand(0, 9999) < 2000) { 6288 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6289 log_spam_filter_trigger('log_safe_req', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6290 } 6291 } 6292 6293 if (!$_pwd_known && ($_threat_score > 0.31)) { 6294 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6295 log_spam_filter_trigger('block_txt_threat', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6296 show_post_successful_fake($resto); 6297 return; 6298 } 6299 6300 if (false && !$_pwd_known && $resto == 18361689 && BOARD_DIR === 'fa' && mt_rand(0, 9) >= 1 && $country != 'US') { 6301 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6302 log_spam_filter_trigger('block_other', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6303 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6304 } 6305 6306 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'g' && strpos($_thread_sub, '/aicg/') !== false) { 6307 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6308 log_spam_filter_trigger('block_aicg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6309 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6310 } 6311 6312 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_com, '/lolg/') !== false) { 6313 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6314 log_spam_filter_trigger('block_lolg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6315 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6316 //show_post_successful_fake($resto); 6317 //return; 6318 } 6319 6320 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_com, '/overwatch') !== false) { 6321 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6322 log_spam_filter_trigger('block_owg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6323 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6324 //show_post_successful_fake($resto); 6325 //return; 6326 } 6327 6328 if (false && !$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'fa' && strpos($_thread_sub, 'Workwear General') !== false) { 6329 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6330 log_spam_filter_trigger('block_denim', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6331 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6332 //show_post_successful_fake($resto); 6333 //return; 6334 } 6335 6336 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/bag/') !== false && ($_threat_score > 0.01 || $browser_id === '04d2237a2')) { 6337 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6338 log_spam_filter_trigger('block_bag', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6339 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6340 } 6341 6342 if (false && !$_pwd_known && !$resto && (BOARD_DIR === 'co' || BOARD_DIR === 'a') && $country !== 'XX' && $browser_id === '02b99990d' && ($country == 'GB' || $country == 'DE' || $country == 'AU' || strpos($_COOKIE['_tcs'], $_SERVER['HTTP_X_TIMEZONE']) === false)) { 6343 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6344 log_spam_filter_trigger('block_peridot', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6345 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6346 } 6347 6348 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/gbfg/') !== false) { 6349 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6350 log_spam_filter_trigger('block_gbfg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6351 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6352 //show_post_successful_fake($resto); 6353 //return; 6354 } 6355 6356 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'v' && strpos($_thread_sub, 'gamesdonequick') !== false && $_threat_score >= 0.09 && mt_rand(0, 9) >= 1) { 6357 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6358 log_spam_filter_trigger('block_adgq', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6359 show_post_successful_fake($resto); 6360 return; 6361 } 6362 6363 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/zzz/') !== false && $_threat_score >= 0.09 && mt_rand(0, 9) >= 1) { 6364 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6365 log_spam_filter_trigger('block_zzz', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6366 show_post_successful_fake($resto); 6367 return; 6368 } 6369 6370 if (!$_pwd_verified && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/funkg/') !== false && $_threat_score >= 0.09) { 6371 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6372 log_spam_filter_trigger('block_funkg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6373 show_post_successful_fake($resto); 6374 return; 6375 } 6376 6377 if (!$has_image) { 6378 if (!$_pwd_verified && $_threat_score >= 0.09 && mt_rand(0, 9) >= 5 && BOARD_DIR !== 'f') { 6379 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6380 log_spam_filter_trigger('block_pub_prox', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6381 show_post_successful_fake($resto); 6382 return; 6383 } 6384 } 6385 else { 6386 if (!$resto) { 6387 $_thres = 3; 6388 } 6389 else { 6390 $_thres = 4; 6391 } 6392 6393 if (!$_pwd_verified && $_threat_score >= 0.09 && mt_rand(0, 9) >= $_thres && BOARD_DIR !== 'f') { 6394 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6395 log_spam_filter_trigger('block_pub_prox', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6396 if (mt_rand(0, 1)) { 6397 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6398 } 6399 else { 6400 show_post_successful_fake($resto); 6401 return; 6402 } 6403 } 6404 } 6405 6406 if (false && BOARD_DIR === 'vg' && $country !== 'CO' && isset($_COOKIE['_tcs']) && strpos($_COOKIE['_tcs'], '.America/Bogota.') !== false) { 6407 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6408 log_spam_filter_trigger('log_bag_co', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6409 } 6410 6411 // FLOOD CHECK 6412 $flood_status = 0;//spam_filter_is_post_flood($host, BOARD_DIR, $resto, null); 6413 6414 if ($flood_status === 3) { 6415 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6416 6417 log_spam_filter_trigger('block_flood_check', BOARD_DIR, $resto, $host, $flood_status, $_bot_headers); 6418 6419 // Raw thread flood 6420 if ($flood_status === 3) { 6421 error(S_FAILEDUPLOAD); 6422 } 6423 else { 6424 show_post_successful_fake($resto); 6425 return; 6426 } 6427 } 6428 6429 // Check Cloudflare's bot score and block using the lenient rangeban message 6430 if ($userpwd && !$userpwd->isUserKnownOrVerified(1440) && isset($_SERVER['HTTP_X_BOT_SCORE'])) { 6431 if (spam_filter_is_pwd_blocked($userpwd->getPwd(), 'block_bm_bot', 24)) { 6432 log_spam_filter_trigger('block_bm_bot_pwd', BOARD_DIR, $resto, $host, $_SERVER['HTTP_X_BOT_SCORE']); 6433 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6434 } 6435 6436 if (spam_filter_is_likely_automated($memcached)) { 6437 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6438 write_to_event_log('block_bm_bot', $host, [ 6439 'pwd' => $userpwd->getPwd(), 6440 'arg_num' => $_SERVER['HTTP_X_BOT_SCORE'], 6441 'board' => BOARD_DIR, 6442 'thread_id' => $resto, 6443 'meta' => $_bot_headers 6444 ]); 6445 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6446 } 6447 } 6448 6449 // If the country has changed, log the pwd and then block it for the next 24h 6450 if ($userpwd && !$userpwd->verifiedLevel() && $userpwd->postCount() < 20) { 6451 if (spam_filter_has_country_changed($userpwd->getPwd())) { 6452 log_spam_filter_trigger('block_country_changed', BOARD_DIR, $resto, $host, 1); 6453 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6454 } 6455 6456 if ($userpwd->envChanged()) { 6457 write_to_event_log('country_changed', $host, [ 6458 'pwd' => $userpwd->getPwd(), 6459 'arg_str' => $country, 6460 'board' => BOARD_DIR, 6461 'thread_id' => $resto 6462 ]); 6463 6464 log_spam_filter_trigger('block_country_changed', BOARD_DIR, $resto, $host, 1); 6465 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6466 } 6467 } 6468 } 6469 6470 /** 6471 * Custom flag selection 6472 */ 6473 if (ENABLE_BOARD_FLAGS) { 6474 if ($_POST['flag'] === '0' || !isset($board_flags_array[$_POST['flag']])) { 6475 $board_flag_code = ''; 6476 6477 // FIXME: remove this eventually as we use localStorage now 6478 if (isset($_COOKIE['4chan_flag'])) { 6479 setcookie('4chan_flag', '', $time - 3600, '/', $cookie_domain); 6480 } 6481 } 6482 else { 6483 $board_flag_code = mysql_real_escape_string($_POST['flag']); 6484 } 6485 } 6486 else { 6487 $board_flag_code = ''; 6488 } 6489 6490 if ($board_flag_code) { 6491 $board_flag_col = ',board_flag'; 6492 $board_flag_val = ",'$board_flag_code'"; 6493 } 6494 else { 6495 $board_flag_col = ''; 6496 $board_flag_val = ''; 6497 } 6498 6499 if( $resto ) calculate_indexes_to_rebuild( $resto ); 6500 6501 // Remove old replies if the thread is sticky+undead 6502 if ($is_undead_sticky && STICKY_CAP > 1) { 6503 $query = "SELECT MIN(no) FROM (SELECT no FROM `" . BOARD_DIR . "` WHERE resto = $resto ORDER BY no DESC LIMIT " . (STICKY_CAP - 1) . ") as subsel"; 6504 $result = mysql_board_call($query); 6505 if ($result) { 6506 $prune_row = mysql_fetch_row($result); 6507 6508 mysql_free_result($result); 6509 6510 $min_no = (int)$prune_row[0]; 6511 6512 if ($min_no > $resto) { 6513 $query = "SELECT no FROM `" . BOARD_DIR . "` WHERE resto = $resto AND no < $min_no"; 6514 $result = mysql_board_call($query); 6515 6516 if ($result) { 6517 while ($prune_row = mysql_fetch_assoc($result)) { 6518 delete_post((int)$prune_row['no'], '', 0, 1, 1, 0); 6519 } 6520 6521 mysql_free_result($result); 6522 } 6523 } 6524 } 6525 } 6526 6527 // April 2024 6528 /* 6529 if ($_xa24_since4pass && !UPLOAD_BOARD && !JANITOR_BOARD) { 6530 $name = april_2024_get_name(); 6531 6532 if ($emails == '$DESU' && strlen($com) < 10000) { 6533 $com .= '<div class="xa24desu"></div>'; 6534 } 6535 } 6536 */ 6537 $user_meta = encode_user_meta($browser_id, substr($_req_sig, 0, 8), $userpwd); 6538 6539 $insert_tries = 2; 6540 do { 6541 if( SKIP_DOUBLES == 1 ) mysql_board_call( "START TRANSACTION" ); 6542 $query = "insert into `" . SQLLOG . "` (now,name,sub,com,host,pwd,email,filename,ext,w,h,tn_w,tn_h,tim,time,last_modified,md5,fsize,root,resto$flag_cols,tmd5,id,country$board_flag_col) values (" . 6543 "'" . $now . "'," . 6544 "'" . mysql_real_escape_string( $name ) . "'," . 6545 mysql_nullify( mysql_real_escape_string( $sub ) ) . "," . 6546 "'" . mysql_real_escape_string( $com ) . "'," . 6547 "'" . mysql_real_escape_string( $host ) . "'," . 6548 "'" . mysql_real_escape_string( $pass ) . "'," . 6549 "'" . mysql_real_escape_string($user_meta) . "'," . 6550 "'" . mysql_real_escape_string( $insfile ) . "'," . 6551 mysql_nullify( $ext ) . "," . 6552 (int)$W . "," . 6553 (int)$H . "," . 6554 (int)$TN_W . "," . 6555 (int)$TN_H . "," . 6556 "'" . $tim . "'," . 6557 (int)$time . "," . 6558 (int)$time . "," . 6559 mysql_nullify( $md5 ) . "," . 6560 (int)$fsize . "," . 6561 $rootpredicate . "," . 6562 (int)$resto . 6563 $flag_vals . "," . 6564 mysql_nullify( $tmd5 ) . "," . 6565 mysql_nullify( $uid ) . "," . 6566 "'$country'$board_flag_val)"; 6567 6568 if( !$result = mysql_board_call( $query ) ) { 6569 echo S_SQLFAIL; 6570 } //post registration 6571 time_log( "i" ); 6572 6573 $insertid = mysql_board_insert_id(); 6574 if( SKIP_DOUBLES == 1 ) { 6575 if( has_doubles( $insertid ) ) { 6576 mysql_board_call( "ROLLBACK" ); 6577 // retry 6578 } else { 6579 mysql_board_call( "COMMIT" ); 6580 $insert_tries = 0; 6581 } 6582 } else { 6583 $insert_tries = 0; 6584 } 6585 } while( $insert_tries-- ); 6586 6587 // Captcha bypass token 6588 if ($captcha_bypass_allow_credits && $_threat_score < 0.15) { 6589 set_twister_captcha_credits($memcached, $host, $userpwd, $time); 6590 } 6591 /* 6592 if (!$captcha_bypass) { 6593 if (mt_rand(0, 99) === 0) { 6594 write_to_event_log('known_sample', $host, [ 6595 'board' => BOARD_DIR, 6596 'thread_id' => $resto, 6597 'arg_num' => $userpwd->isUserKnown(), 6598 ]); 6599 } 6600 } 6601 */ 6602 6603 $_can_upa = false; 6604 6605 if ($userpwd && $userpwd->verifiedLevel()) { 6606 $_can_upa = true; 6607 } 6608 else if ($captcha_bypass) { 6609 $_can_upa = true; 6610 } 6611 else if ($_threat_score < 0.09) { 6612 $_can_upa = true; 6613 } 6614 6615 if ($_can_upa) { 6616 $userpwd->updatePostActivity(!$resto, $has_image); 6617 } 6618 6619 $userpwd->setCookie($cookie_domain); 6620 6621 // April 2019 6622 /* 6623 if (defined('LIKE_MAX_LIKES') && LIKE_MAX_LIKES > 0) { 6624 like_update_post_score(); 6625 } 6626 */ 6627 // Halloween 2017 6628 /* 6629 if ($resto && defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky2017') { 6630 process_halloween_score($com, $resto, $passid, $pass, $pass_is_bannable); 6631 } 6632 */ 6633 // April 2018 6634 //update_april_team_scores(); 6635 6636 // Log mod action if posted with html 6637 if ($log_mod_action) { 6638 $action_log_post = array( 6639 'no' => $insertid, 6640 'name' => $name, 6641 'sub' => $sub, 6642 'com' => $com, 6643 'filename' => $insfile, 6644 'ext' => $ext 6645 ); 6646 6647 if ($log_html_post) { 6648 $action_log_post['com'] = htmlspecialchars($com, ENT_QUOTES); 6649 log_mod_action(4, $action_log_post); 6650 } 6651 // capcode posting 6652 if ($log_capcode_post) { 6653 $action_log_post['name'] .= ' ## ' . ucfirst($capcode); 6654 log_mod_action(5, $action_log_post, $capcode === 'verified'); 6655 } 6656 } 6657 6658 if( $resto ) { //sage or age action 6659 $resline = mysql_board_call( "select count(no) from `" . SQLLOG . "` where archived=0 and resto=" . $resto ); 6660 $countres = mysql_result( $resline, 0, 0 ); 6661 6662 $permasage_hours = (int)PERMASAGE_HOURS; 6663 6664 if ($permasage_hours > 0) { 6665 $time_col = 'time,'; 6666 } 6667 else { 6668 $time_col = ''; 6669 } 6670 6671 // FIXME: a similar query is done at line ~4723 6672 $resline = mysql_board_call( "select {$time_col}sticky,permasage,permaage,root from `" . SQLLOG . "` where no=" . $resto ); 6673 $resline = mysql_fetch_assoc($resline); 6674 6675 if ($resline['sticky'] || $resline['permasage']) { 6676 $root_col = ''; 6677 } 6678 else if ($resline['permaage']) { 6679 $root_col = 'root=now(),'; 6680 } 6681 else if ($is_sage || $countres >= MAX_RES) { 6682 $root_col = ''; 6683 } 6684 else if ($permasage_hours && ($time - ($permasage_hours * 3600) >= $resline['time'])) { 6685 $root_col = ''; 6686 } 6687 else { 6688 $root_col = 'root=now(),'; 6689 6690 if (!$captcha_bypass && BOARD_DIR === 'jp') { 6691 if (!spam_filter_can_bump_thread($resline['root'])) { 6692 $root_col = ''; 6693 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6694 log_spam_filter_trigger('necrobump', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6695 } 6696 } 6697 } 6698 6699 mysql_board_call("update `" . SQLLOG . "` set {$root_col}last_modified=%d where no=%d", $_SERVER['REQUEST_TIME'], $resto); 6700 } 6701 6702 if( defined( 'AUTOSTICKY' ) && AUTOSTICKY ) { 6703 $autosticky = preg_split( "/,\s*/", AUTOSTICKY ); 6704 if( $resto == 0 ) { 6705 if( $insertid % 1000000 == 0 || in_array( $insertid, $autosticky ) ) { 6706 $sticky = true; 6707 mysql_board_call( "update " . SQLLOG . " set sticky=1,root=root where no=$insertid" ); 6708 } 6709 } 6710 } 6711 6712 if( SAVE_XFF == 1 && $xff ) { 6713 mysql_global_do( "INSERT INTO xff (tim,board,xff,ip,postno,is_live) VALUES ('%s','%s','%s',%d,%d,1)", $tim, BOARD_DIR, $xff, ip2long( $host ), $insertid ); 6714 } 6715 6716 if (UPLOAD_BOARD && $md5 ) { 6717 $result = mysql_board_call( "insert ignore into `%s` (filename,md5) values('%s','%s')", SQLLOGMD5, $insfile, $md5 ); 6718 } 6719 6720 // determine url to redirect to 6721 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 6722 if( !$is_nonoko && !$resto ) { 6723 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $insertid . PHP_EXT2; 6724 } else if( !$is_nonoko ) { 6725 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $resto . PHP_EXT2 . '#p' . $insertid; 6726 } else { 6727 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/'; 6728 } 6729 6730 // To let the JavaScript thread watcher know the newly created thread ID 6731 if (!$resto && isset($_POST['awt'])) { 6732 setcookie('4chan_awt', $insertid, 0, '/' . BOARD_DIR . '/', $cookie_domain); 6733 } 6734 6735 show_post_successful( $mes, $com, $insertid, $resto, $redirect, $delay_refresh ); 6736 6737 $static_rebuild = ( STATIC_REBUILD == 1 ); 6738 //logtime( "Before trim_db" ); 6739 6740 // trim database 6741 if (!$resto) { 6742 6743 if (!$static_rebuild) { 6744 trim_db(); 6745 6746 if (ENABLE_ARCHIVE && ARCHIVE_MAX_AGE) { 6747 trim_archive(); 6748 } 6749 } 6750 } 6751 6752 //logtime( "After trim_db" ); 6753 //time_log( "tr" ); 6754 6755 $_need_updatelog = true; 6756 6757 if (AUTOARCHIVE_CAP && $resto && !$sticky && !$undead && ENABLE_ARCHIVE) { 6758 if (count_thread_replies(BOARD_DIR, $resto) >= AUTOARCHIVE_CAP) { 6759 $_need_updatelog = false; 6760 archive_thread($resto); 6761 } 6762 } 6763 6764 // update html 6765 if ($_need_updatelog) { 6766 updatelog( $resto ? $resto : $insertid ); 6767 } 6768 //logtime( "Pages rebuilt" ); 6769 //time_log( "r" ); 6770 6771 // late tasks happen below here 6772 iplog_add( BOARD_DIR, $insertid, $host, $time, $resto == 0, $tim, $has_image ); 6773 6774 // Auto-report possibly nsfw post 6775 if ($tensorchan_score && $tensorchan_score > 0.5) { 6776 tensorchan_log(BOARD_DIR, $insertid, $resto, $tim, $ext, $tensorchan_score); 6777 } 6778 } else { 6779 // silent reject 6780 $insertid = 0; 6781 $noko = 0; 6782 } 6783 /* 6784 if( STATS_USER_JS ) { 6785 mysql_global_do( "UPDATE `user_stats` SET `count` = `count`+1 WHERE name='%s'", $stats_ok ); 6786 } 6787 */ 6788 } 6789 6790 // Redirects to the most rcently created thread 6791 // This is to confuse spambots 6792 function show_post_successful_fake($resto = 0, $captcha_passed = true) { 6793 $thread_id = (int)$resto; 6794 $insert_id = 0; 6795 6796 if (!$resto) { 6797 $query = 'SELECT resto FROM `' . BOARD_DIR . '` WHERE resto != 0 ORDER BY resto DESC LIMIT 1'; 6798 $res = mysql_board_call($query); 6799 if ($res) { 6800 $row = mysql_fetch_row($res); 6801 $insert_id = (int)$row[0]; 6802 } 6803 } 6804 else { 6805 $query = 'SELECT no FROM `' . BOARD_DIR . '` ORDER BY no DESC LIMIT 1'; 6806 $res = mysql_board_call($query); 6807 if ($res) { 6808 $row = mysql_fetch_row($res); 6809 $insert_id = (int)$row[0] + 1; 6810 } 6811 } 6812 6813 if (!$thread_id) { 6814 $redirect = 'https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $insert_id . PHP_EXT2; 6815 } 6816 else { 6817 $redirect = 'https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $thread_id . PHP_EXT2 . '#p' . $insert_id; 6818 } 6819 6820 $cookie_domain = '.' . L::d(BOARD_DIR); 6821 6822 $now = $_SERVER['REQUEST_TIME']; 6823 6824 // Name cookie 6825 $c_name = $_POST['name']; 6826 setrawcookie('4chan_name', rawurlencode($c_name), $now + ($c_name ? (7 * 24 * 3600) : -3600), '/', $cookie_domain); 6827 6828 // Password cookie 6829 $userpwd = UserPwd::getSession(); 6830 6831 if ($userpwd) { 6832 if ($captcha_passed) { 6833 $userpwd->setCookie($cookie_domain); 6834 } 6835 else if (!$userpwd->isFake() && !$userpwd->isNew()) { 6836 $userpwd->setCookie($cookie_domain); 6837 } 6838 else { 6839 UserPwd::setFakeCookie($now, $cookie_domain); 6840 } 6841 } 6842 6843 show_post_successful(null, null, $insert_id, $thread_id, $redirect); 6844 } 6845 6846 function show_post_successful( $mes, $com, $insertid, $resto, $redirect, $delay_refresh = false ) { 6847 if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json') { 6848 return show_post_successful_json($insertid, $resto); 6849 } 6850 6851 if( !$mes ) $mes = S_POSTING_DONE; 6852 $time_to_refresh = ( $delay_refresh ) ? 10 : 1; 6853 $script = "<meta http-equiv=\"refresh\" content=\"$time_to_refresh;URL=$redirect\">"; 6854 if( defined( 'POST_SUCCESSFUL_FILE' ) ) { 6855 $file = file_get_contents( POST_SUCCESSFUL_FILE ); 6856 $success = str_replace( "@REDIRECT@", $redirect, $file ); 6857 } else { 6858 // FIXME templating 6859 $icon = DEFAULT_BURICHAN ? 'favicon-ws.ico' : 'favicon.ico'; 6860 $defaultcss = DEFAULT_BURICHAN ? 'yotsubluenew' : 'yotsubanew'; 6861 $cssVersion = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION; 6862 $sg = style_group(); 6863 6864 $styles = array( 6865 'Yotsuba New' => "yotsubanew.$cssVersion.css", 6866 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 6867 'Futaba New' => "futabanew.$cssVersion.css", 6868 'Burichan New' => "burichannew.$cssVersion.css", 6869 'Photon' => "photon.$cssVersion.css", 6870 'Tomorrow' => "tomorrow.$cssVersion.css" 6871 ); 6872 6873 $css = ''; 6874 6875 if( isset( $_COOKIE[$sg] ) ) { 6876 if( isset( $styles[$_COOKIE[$sg]] ) ) { 6877 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $styles[$_COOKIE[$sg]] . '">'; 6878 } 6879 } else { 6880 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 6881 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 6882 } 6883 6884 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 6885 $css = '<link rel="stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' 6886 . CSS_EVENT_NAME . '.' . $cssVersion . '.css" title="switch">'; 6887 } 6888 6889 if( BOARD_DIR == 'j' ) { 6890 $css = '<link rel="stylesheet" type="text/css" href="' . STATIC_SERVER . 'css/janichan.' . $cssVersion . '.css" title="Yotsuba New">'; 6891 } 6892 6893 6894 $script .= '<link rel="shortcut icon" href="//s.4cdn.org/image/' . $icon . '">'; 6895 $success = "<!DOCTYPE html><head>$script<title>" . S_POSTING_DONE . "</title>$css</head><body style=\"margin-top: 20%; text-align: center;\"><h1 style=\"font-size:36pt;\">$mes</h1><!-- thread:$resto,no:$insertid --></body></html>"; 6896 } 6897 echo $success; 6898 6899 if ($resto) { 6900 fastcgi_finish_request(); 6901 } 6902 } 6903 6904 function show_post_successful_json($post_id, $thread_id) { 6905 header('Content-Type: application/json'); 6906 6907 echo '{"tid":' . $thread_id . ',"pid":' . $post_id . '}'; 6908 6909 if ($thread_id) { 6910 fastcgi_finish_request(); 6911 } 6912 } 6913 6914 function resredir( $res, $delete = 0, $no_exit = false ) { 6915 if (!$_SERVER["HTTP_REFERER"]) { 6916 $proto = 'https:'; 6917 } 6918 else { 6919 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 6920 } 6921 6922 $res = (int)$res; 6923 //mysql_board_lock( true ); 6924 if( !$redir = mysql_board_call( "select no,resto from `" . SQLLOG . "` where no=" . $res ) ) { 6925 echo S_SQLFAIL; 6926 } 6927 list( $no, $resto ) = mysql_fetch_row( $redir ); 6928 6929 // if we're deleting and no post/resto (thread gone) 6930 if( !$no && $delete ) { 6931 // send us back to the board 6932 updating_index(); 6933 //mysql_board_unlock(); 6934 if (!$no_exit) { 6935 die; 6936 } 6937 else { 6938 return; 6939 } 6940 } 6941 6942 if( !JANITOR_BOARD ) { 6943 header("Cache-Control: public, max-age=2"); 6944 } 6945 6946 if( !$no ) { 6947 // If no < max(no) then this could be 410 Gone. 6948 http_response_code(404); 6949 error( S_NOTHREADERR, $dest ); 6950 } 6951 6952 if( $resto == "0" ) { // thread 6953 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $no . PHP_EXT2 . '#p' . $no; 6954 } else { 6955 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $resto . PHP_EXT2 . '#p' . $no; 6956 } 6957 6958 $redirect = JANITOR_BOARD ? str_replace( 'boards.', 'sys.', $redirect ) : $redirect; 6959 6960 header("Location: $redirect", true, 301); 6961 echo "<meta http-equiv=\"refresh\" content=\"0;URL=$redirect\">"; 6962 //mysql_board_unlock(); 6963 } 6964 6965 function tensorchan_is_needed($userpwd, $resto, $W, $H, $ext) { 6966 // Inference is disabled 6967 if (!defined('TENSORCHAN_MODE') || !TENSORCHAN_MODE) { 6968 return false; 6969 } 6970 6971 // Can't check 6972 if (!$userpwd) { 6973 return false; 6974 } 6975 6976 // User is known or verified 6977 if ($userpwd->isUserKnownOrVerified(240)) { // 4 hours 6978 return false; 6979 } 6980 6981 // OPs only but the post is a reply 6982 if (TENSORCHAN_MODE == 1 && $resto) { 6983 return false; 6984 } 6985 6986 if ($W < 150 || $H < 150 || $ext == '.pdf') { 6987 return false; 6988 } 6989 6990 return true; 6991 } 6992 6993 function tensorchan_check_nsfw($tensor_png) { 6994 if (!$tensor_png) { 6995 return false; 6996 } 6997 6998 $tensor_res = tensorchan_predict($tensor_png); 6999 7000 if (!$tensor_res) { 7001 return false; 7002 } 7003 7004 if (isset($tensor_res['error'])) { 7005 write_to_event_log('tensor_err', $_SERVER['REMOTE_ADDR'], [ 7006 'board' => BOARD_DIR, 7007 'meta' => htmlspecialchars($tensor_res['error']) 7008 ]); 7009 7010 return false; 7011 } 7012 else { 7013 if (!isset($tensor_res['nsfw'])) { 7014 return false; 7015 } 7016 7017 return (float)$tensor_res['nsfw']; 7018 } 7019 } 7020 7021 function tensorchan_log($board, $post_id, $thread_id, $file_id, $file_ext, $score) { 7022 $post_id = (int)$post_id; 7023 $thread_id = (int)$thread_id; 7024 $score = (float)$score; 7025 7026 $sql =<<<SQL 7027 INSERT INTO tensor_log(board, thread_id, post_id, file_id, file_ext, nsfw) 7028 VALUES('%s', $thread_id, $post_id, '%s', '%s', $score) 7029 SQL; 7030 7031 return !!mysql_global_call($sql, $board, $file_id, $file_ext); 7032 } 7033 7034 function tensorchan_predict($data) { 7035 if (!$data) { 7036 return false; 7037 } 7038 7039 $curl = curl_init(); 7040 7041 $url = "http://danbo.int:8501/predict"; 7042 7043 curl_setopt($curl, CURLOPT_URL, $url); 7044 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 7045 curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 2); 7046 curl_setopt($curl, CURLOPT_TIMEOUT, 4); 7047 7048 curl_setopt($curl, CURLOPT_CUSTOMREQUEST , "POST"); 7049 curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 7050 7051 $headers = array( 7052 'Content-Type: application/octet-stream' 7053 ); 7054 7055 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 7056 curl_setopt($curl, CURLOPT_USERAGENT, '4chan.org'); 7057 7058 $resp = curl_exec($curl); 7059 7060 if ($resp === false) { 7061 if ($errno = curl_errno($curl)) { 7062 $_err = 'Error (' . $errno . '): ' . curl_strerror($errno); 7063 } 7064 else { 7065 $_err = 'Something went wrong'; 7066 } 7067 7068 return ["error" => $_err]; 7069 } 7070 7071 $resp_status = curl_getinfo($curl, CURLINFO_HTTP_CODE); 7072 7073 if ($resp_status >= 300) { 7074 return ["error" => "HTTP $resp_status: $resp"]; 7075 } 7076 7077 curl_close($curl); 7078 7079 if ($resp[0] == '{') { 7080 $resp = json_decode($resp, true); 7081 } 7082 else { 7083 return ["error" => "Not a JSON response"]; 7084 } 7085 7086 return $resp; 7087 } 7088 7089 function background_color( $im, $is_thread ) 7090 { 7091 if( DEFAULT_BURICHAN == 0 ) { 7092 if( $is_thread ) { 7093 list( $r, $g, $b ) = array(0xFF, 0xFF, 0xEE); 7094 } else { 7095 list( $r, $g, $b ) = array(0xF0, 0xE0, 0xD6); 7096 } 7097 } else { 7098 if( $is_thread ) { 7099 list( $r, $g, $b ) = array(0xEE, 0xF2, 0xFF); 7100 } else { 7101 list( $r, $g, $b ) = array(0xD6, 0xDA, 0xF0); 7102 } 7103 } 7104 7105 return imagecolorallocate( $im, $r, $g, $b ); 7106 } 7107 7108 function optimize_thumb($tmppath) { 7109 system("/usr/local/bin/jpegoptim -q --strip-all '$tmppath' >/dev/null 2>&1"); 7110 } 7111 7112 // Calculates perceptual hash for a thumbnail 7113 // $img is a reference to a GD resource 7114 function get_thumb_dhash(&$img, $width, $height) { 7115 if (!$img) { 7116 return false; 7117 } 7118 7119 $data = imagecreatetruecolor(9, 8); 7120 imagecopyresampled($data, $img, 0, 0, 0, 0, 9, 8, $width, $height); 7121 imagefilter($data, IMG_FILTER_GRAYSCALE); 7122 7123 $hash = 0; 7124 $bit = 1; 7125 7126 for ($y = 0; $y < 8; $y++) { 7127 $previous = imagecolorat($data, 0, $y) & 0xFF; 7128 7129 for ($x = 1; $x < 9; $x++) { 7130 $current = imagecolorat($data, $x, $y) & 0xFF; 7131 7132 if ($previous > $current) { 7133 $hash |= $bit; 7134 } 7135 7136 $bit = $bit << 1; 7137 $previous = $current; 7138 } 7139 } 7140 7141 imagedestroy($data); 7142 7143 return sprintf("%016x", $hash); 7144 } 7145 7146 //thumbnails 7147 function make_thumb( $fname, $tim, $ext, $resto, &$TN_W, &$TN_H, &$tmd5, $webm_sar = null, &$tensor_png = null ) 7148 { 7149 $thumb_dir = THUMB_DIR; //thumbnail directory 7150 $outpath = $thumb_dir . $tim . 's.jpg'; 7151 if( !$resto ) { 7152 $width = MAX_W; //output width 7153 $height = MAX_H; //output height 7154 $jpeg_quality = 50; 7155 } else { 7156 $width = MAXR_W; //output width (imgreply) 7157 $height = MAXR_H; //output height (imgreply) 7158 $jpeg_quality = 40; 7159 } 7160 7161 if( ENABLE_PDF == 1 && $ext == '.pdf' ) { 7162 // create jpeg for the thumbnailer 7163 $pdfjpeg = IMG_DIR . $tim . '.pdf.tmp'; 7164 @exec( "/usr/local/bin/gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=jpeg -sOutputFile=$pdfjpeg $fname" ); 7165 if( !file_exists( $pdfjpeg ) ) unlink( $fname ); 7166 $fname = $pdfjpeg; 7167 } 7168 else if (ENABLE_WEBM && ($ext == '.webm' || $ext == '.mp4')) { 7169 $webm_thumb = thumb_webm($fname, $ext); 7170 7171 if (!$webm_thumb) { 7172 unlink($fname); 7173 return false; 7174 } 7175 7176 $fname = $webm_thumb; 7177 } 7178 7179 $size = @GetImageSize($fname); 7180 7181 if ($size === false) { 7182 return; 7183 } 7184 7185 // File size needs to be checked again because of cleanup_uploaded_file 7186 if (defined('MAX_DIMENSION') && $ext == '.gif') { 7187 if ($size[0] > MAX_DIMENSION || $size[1] > MAX_DIMENSION) { 7188 error(S_TOOLARGERES); 7189 } 7190 } 7191 7192 $memory_limit_increased = false; 7193 $maybe_transparent = true; 7194 // Don't increase memory limit on CLI so that the user can do it with a CLI parameter instead 7195 if( $size[0] * $size[1] > 3000000 && isset($_SERVER['REMOTE_ADDR']) ) { 7196 $memory_limit_increased = true; 7197 ini_set( 'memory_limit', memory_get_usage() + $size[0] * $size[1] * 15 ); // for huge images 7198 } 7199 switch( $size[2] ) { 7200 case 1 : 7201 $im_in = ImageCreateFromGIF( $fname ); 7202 if (!$im_in) { 7203 return; 7204 } 7205 break; 7206 case 2 : 7207 $im_in = ImageCreateFromJPEG( $fname ); 7208 if( !$im_in ) { 7209 return; 7210 } 7211 $maybe_transparent = false; 7212 break; 7213 case 3 : 7214 $im_in = ImageCreateFromPNG( $fname ); 7215 if( !$im_in ) { 7216 return; 7217 } 7218 break; 7219 default : 7220 return; 7221 } 7222 7223 $source_w = $size[0]; 7224 $source_h = $size[1]; 7225 7226 if ($webm_sar) { 7227 if ($webm_sar > 1) { 7228 $size[1] = round($size[1] / $webm_sar); 7229 7230 if ($size[1] == 0) { 7231 $size[1] = 1; 7232 } 7233 } 7234 else { 7235 $size[0] = round($size[0] * $webm_sar); 7236 7237 if ($size[0] == 0) { 7238 $size[0] = 1; 7239 } 7240 } 7241 } 7242 7243 // Resizing 7244 if( $size[0] > $width || $size[1] > $height ) { 7245 $key_w = $width / $size[0]; 7246 $key_h = $height / $size[1]; 7247 ( $key_w < $key_h ) ? $keys = $key_w : $keys = $key_h; 7248 $out_w = floor( $size[0] * $keys ); 7249 $out_h = floor( $size[1] * $keys ); 7250 } else { 7251 $out_w = $size[0]; 7252 $out_h = $size[1]; 7253 } 7254 7255 // the thumbnail is created 7256 $im_out = ImageCreateTrueColor( $out_w, $out_h ); 7257 if( !$im_out ) return; 7258 if( $maybe_transparent ) { 7259 $background = background_color( $im_out, $resto == 0 ); 7260 ImageFill( $im_out, 0, 0, $background ); 7261 } 7262 // copy resized original 7263 ImageCopyResampled( $im_out, $im_in, 0, 0, 0, 0, $out_w, $out_h, $source_w, $source_h ); 7264 $tmppath = tempnam( ini_get( "upload_tmp_dir" ), "thumb" ); 7265 // thumbnail saved 7266 ImageJPEG( $im_out, $tmppath, $jpeg_quality ); 7267 7268 // Generate a perceptual hash from the original image 7269 $tmd5 = Phash::hash($im_in, $source_w, $source_h); 7270 7271 if ($tmd5 === false) { 7272 $tmd5 = ''; 7273 } 7274 7275 // Create the PNG file for inference 7276 if ($tensor_png !== null) { 7277 $tensor_png_dim = TENSORCHAN_DIM; 7278 $tensor_png_out = ImageCreateTrueColor($tensor_png_dim, $tensor_png_dim); 7279 ImageCopyResampled($tensor_png_out, $im_in, 7280 0, 0, 0, 0, 7281 $tensor_png_dim, $tensor_png_dim, $source_w, $source_h 7282 ); 7283 $stream = fopen('php://memory','r+'); 7284 imagepng($tensor_png_out, $stream); 7285 rewind($stream); 7286 $tensor_png = stream_get_contents($stream); 7287 fclose($stream); 7288 ImageDestroy($tensor_png_out); 7289 } 7290 7291 // Cleanup 7292 ImageDestroy( $im_in ); 7293 ImageDestroy( $im_out ); 7294 7295 optimize_thumb($tmppath); 7296 7297 //$tmd5 = md5_file( $tmppath ); 7298 rename_across_device( $tmppath, $outpath ); 7299 7300 // if PDF was thumbnailed delete the orig jpeg 7301 if (isset($pdfjpeg)) { 7302 unlink($pdfjpeg); 7303 } 7304 // delete original webm frame 7305 else if (isset($webm_thumb)) { 7306 unlink($webm_thumb); 7307 } 7308 7309 if( $memory_limit_increased ) 7310 ini_restore( 'memory_limit' ); 7311 7312 $TN_W = $out_w; 7313 $TN_H = $out_h; 7314 7315 return $outpath; 7316 } 7317 7318 /* text plastic surgery */ 7319 // you can call with skip_bidi=1 if cleaning a paragraph element (like $com) 7320 function sanitize_text( $str, $skip_bidi = 0, $allow_html = false ) 7321 { 7322 global $admin, $html; 7323 // stupid unicode-hack removal 7324 if( BOARD_DIR != 'jp' && BOARD_DIR != 'a' && BOARD_DIR != 'b' && !SJIS_TAGS ) { 7325 $str = preg_replace( '#[\x{00A0}\x{3000}]#u', ' ', $str ); 7326 7327 } 7328 7329 if( !CODE_TAGS && !SJIS_TAGS) { 7330 $str = preg_replace( "/([ \t\f]|\xE2\x80\x8B|\xE2\x80\xA9)+/", " ", $str ); //collapse multiple spaces like HTML does 7331 } else { 7332 // fix tabs for html compression 7333 $str = str_replace( "\t", " ", $str ); 7334 } 7335 7336 $str = trim( $str ); //blankspace removal 7337 if( get_magic_quotes_gpc() ) { //magic quotes is deleted (?) 7338 $str = stripslashes( $str ); 7339 } 7340 7341 if ($allow_html && $html == 1 && (has_level('manager') || has_flag('html') || has_flag('developer'))) { 7342 $str = purify_html($str); 7343 } else { 7344 $str = htmlspecialchars( $str, ENT_QUOTES ); 7345 } 7346 7347 if( $skip_bidi == 0 ) { 7348 // fix malformed bidirectional overrides - insert as many PDFs as RLOs 7349 //RLO 7350 $str .= str_repeat( "\xE2\x80\xAC", substr_count( $str, "\xE2\x80\xAE" /* U+202E */ ) ); 7351 $str .= str_repeat( "‬", substr_count( $str, "‮" ) ); 7352 $str .= str_repeat( "‬", substr_count( $str, "‮" ) ); 7353 //RLE 7354 $str .= str_repeat( "\xE2\x80\xAC", substr_count( $str, "\xE2\x80\xAB" /* U+202B */ ) ); 7355 $str .= str_repeat( "‬", substr_count( $str, "‫" ) ); 7356 $str .= str_repeat( "‬", substr_count( $str, "‫" ) ); 7357 } 7358 7359 return $str; 7360 } 7361 7362 // TODO: rewrite this to use less SQL queries 7363 function report() { 7364 global $captcha_bypass; 7365 7366 $host = $_SERVER['REMOTE_ADDR']; 7367 7368 if (isset($_COOKIE['4chan_pass'])) { 7369 $userpwd = new UserPwd($host, MAIN_DOMAIN, $_COOKIE['4chan_pass']); 7370 } 7371 else { 7372 $userpwd = new UserPwd($host, MAIN_DOMAIN); 7373 } 7374 7375 if (BOARD_DIR === 'test') { 7376 require_once('forms/report-test.php'); 7377 require_once('modes/report-test.php'); 7378 } 7379 else { 7380 require_once('forms/report.php'); 7381 require_once('modes/report.php'); 7382 } 7383 7384 if (!CAN_REPORT_POSTS) { 7385 fancydie(S_CANNOTREPORTPOSTS); 7386 } 7387 7388 $no = (int)$_GET['no']; 7389 7390 if ($no <= 0) { 7391 fancydie(S_POST_DEAD); 7392 } 7393 7394 $post = report_check_post(BOARD_DIR, $no); 7395 7396 if (!isset($_COOKIE['4chan_auser']) && !isset($_COOKIE['pass_enabled'])) { 7397 $no_captcha = report_can_bypass_captcha($host, $userpwd, $post); 7398 } 7399 else { 7400 $no_captcha = false; 7401 } 7402 7403 if ($_SERVER['REQUEST_METHOD'] == 'GET') { 7404 header( 'Cache-Control: private, no-cache, must-revalidate' ); 7405 header( 'Expires: -1' ); 7406 7407 // Doesn't check bans here 7408 report_check_ip( BOARD_DIR, $no, false); 7409 7410 form_report(BOARD_DIR, $no, $no_captcha); 7411 } 7412 else { 7413 if (valid_captcha_bypass() !== true && $no_captcha !== true) { 7414 if (CAPTCHA_TWISTER) { 7415 $_m = create_memcached_instance(); 7416 7417 if (isset($_POST['t-challenge']) && $_POST['t-challenge'] === 'noop') { 7418 if (use_twister_captcha_credit($_m, $host, $userpwd) === false) { 7419 error(S_CAPTCHATIMEOUT); 7420 } 7421 } 7422 else if (is_twister_captcha_valid($_m, $host, $userpwd, BOARD_DIR, 1) === false) { 7423 error(S_BADCAPTCHA); 7424 } 7425 } 7426 else { 7427 start_recaptcha_verify(); 7428 7429 if (!$captcha_bypass) { 7430 end_recaptcha_verify(); 7431 } 7432 } 7433 } 7434 7435 // Also checks for bans 7436 report_check_ip(BOARD_DIR, $no, true); 7437 7438 if (!isset($_POST['cat']) && !isset($_POST['cat_id'])) { 7439 fancydie('Invalid category selected.'); 7440 } 7441 7442 if ($_POST['cat']) { 7443 $cat_id = (int)$_POST['cat']; 7444 } 7445 else if ($_POST['cat_id']) { 7446 $cat_id = (int)$_POST['cat_id']; 7447 } 7448 else { 7449 $cat_id = null; 7450 } 7451 7452 if (!$cat_id) { 7453 fancydie('Invalid category selected.'); 7454 } 7455 /* 7456 if ($no_captcha) { 7457 write_to_event_log('skip_rep_captcha', $host, [ 7458 'board' => BOARD_DIR, 7459 'thread_id' => $post['resto'] ? $post['resto'] : $post['no'], 7460 'post_id' => $post['no'] 7461 ]); 7462 } 7463 */ 7464 report_submit(BOARD_DIR, $no, $cat_id); // script dies here 7465 } 7466 7467 die( '</body></html>' ); 7468 } 7469 7470 /** 7471 * Archive deletion function 7472 * only works on archived posts, for authed users 7473 */ 7474 function arcdel($no, $redirect = false, $redirect_res = null) { 7475 global $onlyimgdel; 7476 7477 $delno = array(); 7478 $time = $_SERVER['REQUEST_TIME']; 7479 reset( $_POST ); 7480 7481 while ($item = each($_POST)) { 7482 if ($item[1] == 'delete') { 7483 $delno[] = $item[0]; 7484 } 7485 } 7486 7487 $numdeletions = count($delno); 7488 7489 if (!$numdeletions) { 7490 return; 7491 } 7492 7493 $rebuild_archive_json = false; 7494 7495 $rebuild = array(); 7496 7497 for ($i = 0; $i < $numdeletions; $i++) { 7498 $resto = delete_post($delno[$i], '', $onlyimgdel, 0, 1, $numdeletions == 1, false, true); 7499 if ($resto) { 7500 $rebuild[$resto] = true; 7501 } 7502 else if (!$onlyimgdel) { 7503 $rebuild_archive_json = true; 7504 } 7505 } 7506 7507 if (!has_level('janitor')) { 7508 mysql_global_call("INSERT INTO user_actions (ip,board,action,postno,time) VALUES (%d,'%s','delete',%d,now())", ip2long( $_SERVER["REMOTE_ADDR"] ), BOARD_DIR, $delno[0]); 7509 } 7510 7511 if ($redirect) { 7512 if ($redirect_res) { 7513 resredir($redirect_res, 1, true); 7514 } 7515 else { 7516 updating_index(); 7517 } 7518 7519 fastcgi_finish_request(); 7520 } 7521 7522 foreach ($rebuild as $thread_id => $true) { 7523 rebuild_archived_thread($thread_id); 7524 } 7525 7526 if ($rebuild_archive_json && ENABLE_JSON_THREADS) { 7527 generate_board_archived_json(); 7528 } 7529 } 7530 7531 function user_delete( $no, $pwd, $redirect = false, $redirect_res = null ) 7532 { 7533 global $pwdc, $onlyimgdel, $captcha_bypass; 7534 7535 if (UPLOAD_BOARD && $onlyimgdel) { 7536 error("It doesn't make any sense to do a file-only delete on a file board!"); 7537 } 7538 7539 $delno = array(); 7540 $time = $_SERVER['REQUEST_TIME']; 7541 $delflag = false; 7542 reset( $_POST ); 7543 7544 while( $item = each( $_POST ) ) { 7545 if( $item[1] == 'delete' ) { 7546 array_push( $delno, $item[0] ); 7547 $delflag = true; 7548 } 7549 } 7550 7551 $user_is_known = false; 7552 7553 if ($pwdc) { 7554 $userpwd = new UserPwd($_SERVER['REMOTE_ADDR'], MAIN_DOMAIN, $pwdc); 7555 7556 if ($userpwd) { 7557 $pwd = $userpwd->getPwd(); 7558 $user_is_known = $userpwd->maskLifetime() >= 900; 7559 } 7560 else { 7561 $pwd = null; 7562 } 7563 } 7564 else { 7565 $pwd = null; 7566 } 7567 7568 $numdeletions = count( $delno ); 7569 if( !$numdeletions ) return; 7570 $flag = false; 7571 7572 if( !has_level( 'janitor' ) ) { 7573 $n = mysql_global_call( "select count(*)>%d from user_actions where ip=%d and action='delete' and time >= subdate(now(), interval 1 hour)", RENZOKU_DEL_HOURLY, ip2long( $_SERVER['REMOTE_ADDR'] ) ); 7574 list( $h ) = mysql_fetch_row( $n ); 7575 7576 if( $h ) { 7577 //check_fail_floodcheck($no); 7578 error(S_FLOOD_DEL); 7579 } 7580 7581 $n = mysql_global_call( "select count(*)>%d from user_actions where ip=%d and action='delete' and time >= subdate(now(), interval 1 day)", RENZOKU_DEL_DAILY, ip2long( $_SERVER['REMOTE_ADDR'] ) ); 7582 list( $h ) = mysql_fetch_row( $n ); 7583 7584 if( $h ) { 7585 //check_fail_floodcheck($no); 7586 error(S_FLOOD_DEL); 7587 } 7588 } 7589 7590 $rebuild = array(); // keys are pages that need to be rebuilt (0 is index, of course) 7591 7592 $lazy_rebuild = false; 7593 7594 if (isset($_POST['tool']) && $_POST['tool']) { 7595 $tool = $_POST['tool']; 7596 } 7597 else { 7598 $tool = null; 7599 } 7600 7601 // Manual, single post deletion. Only rebuilds one page if deleting a reply. 7602 if ($numdeletions == 1) { 7603 $resto = delete_post( $delno[0], $pwd, $onlyimgdel, 0, 1, $numdeletions == 1, false, false, $tool, $user_is_known ); 7604 if ($resto) { 7605 $rebuild[$resto] = 1; 7606 calculate_indexes_to_rebuild($resto); 7607 $lazy_rebuild = true; 7608 } 7609 } 7610 // Other (multi, automatic, etc...) 7611 else { 7612 for( $i = 0; $i < $numdeletions; $i++ ) { 7613 $resto = delete_post( $delno[$i], $pwd, $onlyimgdel, 0, 1, $numdeletions == 1, false, false, $tool, $user_is_known ); 7614 if( $resto ) { 7615 $rebuild[$resto] = 1; 7616 } 7617 } 7618 } 7619 7620 if (!has_level('janitor')) { 7621 mysql_global_call("INSERT INTO user_actions (ip,board,action,postno,time) VALUES (%d,'%s','delete',%d,now())", ip2long( $_SERVER["REMOTE_ADDR"] ), BOARD_DIR, $delno[0]); 7622 } 7623 7624 if ($redirect) { 7625 if ($redirect_res) { 7626 resredir($redirect_res, 1, true); 7627 } 7628 else { 7629 updating_index(); 7630 } 7631 7632 fastcgi_finish_request(); 7633 } 7634 7635 rebuild_deletions($rebuild, $lazy_rebuild); 7636 } 7637 7638 function updatelog_remote( $no, $noidx ) 7639 { 7640 if( !has_level() ) die( '' ); // anti dos 7641 $no = intval( $no ); 7642 $noidx = !!$noidx; 7643 // FIXME, running this when $noidx is true breaks cross-thread quotelinks. 7644 updatelog( $no, $noidx ); 7645 } 7646 7647 function _print( $s, $echo = 1 ) 7648 { 7649 if( $echo ) { 7650 echo $s; 7651 7652 return; 7653 } 7654 7655 ob_flush(); 7656 flush(); 7657 7658 echo $s; 7659 echo str_repeat( ' ', 256 ) . "\n"; 7660 7661 ob_flush(); 7662 flush(); 7663 } 7664 7665 function fancystyle() 7666 { 7667 $style = <<<HTML 7668 <style type="text/css"> 7669 body { 7670 font-family: Helvetica, Arial, sans-serif; 7671 font-size: 12pt; 7672 } 7673 7674 h1 { 7675 margin: 0; 7676 padding: 0; 7677 } 7678 </style> 7679 HTML; 7680 7681 7682 return $style; 7683 } 7684 7685 function rebuild_catalog( $shutup = false ) 7686 { 7687 if( !has_level() ) die(); 7688 if( !$shutup ) { 7689 echo fancystyle(); 7690 echo '<h1>Rebuilding catalog...</h1>'; 7691 } 7692 7693 $start = microtime( true ); 7694 generate_catalog(); 7695 $time = round( microtime( true ) - $start, 6 ); 7696 7697 if( !$shutup ) { 7698 echo 'Done!<br><br>Rebuilding took ' . $time . ' seconds.<br><br>Redirecting to catalog...<br><br><meta http-equiv="refresh" content="5;URL=//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/catalog">'; 7699 die(); 7700 } 7701 } 7702 7703 function rebuild_boards_json() 7704 { 7705 if( !has_level() ) die(); 7706 echo fancystyle(); 7707 echo '<h1>Rebuilding boards.json...</h1>'; 7708 7709 $start = microtime( true ); 7710 $query = mysql_global_call( "SELECT dir as board,name as title FROM boardlist ORDER BY board ASC" ); 7711 7712 $boards = array(); 7713 7714 $host = 'https://sys.int'; 7715 7716 $post = array( 7717 'mode' => 'cataloginfo' 7718 ); 7719 7720 while( $row = mysql_fetch_assoc( $query ) ) { 7721 if( $row['board'] == 'vp' ) $row['title'] = 'Pokémon'; 7722 //cataloginfo 7723 $url = "$host/{$row['board']}/imgboard.php"; 7724 7725 $ch = rpc_start_request($url, $post, null, true); 7726 7727 $response = rpc_finish_request($ch, $error, $httperror); 7728 7729 if (!$response) { 7730 die( 'Could not generate info for /' . $row['board'] . '/; ' . $error ); 7731 } 7732 7733 $json = json_decode($response, true); 7734 7735 foreach( $json as $key => $val ) { 7736 if( $key != 'board' && ctype_digit( $val ) ) $val = (int)$val; 7737 $row[$key] = $val; 7738 } 7739 7740 $boards['boards'][] = $row; 7741 } 7742 7743 // Dump resulting json on /test/ 7744 if (BOARD_DIR === 'test') { 7745 echo '<br>'; 7746 echo json_encode($boards, JSON_HEX_AMP | JSON_PRETTY_PRINT); 7747 echo '<br>'; 7748 } 7749 else { 7750 $_json = json_encode($boards, JSON_HEX_AMP); 7751 print_page(BOARDS_ROOT . 'boards.json', $_json); 7752 } 7753 7754 $time = round( microtime( true ) - $start, 6 ); 7755 echo 'Done!<br><br>Rebuilding took ' . $time . ' seconds.<br><br>No redirect here boss. Off you go.'; 7756 die(); 7757 } 7758 7759 function rebuild( $all = 0 ) 7760 { 7761 global $rebuildall, $fwritetimer; 7762 if( !has_level() ) die( '' ); // anti dos 7763 7764 if (has_flag('developer')) { 7765 error_reporting(E_ALL); 7766 } 7767 7768 header( "Pragma: no-cache" ); 7769 _print(fancystyle()); 7770 $l = $all ? 'all' : 'missing'; 7771 7772 _print( "Rebuilding $l replies and pages... <a href=\"//boards." . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/\">Go back</a><br><br>\n" ); 7773 log_cache(); 7774 trim_db(); 7775 trim_archive(); 7776 mysql_board_lock( true ); 7777 $starttime = microtime( true ); 7778 $query = "SELECT no, resto FROM `" . SQLLOG . "` WHERE resto = 0 AND archived = 0 ORDER BY root DESC"; 7779 $treeline = mysql_board_call($query); 7780 if (!$treeline) { 7781 echo S_SQLFAIL; 7782 } 7783 mysql_board_unlock(); 7784 _print( "Writing...<br>\n" ); 7785 if( $all ) { 7786 while( list( $no, $resto ) = mysql_fetch_row( $treeline ) ) { 7787 if( !$resto ) { 7788 _print( "Writing No.$no... " ); 7789 updatelog( $no, 1 ); 7790 $ext = TEST_BOARD ? "rp cache: ".realpath_cache_size() : ""; 7791 _print( "<b>DONE!</b><br> $ext\n" ); 7792 } 7793 } 7794 _print( "Writing index pages... " ); 7795 updatelog(); 7796 _print( "<b>DONE!</b><br>" ); 7797 7798 if (ENABLE_CATALOG) { 7799 _print( "Writing catalog..." ); 7800 generate_catalog( true ); 7801 _print( "<b>DONE!</b><br>" ); 7802 } 7803 7804 if (ENABLE_ARCHIVE) { 7805 _print( "Writing archive..." ); 7806 rebuild_archive_list(); 7807 _print( "<b>DONE!</b><br>" ); 7808 } 7809 } 7810 7811 $totaltime = microtime( true ) - $starttime; 7812 $proctimer = $totaltime - $fwritetimer; 7813 7814 $peakmem = memory_get_peak_usage(true) / (1024*1024.0); 7815 $usedmem = memory_get_usage(true) / (1024*1024.0); 7816 7817 $redir = '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/'; 7818 7819 echo <<<END 7820 <br>Total running time (lock excluded): $totaltime seconds. 7821 <br>Composed of: 7822 <br>Time spent writing files: $fwritetimer seconds. 7823 <br>Time spent processing: $proctimer seconds. 7824 <br>Memory: Peak: $peakmem MB Final: $usedmem MB 7825 <br>Pages created. 7826 <br><br> 7827 END; 7828 /* 7829 if (!TEST_BOARD) { 7830 echo <<<END 7831 Redirecting back to board. 7832 <meta http-equiv="refresh" content="10;URL=$redir"> 7833 END; 7834 } 7835 */ 7836 } 7837 7838 function rebuild_after_deletion( $no ) 7839 { 7840 if( !has_level() ) die(); 7841 7842 mysql_board_lock( true ); 7843 7844 if( !$treeline = mysql_board_call( "SELECT no FROM `" . SQLLOG . "` WHERE no = %d", $no ) ) { 7845 mysql_board_unlock(); 7846 die( S_POSTGONE ); 7847 } 7848 7849 log_cache( 0, $no ); 7850 mysql_board_unlock(); 7851 7852 updatelog( $no, 1 ); 7853 7854 die( $no . ' Rebuilt OK!' ); 7855 } 7856 7857 function updating_index() 7858 { 7859 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 7860 echo "<!doctype html><head><meta http-equiv=\"refresh\" content=\"2;URL=$proto" 7861 . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/\"><title>" 7862 . S_UPDATING_INDEX . "</title></head><body><table style=\"font-family:times,serif;font-size:36pt;text-align:center;width:100%;height:300px;\"><td><strong>" 7863 . S_UPDATING_INDEX . "</strong></td></table>"; 7864 } 7865 7866 function require_request_method( $method ) 7867 { 7868 if (!isset($_SERVER['REMOTE_ADDR'])) return; 7869 $umethod = $_SERVER["REQUEST_METHOD"]; 7870 //$req = htmlspecialchars( $_REQUEST["mode"] ); 7871 if( $umethod == "OPTIONS" || ( $umethod == "HEAD" && $method == "GET" ) ) return; 7872 if( $umethod != $method ) { 7873 error( S_REJECTTEXTBAN ); 7874 } 7875 } 7876 7877 function get_catalog_info() { 7878 $arr = array( 7879 'ws_board' => (int)(CATEGORY == 'ws'), 7880 'per_page' => (int)DEF_PAGES, 7881 'pages' => (int)PAGE_MAX, 7882 'max_filesize' => ((int)MAX_KB) * 1024, 7883 'max_webm_filesize' => ((int)MAX_WEBM_FILESIZE) * 1024, 7884 'max_comment_chars' => (int)MAX_COM_CHARS, 7885 'max_webm_duration' => (int)MAX_WEBM_DURATION, 7886 'bump_limit' => (int)MAX_RES, 7887 'image_limit' => (int)MAX_IMGRES, 7888 'cooldowns' => array( 7889 'threads' => (int)RENZOKU3, 7890 'replies' => (int)RENZOKU, 7891 'images' => (int)RENZOKU2 7892 ) 7893 ); 7894 7895 if (defined('META_DESCRIPTION')) { 7896 $arr['meta_description'] = META_DESCRIPTION; 7897 } 7898 7899 if (SPOILERS) { 7900 $arr['spoilers'] = 1; 7901 if (SPOILER_NUM) { 7902 $arr['custom_spoilers'] = (int)SPOILER_NUM; 7903 } 7904 } 7905 7906 if (DISP_ID) { 7907 $arr['user_ids'] = 1; 7908 } 7909 7910 if (ENABLE_ARCHIVE) { 7911 $arr['is_archived'] = 1; 7912 } 7913 7914 if (CODE_TAGS) { 7915 $arr['code_tags'] = 1; 7916 } 7917 7918 if (SJIS_TAGS) { 7919 $arr['sjis_tags'] = 1; 7920 } 7921 7922 if (JSMATH) { 7923 $arr['math_tags'] = 1; 7924 } 7925 7926 if (SHOW_COUNTRY_FLAGS) { 7927 $arr['country_flags'] = 1; 7928 } 7929 7930 if (ENABLE_BOARD_FLAGS) { 7931 $arr['board_flags'] = get_board_flags_selector(); 7932 } 7933 7934 if (ENABLE_WEBM_AUDIO) { 7935 $arr['webm_audio'] = 1; 7936 } 7937 7938 if (MIN_W > 1) { 7939 $arr['min_image_width'] = (int)MIN_W; 7940 } 7941 7942 if (MIN_H > 1) { 7943 $arr['min_image_height'] = (int)MIN_H; 7944 } 7945 7946 if (TEXT_ONLY) { 7947 $arr['text_only'] = 1; 7948 $arr['require_subject'] = 1; 7949 } 7950 7951 if (FORCED_ANON) { 7952 $arr['forced_anon'] = 1; 7953 } 7954 7955 if (REQUIRE_SUBJECT) { 7956 $arr['require_subject'] = 1; 7957 } 7958 7959 if (ENABLE_PAINTERJS) { 7960 $arr['oekaki'] = 1; 7961 } 7962 7963 die(json_encode($arr)); 7964 } 7965 7966 /** 7967 * Generates context to append to thread links. 7968 */ 7969 function generate_href_context($sub, $com) { 7970 if (JANITOR_BOARD) { 7971 return ''; 7972 } 7973 7974 $context = ''; 7975 7976 if (strpos($sub, 'SPOILER<>') === 0) { 7977 $sub = substr($sub, 9); 7978 } 7979 7980 if ($sub !== '') { 7981 $context = cleanup_context_string($sub); 7982 } 7983 7984 if ($context === '' && $com !== '') { 7985 $context = $com; 7986 7987 if (strpos($context, '<br>') !== false) { 7988 $context = str_replace('<br>', "\n", $context); 7989 $has_br = true; 7990 } 7991 else { 7992 $has_br = false; 7993 } 7994 7995 $context = preg_replace('/(^|\s)https?:\/\/[^\s]{4,}/', '', $context); 7996 7997 if (strpos($context, '<span class="abbr">') !== false) { 7998 $context = preg_replace('/<span class="abbr">.*<\/table>/', '', $context); // ??? 7999 } 8000 if (strpos($context, '<strong') !== false) { 8001 $context = preg_replace('/<strong [^>]+>.*<\/strong>/', '', $context); // ??? 8002 } 8003 8004 if ($has_br) { 8005 $context = ltrim($context); 8006 $context = explode("\n", $context)[0]; 8007 } 8008 8009 $context = preg_replace('/<[^>]+>/', ' ', $context); 8010 $context = cleanup_context_string($context); 8011 } 8012 8013 8014 return $context; 8015 } 8016 8017 /** 8018 * Generates page title from subjects and comments 8019 * params must be already html-escaped. 8020 */ 8021 function generate_page_title($thread_id, $sub, $com) { 8022 if (JANITOR_BOARD) { 8023 return strip_tags(TITLE); 8024 } 8025 8026 if (UPLOAD_BOARD) { 8027 $sub = preg_replace('/^(\d+)\|/', '', $sub); 8028 } 8029 8030 $context = ''; 8031 8032 if (strpos($sub, 'SPOILER<>') === 0) { 8033 $sub = substr($sub, 9); 8034 } 8035 8036 if ($sub !== '') { 8037 $context = $sub; 8038 } 8039 8040 if ($context === '' && $com !== '') { 8041 if (SJIS_TAGS && strpos($com, '<span class="sjis"') !== false) { 8042 $com = preg_replace('/<span class="sjis".+?<\/span>/', '[SJIS]', $com); 8043 } 8044 $context = str_replace('<br>', ' ', $com); 8045 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8046 $context = mb_substr(strip_tags($context), 0, 50); 8047 $context = htmlspecialchars($context, ENT_QUOTES); 8048 } 8049 8050 if ($context === '') { 8051 $context = 'No.' . $thread_id; 8052 } 8053 8054 if (BOARD_DIR === 's4s') { 8055 return '[' . BOARD_DIR . '] - ' . $context; 8056 } 8057 else { 8058 return '/' . BOARD_DIR . '/ - ' . $context; 8059 } 8060 } 8061 8062 /** 8063 * Generates metatags from subjects and comments 8064 * params must be already html-escaped. 8065 */ 8066 function generate_page_metatags($sub, $com) { 8067 if (JANITOR_BOARD) { 8068 return null; 8069 } 8070 8071 $context = ''; 8072 8073 if (strpos($sub, 'SPOILER<>') === 0) { 8074 $sub = substr($sub, 9); 8075 } 8076 8077 if ($sub !== '') { 8078 $context = $sub; 8079 } 8080 8081 $ell = ''; 8082 8083 if ($context === '' && $com !== '') { 8084 $context = preg_replace('/(<br>|\s)+/', ' ', $com); 8085 $context = htmlspecialchars_decode(strip_tags($context), ENT_QUOTES); 8086 8087 if (mb_strlen($context) > 100) { 8088 $ell = '...'; 8089 $context = mb_substr($context, 0, 100); 8090 } 8091 } 8092 else { 8093 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8094 } 8095 8096 if (empty($context)) { 8097 return null; 8098 } 8099 else { 8100 $words = preg_split('/[[:punct:]\s]+/', $context); 8101 $keywords = ''; 8102 foreach ($words as $word) { 8103 if (strlen($word) > 3) { 8104 $keywords .= ',' . $word; 8105 } 8106 } 8107 } 8108 8109 $context = htmlspecialchars($context, ENT_QUOTES); 8110 $keywords = htmlspecialchars($keywords, ENT_QUOTES); 8111 8112 return array($context.$ell, $keywords); 8113 } 8114 8115 function cleanup_context_string($context) { 8116 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8117 8118 $context = strtolower(preg_replace('/[^a-zA-Z0-9\s]+/', '', $context)); 8119 8120 $length = 0; 8121 8122 $words = explode(' ', $context); 8123 8124 $context = array(); 8125 8126 foreach ($words as $word) { 8127 if ($word === '') { 8128 continue; 8129 } 8130 8131 $length += strlen($word) + 1; 8132 8133 if ($length > 50) { 8134 break; 8135 } 8136 8137 $context[] = $word; 8138 } 8139 8140 $context = implode('-', $context); 8141 8142 return htmlspecialchars($context, ENT_QUOTES); 8143 } 8144 8145 /** 8146 * Embedded data detection 8147 * Dies if the PNG or JPG file contains embedded data. 8148 * Returns true if the GIF file was modified, otherwise returns false. 8149 */ 8150 function cleanup_uploaded_file($file, $type) { 8151 $full_size = filesize($file); 8152 8153 // 50KB 8154 $max_delta = 51200; 8155 8156 switch ($type) { 8157 case '.png': 8158 $clean_size = get_clean_png_size($file); 8159 break; 8160 case '.jpg': 8161 if ($full_size < 204800) { 8162 return false; 8163 } 8164 $clean_size = get_clean_jpg_size($file); 8165 break; 8166 case '.gif': 8167 if ($full_size < 204800) { 8168 return false; 8169 } 8170 $clean_size = get_clean_gif_size($file); 8171 break; 8172 case '.swf': 8173 return true; // Why? 8174 default: 8175 return false; 8176 } 8177 8178 if ($clean_size === false) { 8179 return false; 8180 } 8181 8182 // PNGs can still fail the check even if no size delta is found 8183 if ($clean_size === -1) { 8184 if ($type === '.gif') { 8185 $file = escapeshellcmd($file); 8186 $res = system("/usr/local/bin/gifsicle --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1"); 8187 if ($res !== false) { 8188 return true; 8189 } 8190 else { 8191 return false; 8192 } 8193 } 8194 else { 8195 error(S_IMGCONTAINSFILE, $file); 8196 } 8197 } 8198 8199 $delta_size = $full_size - $clean_size; 8200 8201 if ($delta_size > $max_delta) { 8202 if ($type === '.gif') { 8203 $file = escapeshellcmd($file); 8204 $res = system("/usr/local/bin/gifsicle --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1"); 8205 if ($res !== false) { 8206 return true; 8207 } 8208 } 8209 else { 8210 error(S_IMGCONTAINSFILE, $file); 8211 } 8212 } 8213 8214 return false; 8215 } 8216 8217 // Returns the size of critical data or -1 if the file contains extensions. 8218 function get_clean_gif_size($file) { 8219 $file = escapeshellcmd($file); 8220 8221 $binary = '/usr/local/bin/gifsicle'; 8222 8223 $res = shell_exec("$binary --sinfo \"$file\" 2>&1"); 8224 8225 if ($res !== null) { 8226 $size = 0; 8227 8228 if (preg_match('/ extensions [0-9]+/', $res)) { 8229 return -1; 8230 } 8231 8232 if (preg_match_all('/compressed size ([0-9]+)/', $res, $m)) { 8233 foreach ($m[1] as $frame_size) { 8234 $size += (int)$frame_size; 8235 } 8236 8237 return $size; 8238 } 8239 } 8240 8241 return false; 8242 } 8243 8244 // Returns the number of bytes in critical chunks or -1 if too many IDAT chunks are found 8245 // Returns false on error 8246 function get_clean_png_size($file) { 8247 $file = escapeshellcmd($file); 8248 8249 $binary = '/usr/local/bin/pngcrush'; 8250 8251 $res = shell_exec("$binary -m 1 -n -v \"$file\" 2>&1 1>/dev/null"); 8252 8253 if ($res !== null) { 8254 if (preg_match('/Reading (?:iTXt|tEXt|zTXt) chunk,/', $res, $m)) { 8255 if (preg_match('/ [a-z]oo: /i', $res)) { 8256 return -1; 8257 } 8258 else if (preg_match('/ (?:Software|Creation Time): ([=a-zA-Z0-9]{10,})\n/', $res, $ct)) { 8259 $_b64 = base64_decode($ct[0]); 8260 if ($_b64 && preg_match('/[0-9]/', $_b64)) { 8261 write_to_event_log('png_link', $_SERVER['REMOTE_ADDR'], [ 8262 'board' => BOARD_DIR, 8263 'meta' => htmlspecialchars($res) 8264 ]); 8265 return -1; 8266 } 8267 } 8268 } 8269 8270 if (preg_match('/in critical chunks\s+=\s+([0-9]+)/', $res, $m)) { 8271 return (int)$m[1]; 8272 } 8273 } 8274 8275 return false; 8276 } 8277 8278 function get_clean_jpg_size($file) { 8279 $eof = false; 8280 8281 $img = fopen($file, 'rb'); 8282 8283 $data = fread($img, 2); 8284 8285 if ($data !== "\xff\xd8") { 8286 fclose($img); 8287 return false; 8288 } 8289 8290 while (!feof($img)) { 8291 $data = fread($img, 1); 8292 8293 if ($data !== "\xff") { 8294 continue; 8295 } 8296 8297 while (!feof($img)) { 8298 $data = fread($img, 1); 8299 8300 if ($data !== "\xff") { 8301 break; 8302 } 8303 } 8304 8305 if (feof($img)) { 8306 break; 8307 } 8308 8309 $byte = unpack('C', $data)[1]; 8310 8311 if ($byte === 217) { 8312 $eof = ftell($img); 8313 break; 8314 } 8315 8316 if ($byte === 0 || $byte === 1 || ($byte >= 208 && $byte <= 216)) { 8317 continue; 8318 } 8319 8320 $data = fread($img, 2); 8321 8322 $length = unpack('n', $data)[1]; 8323 8324 if ($length < 1) { 8325 break; 8326 } 8327 8328 fseek($img, $length - 2, SEEK_CUR); 8329 } 8330 8331 fclose($img); 8332 8333 return $eof; 8334 } 8335 8336 /** 8337 * Generates a "Pass User Since YEAR" string for pass users 8338 */ 8339 function get_since_4chan($pass_id) { 8340 $query = "SELECT UNIX_TIMESTAMP(purchase_date) FROM pass_users WHERE user_hash = '%s' ORDER BY purchase_date ASC LIMIT 1"; 8341 8342 $res = mysql_global_call($query, $pass_id); 8343 8344 if (!$res) { 8345 return 0; 8346 } 8347 8348 $row = mysql_fetch_row($res); 8349 8350 $ts = (int)$row[0]; 8351 8352 if (!$ts) { 8353 return 0; 8354 } 8355 8356 $ts = date('Y', $ts); 8357 8358 if (!$ts) { 8359 return 0; 8360 } 8361 8362 return (int)$ts; 8363 } 8364 /* 8365 // Halloween 2017 8366 function get_halloween_score($pass_id) { 8367 $query = "SELECT score FROM halloween_tricks WHERE user_hash = '%s' LIMIT 1"; 8368 8369 $res = mysql_global_call($query, $pass_id); 8370 8371 if (!$res) { 8372 return 0; 8373 } 8374 8375 $row = mysql_fetch_row($res); 8376 8377 if (!$row) { 8378 return 0; 8379 } 8380 8381 return (int)$row[0]; 8382 } 8383 8384 // Halloween 2017 8385 /* 8386 function get_halloween_dummy_pass($name) { 8387 if (!$name) { 8388 return ''; 8389 } 8390 8391 $hashed_bits = hash_hmac('sha1', $name, 'CIaPCUkJq9n3fskmKC06tquCV/2edWqbgBeY9pk7RlQ', true); 8392 8393 $hashed_name = base64_encode($hashed_bits); 8394 8395 if (!$hashed_name) { 8396 return ''; 8397 } 8398 8399 return '_' . substr($hashed_name, 0, 9); 8400 } 8401 8402 // Halloween 2017 8403 function process_halloween_score($com, $thread_id, $this_pass_id, $this_pwd, $pass_is_bannable) { 8404 if ($com === '') { 8405 return; 8406 } 8407 8408 $thread_id = (int)$thread_id; 8409 8410 if (!$thread_id) { 8411 return; 8412 } 8413 8414 if (preg_match_all('/>>([0-9]{4,})/', $com, $m) === 1) { 8415 $post_id = (int)$m[1][0]; 8416 8417 if (!$post_id || $post_id == $thread_id) { 8418 return; 8419 } 8420 8421 $query = 'SELECT host, pwd, 4pass_id FROM `' . SQLLOG . '` WHERE no = ' . $post_id . ' AND resto = ' . $thread_id; 8422 8423 $res = mysql_board_call($query); 8424 8425 if (!$res) { 8426 return; 8427 } 8428 8429 $row = mysql_fetch_assoc($res); 8430 8431 if (!$row) { 8432 return; 8433 } 8434 8435 // Check if same person 8436 if (!$row['4pass_id'] || $row['4pass_id'] == $this_pass_id || $row['pwd'] == $this_pwd || $row['host'] == $_SERVER['REMOTE_ADDR']) { 8437 return; 8438 } 8439 8440 $long_ip = ip2long($_SERVER['REMOTE_ADDR']); 8441 8442 if (!$long_ip) { 8443 return; 8444 } 8445 8446 // Check if already gave points 8447 $query = "SELECT 1 FROM `halloween_votes` WHERE long_ip = $long_ip AND board = '" . BOARD_DIR . "' AND post_id = $post_id"; 8448 8449 $res = mysql_global_call($query); 8450 8451 if (!$res) { 8452 return; 8453 } 8454 8455 if (mysql_num_rows($res) > 0) { 8456 return; 8457 } 8458 8459 // Check if the user is known 8460 if (!spam_filter_is_user_known($long_ip, BOARD_DIR, $pass_is_bannable ? $this_pwd : null)) { 8461 return; 8462 } 8463 8464 // Good to go 8465 $query = <<<SQL 8466 INSERT INTO halloween_tricks (user_hash, score) VALUES ('%s', 1) 8467 ON DUPLICATE KEY UPDATE score = score + 1 8468 SQL; 8469 8470 mysql_global_call($query, $row['4pass_id']); 8471 8472 $query = "INSERT INTO halloween_votes (long_ip, board, post_id) VALUES ($long_ip, '" . BOARD_DIR . "', $post_id)"; 8473 8474 mysql_global_call($query); 8475 } 8476 } 8477 8478 // Halloween 2017 8479 function decrease_halloween_score($post_id, $ratio = 0.75) { 8480 $post_id = (int)$post_id; 8481 8482 if (!$post_id) { 8483 return; 8484 } 8485 8486 $query = 'SELECT 4pass_id FROM `' . SQLLOG . '` WHERE no = ' . $post_id; 8487 8488 $res = mysql_board_call($query); 8489 8490 if (!$res) { 8491 return; 8492 } 8493 8494 $pass_id = mysql_fetch_row($res)[0]; 8495 8496 if (!$pass_id) { 8497 return; 8498 } 8499 8500 $query = "UPDATE halloween_tricks SET score = FLOOR(score * %.2f) WHERE user_hash = '%s' LIMIT 1"; 8501 8502 mysql_global_call($query, $ratio, $pass_id); 8503 } 8504 8505 // Halloween 2017 8506 function get_halloween_css_cls($trick_count) { 8507 if ($trick_count >= 1000) { 8508 return " n-jol-6"; 8509 } 8510 if ($trick_count >= 500) { 8511 return " n-jol-5"; 8512 } 8513 if ($trick_count >= 200) { 8514 return " n-jol-4"; 8515 } 8516 if ($trick_count >= 100) { 8517 return " n-jol-3"; 8518 } 8519 if ($trick_count >= 50) { 8520 return " n-jol-2"; 8521 } 8522 if ($trick_count >= 25) { 8523 return " n-jol-1"; 8524 } 8525 return ''; 8526 } 8527 */ 8528 8529 /** 8530 * Checks if the user has a recent ban request. 8531 * dies with an error if user needs to be blocked. 8532 */ 8533 function check_for_ban_request($ip, $pwd = null) { 8534 $time_lim = BLOCK_ON_BR_LEN; 8535 8536 $clauses = []; 8537 8538 $clauses[] = "host = '" . mysql_real_escape_string($ip) . "'"; 8539 8540 if ($pwd) { 8541 $clauses[] = "pwd = '" . mysql_real_escape_string($pwd) . "'"; 8542 } 8543 8544 $board_sql = mysql_real_escape_string(BOARD_DIR); 8545 8546 $clauses = implode(' OR ', $clauses); 8547 8548 $query = <<<SQL 8549 SELECT tpl_name, board, TIMESTAMPDIFF(MINUTE, NOW() - $time_lim, ts) diff 8550 FROM `ban_requests` 8551 WHERE ($clauses) 8552 AND (board = '$board_sql' OR global = 1) 8553 AND warn_req = 0 8554 AND (ts > DATE_SUB(NOW(), $time_lim) OR ban_template IN (1, 2, 123, 126)) 8555 LIMIT 1 8556 SQL; 8557 8558 $res = mysql_global_call($query); 8559 8560 if (!$res || mysql_num_rows($res) !== 1) { 8561 return false; 8562 } 8563 8564 $row = mysql_fetch_assoc($res); 8565 8566 $time = (int)$row['diff']; 8567 8568 $tpl_name = rtrim(preg_replace('/\[[^\]]+\]/', '', $row['tpl_name'])); 8569 8570 // Non-expiring blocks for some global templates 8571 if ($time < 0) { 8572 error(sprintf(S_BRBLOCKED_2, $tpl_name)); 8573 } 8574 8575 $str = $time <= 1 ? '1 minute' : "$time minutes"; 8576 8577 error(sprintf(S_BRBLOCKED, $tpl_name, $str)); 8578 } 8579 8580 /** 8581 * Checks if the IP is banned, also checks for ban evasion 8582 * return 0 for not banned, 1 for banned, 2 for warned 8583 * If the ban has expired, delete the banned thumbnail and de-activate the ban 8584 */ 8585 function check_for_ban($ip, $fields = array(), $thread_id = 0, $user_verified = false) { 8586 global $captcha_bypass; 8587 8588 // Skip IP bans when the user has a valid 4chan Pass 8589 $skip_ip_bans = isset($fields['4pass_id']) && $captcha_bypass; 8590 8591 if (!$skip_ip_bans) { 8592 $fields['host'] = $ip; 8593 } 8594 8595 $expired = []; 8596 8597 $is_banned = 0; 8598 8599 foreach ($fields as $key => $value) { 8600 $query =<<<SQL 8601 SELECT no, global, board, post_num, template_id, 4pass_id, admin, reason, 8602 UNIX_TIMESTAMP(now) as starts_on, UNIX_TIMESTAMP(length) as ends_on 8603 FROM banned_users 8604 WHERE active = 1 AND $key = '%s' 8605 SQL; 8606 8607 $result = mysql_global_call($query, $value); 8608 8609 // Not banned 8610 if (mysql_num_rows($result) < 1) { 8611 continue; 8612 } 8613 8614 while ($ban = mysql_fetch_assoc($result)) { 8615 $end = (int)$ban['ends_on']; 8616 8617 // Warning 8618 if ($end && ($end - (int)$ban['starts_on'] < 1)) { 8619 $is_banned = 2; 8620 break 2; 8621 } 8622 8623 // Ban has expired 8624 if ($end && $end <= $_SERVER['REQUEST_TIME']) { 8625 $expired[] = $ban; 8626 continue; 8627 } 8628 8629 // Skip GR14 bans for pass users 8630 if ($key == '4pass_id') { 8631 if ($ban['template_id'] == 124) { // Global 14 - Proxy, VPN, or Tor Node 8632 continue; 8633 } 8634 } 8635 8636 // Skip proxy autobans for verified users 8637 if ($user_verified && $key == 'host' && $ban['admin'] === 'Auto-ban') { 8638 if (strpos($ban['reason'], 'Proxy') !== false) { 8639 continue; 8640 } 8641 } 8642 8643 if ($ban['global'] || $ban['board'] == BOARD_DIR) { 8644 $is_banned = 1; 8645 break 2; 8646 } 8647 } 8648 } 8649 8650 // Cleanup expired bans 8651 if (!empty($expired)) { 8652 $salt = file_get_contents_cached(SALTFILE); 8653 8654 $expired_ids = []; 8655 8656 foreach ($expired as $ban) { 8657 $expired_ids[] = (int)$ban['no']; 8658 8659 $hash = sha1($ban['board'] . $ban['post_num'] . $salt); 8660 8661 $file_path = BANTHUMB_ROOT . $ban['board'] . '/' . $hash . 's.jpg'; 8662 8663 if (file_exists($file_path)) { 8664 unlink($file_path); 8665 } 8666 } 8667 8668 $lim = count($expired_ids); 8669 8670 $expired_ids = implode(',', $expired_ids); 8671 $query = "UPDATE banned_users SET active = 0, unbannedon = NOW(), unbannedby = 'expiration' WHERE no IN($expired_ids) LIMIT $lim"; 8672 $result = mysql_global_do($query); 8673 } 8674 8675 return $is_banned; 8676 } 8677 8678 /** 8679 * Strips tags from webms and repairs broken streams. 8680 * This is needed to prevent people from bypassing duration limits. 8681 * $file must be safe to use as shell argument 8682 */ 8683 function remux_webm($file, $format, $strip_metadata = true) { 8684 $binary = '/usr/local/bin/ffmpeg-mp4'; 8685 8686 $out_file = $file . '_tmpff'; 8687 8688 if ($strip_metadata) { 8689 $map_meta = '-map_metadata -1 '; 8690 } 8691 else { 8692 $map_meta = ''; 8693 } 8694 8695 if ($format !== 'webm' && $format !== 'mp4') { 8696 return false; 8697 } 8698 8699 // $file and $format must be safe for shell_exec 8700 shell_exec("$binary -f $format -i \"$file\" $map_meta-bitexact -c copy -f $format -y \"$out_file\""); 8701 8702 if (!file_exists($out_file)) { 8703 return false; 8704 } 8705 8706 $ret = rename($out_file, $file); 8707 8708 clearstatcache(true, $file); 8709 8710 return $ret; 8711 } 8712 8713 /** 8714 * Remuxes and validates webm file 8715 * Returns video info on success array(width, height, sar, extension) 8716 * Dies if the file is invalid or not acceptable 8717 * $file must be safe to use as shell argument 8718 */ 8719 function validate_webm($file, $ext) { 8720 $binary = '/usr/local/bin/ffprobe-mp4'; 8721 8722 if ($ext == '.webm') { 8723 $format = 'webm'; 8724 } 8725 else if ($ext == '.mp4') { 8726 $format = 'mp4'; 8727 } 8728 else { 8729 error(S_NOREC, $file); 8730 } 8731 8732 // Remux webm to strip extra data and repair broken streams 8733 if (!remux_webm($file, $format)) { 8734 error(S_NOREC, $file); 8735 } 8736 8737 // $file and $format must be safe for proc_open 8738 $cmd = "$binary -f $format -i \"$file\" -hide_banner -show_streams -show_format -of json"; 8739 8740 $desc = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ]; 8741 8742 $pipes = []; 8743 8744 $proc = proc_open($cmd, $desc, $pipes); 8745 8746 if ($proc === false || !is_resource($proc)) { 8747 error(S_FAILEDUPLOAD, $file); 8748 } 8749 8750 $stdout = stream_get_contents($pipes[1]); 8751 fclose($pipes[1]); 8752 8753 $stderr = stream_get_contents($pipes[2]); 8754 fclose($pipes[2]); 8755 8756 $status = proc_close($proc); 8757 8758 if ($status !== 0) { 8759 error(S_FAILEDUPLOAD, $file); 8760 } 8761 8762 // Check stderr 8763 if (stripos($stderr, 'invalid') !== false) { 8764 error(S_NOREC, $file); 8765 } 8766 8767 // Check stdout 8768 $res = json_decode($stdout, true); 8769 8770 if (json_last_error() !== JSON_ERROR_NONE) { 8771 error(S_FAILEDUPLOAD, $file); 8772 } 8773 8774 //print_r($res); 8775 8776 if ($res['format']['format_name'] === 'matroska,webm') { 8777 $format = 'webm'; 8778 } 8779 else if ($res['format']['format_name'] === 'mov,mp4,m4a,3gp,3g2,mj2') { 8780 $format = 'mp4'; 8781 } 8782 else { 8783 error(S_NOREC, $file); 8784 } 8785 8786 // container duration, can be forged but we remux the file to fix this 8787 $duration = (float)$res['format']['duration']; 8788 8789 if ($duration <= 0 || $duration > MAX_WEBM_DURATION) { 8790 error(S_VIDEOTOOLONG, $file); // Duration too long 8791 } 8792 8793 $has_audio = false; 8794 $video_dims = false; 8795 8796 foreach ($res['streams'] as $stream) { 8797 $type = $stream['codec_type']; 8798 8799 if ($type === 'audio') { 8800 if (!ENABLE_WEBM_AUDIO) { 8801 error(S_AUDIODISABLED, $file); // Audio streams are not allowed 8802 } 8803 8804 // Vorbis or Opus for webm audio 8805 if ($format === 'webm') { 8806 if ($stream['codec_name'] !== 'vorbis' && $stream['codec_name'] !== 'opus') { 8807 error(S_BADAUDIO, $file); // Bad audio stream 8808 } 8809 } 8810 // AAC for mp4 audio 8811 else if ($format === 'mp4') { 8812 if ($stream['codec_name'] !== 'aac') { 8813 error(S_BADAUDIO, $file); // Bad audio stream 8814 } 8815 } 8816 else { 8817 error(S_BADAUDIO, $file); // Bad audio stream 8818 } 8819 8820 $has_audio = true; 8821 } 8822 else if ($type === 'video') { 8823 if ($video_dims) { 8824 error(S_BADSTREAM, $file); // Too many video streams 8825 } 8826 8827 // VP8 or VP9 for webm video 8828 if ($format === 'webm') { 8829 if ($stream['codec_name'] !== 'vp8' && $stream['codec_name'] !== 'vp9') { 8830 error(S_BADVIDEO, $file); // Bad video stream 8831 } 8832 } 8833 // H264 for mp4 video 8834 else if ($format === 'mp4') { 8835 if ($stream['codec_name'] !== 'h264') { 8836 error(S_BADVIDEO, $file); // Bad video stream 8837 } 8838 8839 // Reject 10 bit streams 8840 if ($stream['bits_per_raw_sample'] > 8) { 8841 error(S_NOREC, $file); 8842 } 8843 8844 // Only accept yuv420p streams 8845 if ($stream['pix_fmt'] !== 'yuv420p') { 8846 error(S_NOREC, $file); 8847 } 8848 } 8849 else { 8850 error(S_BADVIDEO, $file); // Bad video stream 8851 } 8852 8853 $width = (int)$stream['width']; 8854 $height = (int)$stream['height']; 8855 8856 if (!$width || !$height || $width > MAX_WEBM_DIMENSION || $height > MAX_WEBM_DIMENSION) { 8857 error(S_TOOLARGERES, $file); // Dimensions too big 8858 } 8859 8860 $sar = null; 8861 8862 if (isset($stream['sample_aspect_ratio'])) { 8863 $tmp_sar = explode(':', $stream['sample_aspect_ratio']); 8864 8865 $tmp_sar[0] = (int)$tmp_sar[0]; 8866 $tmp_sar[1] = (int)$tmp_sar[1]; 8867 8868 if ($tmp_sar[1] && $tmp_sar[0] !== $tmp_sar[1]) { 8869 $tmp_sar = $tmp_sar[0] / $tmp_sar[1]; 8870 8871 if ($tmp_sar < 2 && $tmp_sar > 0.5) { 8872 $sar = $tmp_sar; 8873 } 8874 } 8875 } 8876 8877 $video_dims = array($width, $height, $sar); 8878 } 8879 else { 8880 error(S_BADSTREAM, $file); // Bad stream 8881 } 8882 } 8883 8884 if (!$video_dims) { 8885 error(S_NOVIDEOSTREAM, $file); // No video streams 8886 } 8887 8888 return $video_dims; 8889 } 8890 8891 /** 8892 * Generates thumbnails for webm files 8893 * Returns the thumbnail filename on success. 8894 * Returns false if the thumbnail couldn't be generated. 8895 */ 8896 function thumb_webm($file, $ext) { 8897 $binary = '/usr/local/bin/ffmpeg-mp4'; 8898 8899 $out_file = $file . '.tmp.jpg'; 8900 8901 if ($ext === '.webm') { 8902 $format = 'webm'; 8903 } 8904 else if ($ext === '.mp4') { 8905 $format = 'mp4'; 8906 } 8907 else { 8908 return false; 8909 } 8910 8911 // $file and $format must be safe for shell_exec 8912 $res = shell_exec("$binary -f $format -i \"$file\" -vframes 1 -an -y \"$out_file\" 2>&1"); 8913 8914 if (file_exists($out_file)) { 8915 return $out_file; 8916 } 8917 8918 quick_log_to( "/www/perhost/bad-upload.log", "webm failure on $file:\n$res"); 8919 8920 return false; 8921 } 8922 8923 /** 8924 * Contest banners 468x60 8925 */ 8926 function get_contest_banner() { 8927 $query = "SELECT file_id, file_ext, board FROM contest_banners WHERE is_live = 1 ORDER BY RAND() LIMIT 1"; 8928 8929 $res = mysql_global_call($query); 8930 8931 if (!$res) { 8932 return ''; 8933 } 8934 8935 $banner = mysql_fetch_assoc($res); 8936 8937 if (!$banner) { 8938 return ''; 8939 } 8940 8941 $img_url = STATIC_SERVER . "image/contest_banners/{$banner['file_id']}.{$banner['file_ext']}"; 8942 $link_url = '//boards.' . L::d($banner['board']) . '/' . $banner['board'] . '/'; 8943 8944 return '<div><a href="' . $link_url . '"><img alt="" src="' . $img_url . '"></a></div>'; 8945 } 8946 8947 /** 8948 * Get latest post number from /j/ 8949 * Returns a json { "no": 123 } 8950 */ 8951 8952 function get_last_post_no() { 8953 $no = 0; 8954 $query = "SELECT no FROM `j` ORDER BY no DESC LIMIT 1"; 8955 $res = mysql_board_call($query); 8956 if ($res) { 8957 if ($row = mysql_fetch_row($res)) { 8958 $no = (int)$row[0]; 8959 } 8960 } 8961 echo "{\"no\":$no}"; 8962 } 8963 8964 /** 8965 * Deletes partial jsons for all live threads 8966 */ 8967 function purge_json_tails() { 8968 $query = 'SELECT no FROM `' . SQLLOG . '` WHERE resto = 0 AND archived = 0'; 8969 8970 $res = mysql_board_call($query); 8971 8972 if (!$res) { 8973 return false; 8974 } 8975 8976 while ($row = mysql_fetch_row($res)) { 8977 $thread_id = (int)$row[0]; 8978 update_json_tail_deletion($thread_id, true); 8979 } 8980 8981 return true; 8982 } 8983 8984 /** 8985 * Deletes, if necessary, the partial json tail file after post deletion. 8986 */ 8987 function update_json_tail_deletion($thread_id, $force = false) { 8988 if (!JSON_TAIL_SIZE && !$force) { 8989 return false; 8990 } 8991 8992 $tail_size = $force ? 0 : get_json_tail_size($thread_id); 8993 8994 if (!$tail_size) { 8995 $fname = RES_DIR . $thread_id . '-tail.json'; 8996 8997 if (USE_GZIP) { 8998 $fname = "$fname.gz"; 8999 } 9000 9001 if (file_exists($fname)) { 9002 return unlink($fname); 9003 } 9004 } 9005 9006 return false; 9007 } 9008 9009 /** 9010 * Returns the number of posts in the partial -tail json. 9011 * 0 if no tail json is available 9012 */ 9013 function get_json_tail_size($thread_id) { 9014 global $log; 9015 9016 $tail_size = (int)JSON_TAIL_SIZE; 9017 9018 if (!$tail_size || !isset($log[$thread_id])) { 9019 return 0; 9020 } 9021 9022 $th = $log[$thread_id]; 9023 9024 if ($th['sticky'] && $th['undead']) { 9025 $tail_size = $tail_size * 2; 9026 } 9027 9028 $post_count = count($th['children']); 9029 9030 if ($post_count >= $tail_size * 2) { 9031 return $tail_size; 9032 } 9033 else { 9034 return 0; 9035 } 9036 } 9037 9038 /** 9039 * Test function for mobile image resizing 9040 */ 9041 function resize_mobile_image($path, $w, $h, $fsize, $tim, $ext) { 9042 if ($ext !== '.jpg' && $ext !== '.png') { 9043 return false; 9044 } 9045 9046 $MAX_W = 1024; 9047 $MAX_H = 1024; 9048 $MAX_PXL = 524288; 9049 $MAX_PNG_BYTES = 524288; 9050 9051 if ($ext === '.png' && $fsize <= $MAX_PNG_BYTES) { 9052 return; 9053 } 9054 9055 if (($w > $MAX_W || $h > $MAX_H) && $w * $h > $MAX_PXL) { 9056 $jpeg_quality = 80; 9057 9058 $memory_limit_increased = false; 9059 9060 if ($w * $h > 3000000) { 9061 $memory_limit_increased = true; 9062 ini_set('memory_limit', memory_get_usage() + $w * $h * 15); 9063 } 9064 9065 if ($ext === '.jpg') { 9066 $img_in = ImageCreateFromJPEG($path); 9067 } 9068 else { 9069 $img_in = ImageCreateFromPNG($path); 9070 } 9071 9072 if (!$img_in) { 9073 error(S_FAILEDUPLOAD . ' (rmi)', $path); 9074 } 9075 9076 $ratio = $w / $h; 9077 9078 if ($ratio > 1) { 9079 $out_w = $MAX_W; 9080 $out_h = round($MAX_W / $ratio); 9081 } 9082 else { 9083 $out_w = round($MAX_H * $ratio); 9084 $out_h = $MAX_H; 9085 } 9086 9087 $img_out = ImageCreateTrueColor($out_w, $out_h); 9088 9089 ImageCopyResampled($img_out, $img_in, 0, 0, 0, 0, $out_w, $out_h, $w, $h); 9090 ImageDestroy($img_in); 9091 9092 $out_path = IMG_DIR . $tim . 'm.jpg'; 9093 ImageJPEG($img_out, $out_path, $jpeg_quality); 9094 ImageDestroy($img_out); 9095 9096 if ($memory_limit_increased) { 9097 ini_restore('memory_limit'); 9098 } 9099 9100 return $out_path; 9101 } 9102 } 9103 9104 /** 9105 * Returns the number of unique IPs for a given thread id 9106 * $thread_id needs to be cached in $log. 9107 */ 9108 function get_unique_ip_count($thread_id) { 9109 global $log; 9110 9111 if (!isset($log[$thread_id]) || $log[$thread_id]['archived']) { 9112 return false; 9113 } 9114 9115 $posts = $log[$thread_id]['children']; 9116 9117 if (empty($posts)) { 9118 return 1; 9119 } 9120 9121 $ip_map = array(); 9122 $ip_count = 1; 9123 $ip_map[$log[$thread_id]['host']] = true; 9124 9125 foreach ($posts as $pid => $val) { 9126 if (!isset($log[$pid])) { 9127 continue; 9128 } 9129 $post = $log[$pid]; 9130 if (!isset($ip_map[$post['host']])) { 9131 ++$ip_count; 9132 $ip_map[$post['host']] = true; 9133 } 9134 } 9135 9136 return $ip_count; 9137 } 9138 9139 function generate_del_pwd() { 9140 return '_' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 32); 9141 } 9142 9143 function get_hashed_mod_name($name) { 9144 if (!$name) { 9145 die('Internal Server Error (ghmn0)'); 9146 } 9147 9148 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 9149 9150 if (!$admin_salt) { 9151 die('Internal Server Error (ghmn1)'); 9152 } 9153 9154 $hashed_bits = hash_hmac('sha256', $name, $admin_salt, true); 9155 9156 $hashed_name = base64_encode($hashed_bits); 9157 9158 if (!$hashed_name) { 9159 die('Internal Server Error (ghmn2)'); 9160 } 9161 9162 return $hashed_name; 9163 } 9164 9165 function create_memcached_instance() { 9166 $m = new Memcached(); 9167 //$m->setOption(Memcached::OPT_TCP_NODELAY, true); 9168 $m->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 1); 9169 $m->setOption(Memcached::OPT_SEND_TIMEOUT, 500000); // 500ms 9170 $m->setOption(Memcached::OPT_RECV_TIMEOUT, 500000); // 500ms 9171 $m->addServer(MEMCACHED_HOST, MEMCACHED_PORT); 9172 return $m; 9173 } 9174 9175 function forcearchive() { 9176 if (!has_level()) { 9177 error("Can't let you do that."); 9178 } 9179 9180 if (!ENABLE_ARCHIVE) { 9181 error('Archives are disabled on this board.'); 9182 } 9183 9184 if (!isset($_POST['id'])) { 9185 error('Bad Request.'); 9186 } 9187 9188 $tid = (int)$_POST['id']; 9189 9190 $query = 'SELECT resto, sticky, archived, no, name, sub, com, filename, ext FROM `%s` WHERE no = %d'; 9191 $res = mysql_board_call($query, BOARD_DIR, $tid); 9192 9193 if (!$res) { 9194 error('Database error.'); 9195 } 9196 9197 $thread = mysql_fetch_assoc($res); 9198 9199 if (!$thread || $thread['resto']) { 9200 error('Thread not found.'); 9201 } 9202 9203 if ($thread['archived']) { 9204 error('This thread is already archived.'); 9205 } 9206 9207 if ($thread['sticky']) { 9208 error(S_MAYNOTDELSTICKY); 9209 } 9210 9211 archive_thread($tid); 9212 9213 // Log the action 9214 $action_log_post = array( 9215 'no' => $thread['no'], 9216 'name' => $thread['name'], 9217 'sub' => $thread['sub'], 9218 'com' => $thread['com'], 9219 'filename' => $thread['filename'], 9220 'ext' => $thread['ext'] 9221 ); 9222 9223 log_mod_action(3, $action_log_post); 9224 9225 if (ENABLE_JSON_THREADS) { 9226 generate_board_archived_json(); 9227 } 9228 9229 updating_index(); 9230 } 9231 9232 // Called remotely by other tools 9233 function rebuild_threads_by_id() { 9234 header('Content-Type: text/plain'); 9235 9236 if (!isset($_POST['ids']) || !is_array($_POST['ids']) || empty($_POST['ids'])) { 9237 echo '0'; 9238 return; 9239 } 9240 9241 $live_ids = array(); 9242 9243 // Rebuild archived threads first 9244 foreach ($_POST['ids'] as $id) { 9245 $id = (int)$id; 9246 9247 if (!$id) { 9248 continue; 9249 } 9250 9251 $query = "SELECT archived FROM `" . SQLLOG . "` WHERE no = $id LIMIT 1"; 9252 $res = mysql_board_call($query); 9253 9254 if (!$res) { 9255 echo '0'; 9256 return; 9257 } 9258 9259 if (mysql_fetch_row($res)[0] === '1') { 9260 rebuild_archived_thread($id); 9261 } 9262 else { 9263 $live_ids[] = $id; 9264 } 9265 } 9266 9267 // Rebuild live threads 9268 if (!empty($live_ids)) { 9269 9270 foreach ($live_ids as $id) { 9271 updatelog($id, 1); 9272 } 9273 9274 if (STATIC_REBUILD) { 9275 return; 9276 } 9277 9278 updatelog(0, 0); // rebuild indexes 9279 } 9280 9281 echo '1'; 9282 } 9283 9284 function rebuild_archive_list($print = false) { 9285 $board = BOARD_DIR; 9286 9287 $html = ''; 9288 9289 $maxlen = 100; 9290 9291 $max_age_in_days = 3; 9292 9293 $hour_clause = $max_age_in_days * 24; 9294 9295 $thread_limit = 3000; 9296 9297 $query = <<<SQL 9298 SELECT no, sub, com 9299 FROM `$board` 9300 WHERE archived = 1 AND resto = 0 AND root >= DATE_SUB(NOW(), INTERVAL $hour_clause HOUR) 9301 ORDER BY root DESC 9302 LIMIT $thread_limit 9303 SQL; 9304 9305 $res = mysql_board_call($query); 9306 9307 $thread_count = mysql_num_rows($res); 9308 9309 head($html, 0, 0, 0, 0, true); 9310 9311 $html .= '<div class="navLinks mobile"> 9312 <span class="mobileib button"><a href="/' . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a></span> <span class="mobileib button"><a href="/' . BOARD_DIR . '/catalog">' . S_CATALOG . '</a></span> <span class="mobileib button"><a href="#bottom">' . S_BOTTOM . '</a></span> 9313 </div>'; 9314 9315 $html .= '<hr class="desktop"> 9316 <div class="navLinks desktop"> 9317 [<a href="/' . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#bottom">' . S_BOTTOM . '</a>] 9318 </div><hr>'; 9319 9320 $html .= '<h4 class="center">Displaying ' . number_format($thread_count) . ' expired thread' 9321 . (!$thread_count || $thread_count > 1 ? 's' : '') . ' from the past ' . $max_age_in_days . ' day' 9322 . ($max_age_in_days > 1 ? 's' : '') . '</h4> 9323 <table id="arc-list" class="flashListing"><thead><tr> 9324 <td class="postblock">No.</td> 9325 <td class="postblock">Excerpt</td> 9326 <td class="postblock"></td> 9327 </tr></thead><tbody>'; 9328 9329 while ($row = mysql_fetch_assoc($res)) { 9330 if (strpos($row['sub'], 'SPOILER<>') === 0) { 9331 $row['sub'] = substr($row['sub'], 9); 9332 } 9333 9334 if (!empty($row['sub'])) { 9335 if ($row['com'] !== '') { 9336 $teaser = '<b>' . $row['sub'] . ':</b> ' . $row['com']; 9337 } 9338 else { 9339 $teaser = $row['sub']; 9340 } 9341 } 9342 else { 9343 $teaser = $row['com']; 9344 } 9345 9346 $teaser = preg_replace('/(?:<br>)+/', ' ', str_replace('"', "'", $teaser)); 9347 9348 $href_context = generate_href_context($row['sub'], $row['com']); 9349 9350 if ($href_context !== '') { 9351 $href_context = "/$href_context"; 9352 } 9353 9354 $html .= '<tr> 9355 <td>' . $row['no'] . '</td> 9356 <td class="teaser-col">' 9357 . truncate_comment($teaser, $maxlen) . 9358 '</td> 9359 <td>[<a class="quotelink" href="/' . $board . '/thread/' 9360 . $row['no'] . $href_context . '">View</a>] 9361 </td> 9362 </tr>'; 9363 } 9364 9365 $html .= '</tbody></table><hr>'; 9366 9367 $html .= '<div class="navLinks navLinksBot desktop">[<a href="/' 9368 . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' 9369 . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#top">' 9370 . S_TOP . '</a>] </div><hr class="desktop">'; 9371 9372 $html .= '<div class="navLinks mobile"><span class="mobileib button"><a href="/' 9373 . BOARD_DIR . '/" accesskey="a">' 9374 . S_RETURN . '</a></span> <span class="mobileib button"><a href="/' 9375 . BOARD_DIR . '/catalog">' 9376 . S_CATALOG . '</a></span> <span class="mobileib button"><a href="#top">' 9377 . S_TOP . '</a></span></div><hr class="mobile">'; 9378 9379 if (AD_BOTTOM_ENABLE == 1) { 9380 $bottomad = ''; 9381 9382 if (defined('AD_BOTTOM_TEXT') && AD_BOTTOM_TEXT) { 9383 $bottomad .= '<div class="bottomad center ad-cnt">' 9384 . ad_text_for(AD_BOTTOM_TEXT) . '</div>' 9385 . (defined('AD_BOTTOM_PLEA') ? AD_BOTTOM_PLEA : ''); 9386 } 9387 9388 if ($bottomad) { 9389 $html .= "$bottomad<hr>"; 9390 } 9391 } 9392 9393 $html .= '<div class="bottomCtrl desktop">'; 9394 9395 if (!defined('CSS_FORCE')) { 9396 $html .= '<span class="stylechanger">Style: 9397 <select id="styleSelector"> 9398 <option value="Yotsuba New">Yotsuba</option> 9399 <option value="Yotsuba B New">Yotsuba B</option> 9400 <option value="Futaba New">Futaba</option> 9401 <option value="Burichan New">Burichan</option> 9402 <option value="Tomorrow">Tomorrow</option> 9403 <option value="Photon">Photon</option>'; 9404 9405 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 9406 $html .= '<option value="_special">Special</option>'; 9407 } 9408 9409 $html .= '</select> 9410 </span>'; 9411 } 9412 9413 $html .= '</div>'; 9414 9415 foot($html, false, true); 9416 9417 if ($print) { 9418 echo $html; 9419 } 9420 else { 9421 print_page(INDEX_DIR . 'archive'. PHP_EXT, $html); 9422 } 9423 } 9424 9425 function rebuild_syncframe_page($print = false) { 9426 $tpl_file = YOTSUBA_DIR . 'views/syncframe.html'; 9427 9428 if (!file_exists($tpl_file)) { 9429 die('Template file not found'); 9430 } 9431 9432 $html = file_get_contents($tpl_file); 9433 9434 if ($print) { 9435 die($html); 9436 } 9437 else { 9438 print_page(BOARDS_ROOT . 'syncframe' . PHP_EXT, $html); 9439 } 9440 } 9441 9442 function rebuild_search_page($print = false) { 9443 $html = ''; 9444 9445 // board select box 9446 9447 $board_select_html = '<select class="g-search-ctrl" id="js-sf-bf"><option value="">All Boards</option>'; 9448 9449 $query = 'SELECT dir, name FROM boardlist ORDER BY dir ASC'; 9450 9451 $res = mysql_global_call($query); 9452 9453 if (!$res) { 9454 error('Database Error (rsp0)'); 9455 } 9456 9457 while ($row = mysql_fetch_assoc($res)) { 9458 $board_select_html .= '<option value="' . $row['dir'] . '">/' . $row['dir'] . '/ - ' . htmlspecialchars($row['name'], ENT_QUOTES) . '</option>'; 9459 } 9460 9461 $board_select_html .= '</select>'; 9462 9463 // header --- 9464 9465 $cssVersion = $print ? CSS_VERSION_TEST : CSS_VERSION; 9466 $defaultcss = 'yotsubanew'; 9467 $mobilecss = 'yotsubamobile.' . $cssVersion . '.css'; 9468 9469 $styles = array( 9470 'Yotsuba New' => "yotsubanew.$cssVersion.css", 9471 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 9472 'Futaba New' => "futabanew.$cssVersion.css", 9473 'Burichan New' => "burichannew.$cssVersion.css", 9474 'Photon' => "photon.$cssVersion.css", 9475 'Tomorrow' => "tomorrow.$cssVersion.css" 9476 ); 9477 9478 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 9479 9480 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 9481 9482 foreach ($styles as $style => $stylecss) { 9483 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' . $stylecss . '" title="' . $style . '">'; 9484 } 9485 9486 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/' . $mobilecss . '">'; 9487 9488 $scriptjs = '<script type="text/javascript">var style_group = "nws_style";</script>'; 9489 9490 $testjs = $print ? 'test/core-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'core.min.' . JS_VERSION_CORE . '.js'; 9491 $testextra = $print ? 'test/extension-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'extension.min.' . JS_VERSION_EXT . '.js'; 9492 9493 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testjs . '"></script>'; 9494 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testextra . '"></script>'; 9495 9496 if (defined('FAVICON')) { 9497 $favicon = '<link rel="shortcut icon" href="' . FAVICON . '">'; 9498 } 9499 else { 9500 $favicon = ''; 9501 } 9502 9503 $includenav = file_get_contents_cached(NAV_TXT); 9504 9505 $html .= '<!DOCTYPE html> 9506 <html> 9507 <head> 9508 <meta charset="utf-8"> 9509 <meta name="robots" content="' . META_ROBOTS . '"> 9510 <meta name="description" content="4chan Search"> 9511 <meta name="keywords" content="4chan,search"> 9512 <meta name="viewport" content="width=device-width,initial-scale=1"> 9513 ' . $favicon . ' 9514 ' . $css . ' 9515 <title>Search 4chan</title>' . $scriptjs . 9516 '</head> 9517 <body class="is_search">' . $stylejs . $includenav . 9518 '<div class="boardBanner"> 9519 <div class="boardTitle">4chan Search</div> 9520 </div>'; 9521 9522 // --- 9523 9524 $html .= '<hr class="desktop"><form action="#" id="g-search-form"><input placeholder="Search" class="g-search-ctrl" id="js-sf-qf" type="text">' . $board_select_html . '<button class="g-search-ctrl" id="js-sf-btn">Search</button></form>'; 9525 9526 $html .= '<hr> 9527 <form name="delform" id="delform"> 9528 <div class="board">'; 9529 9530 $html .= '</div><hr>'; 9531 9532 $html .= '<div class="bottomCtrl desktop">'; 9533 9534 if (!defined('CSS_FORCE')) { 9535 $html .= '<span class="stylechanger">Style: 9536 <select id="styleSelector"> 9537 <option value="Yotsuba New">Yotsuba</option> 9538 <option value="Yotsuba B New">Yotsuba B</option> 9539 <option value="Futaba New">Futaba</option> 9540 <option value="Burichan New">Burichan</option> 9541 <option value="Tomorrow">Tomorrow</option> 9542 <option value="Photon">Photon</option>'; 9543 9544 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 9545 $html .= '<option value="_special">Special</option>'; 9546 } 9547 9548 $html .= '</select> 9549 </span>'; 9550 } 9551 9552 $html .= '</div></form>'; 9553 9554 foot($html); 9555 9556 if ($print) { 9557 echo $html; 9558 } 9559 else { 9560 print_page(BOARDS_ROOT . 'globalsearch' . PHP_EXT, $html); 9561 } 9562 } 9563 9564 /** 9565 * Enforce the maximum number of allowed threads per user, per board. 9566 * error() if limit has been reached 9567 */ 9568 function validate_user_thread_limit($ip, $password = null, $pass_id = null) { 9569 $clauses = array(); 9570 9571 $clauses[] = "host = '" . mysql_real_escape_string($ip) . "'"; 9572 9573 if ($password) { 9574 $clauses[] = "pwd = '" . mysql_real_escape_string($password) . "'"; 9575 } 9576 9577 if ($pass_id) { 9578 $clauses[] = "4pass_id = '" . mysql_real_escape_string($pass_id) . "'"; 9579 } 9580 9581 $ts = $_SERVER['REQUEST_TIME'] - ((int)MAX_USER_THREADS_PERIOD * 3600); 9582 9583 $clauses = implode(' OR ', $clauses); 9584 9585 $query = 'SELECT COUNT(*) FROM `' . SQLLOG 9586 . "` WHERE resto = 0 AND archived = 0 AND time > $ts AND ($clauses)"; 9587 9588 $res = mysql_board_call($query); 9589 9590 if (!$res) { 9591 return true; 9592 } 9593 9594 $count = (int)mysql_fetch_row($res)[0]; 9595 9596 if ($count >= (int)MAX_USER_THREADS) { 9597 $plural = MAX_USER_THREADS > 1 ? 's' : ''; 9598 error(sprintf(S_TOOMANYTHREADS, MAX_USER_THREADS, $plural)); 9599 } 9600 9601 return true; 9602 } 9603 9604 function is_poster_op($host, $hashed_pwd, $resto) { 9605 $query = 'SELECT host, pwd FROM `%s` WHERE no = %d'; 9606 $res = mysql_board_call($query, SQLLOG, $resto); 9607 9608 if (!$res) { 9609 return false; 9610 } 9611 9612 $post = mysql_fetch_assoc($res); 9613 9614 if (!$post) { 9615 return false; 9616 } 9617 9618 return $post['host'] === $host || $post['pwd'] === $hashed_pwd; 9619 } 9620 9621 function spam_filter_check_qa_bot($board, $resto, $ip, $country, $com, $captcha_resp) { 9622 if (preg_match('/Edge|Safari|WebKit|Firefox|Mozilla/', $_SERVER['HTTP_USER_AGENT']) && $captcha_resp['hostname'] === 'boards.4chan.org') { 9623 return true; 9624 } 9625 9626 // Check if IP is known 9627 $long_ip = ip2long($ip); 9628 9629 if (spam_filter_is_user_known($long_ip)) { 9630 return false; 9631 } 9632 9633 if (!preg_match('/Edge|Mobile/', $_SERVER['HTTP_USER_AGENT']) && preg_match('/WebKit/', $_SERVER['HTTP_USER_AGENT']) !== preg_match('/WebKit/', $_SERVER['HTTP_CONTENT_TYPE'])) { 9634 return true; 9635 } 9636 9637 $bot_countries = array( 9638 'AD','AE','AF','AG','AI','AL','AM','AN','AO','AR','AS','AW','AZ', 9639 'BB','BD','BF','BG','BH','BI','BJ','BM','BN','BO','BR','BS','BT','BV','BW','BY','BZ', 9640 'CC','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CX','CY','CZ', 9641 'DJ','DM','DO','DZ','EC','EE','EG','EH','ER','ET','FJ','FM','FO', 9642 'GA','GD','GE','GF','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GY', 9643 'HK','HM','HN','HT','HU','HR','ID','IL','IN','IO','IQ','IR','IS','JM','JO', 9644 'KE','KG','KH','KI','KM','KN','KR','KW','KY','KZ', 9645 'LA','LB','LC','LI','LK','LR','LS','LU','LY', 9646 'MA','MD','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MY','MZ','NA', 9647 'NE','NF','NG','NI','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PM','PN','PR','PS','PT','PW', 9648 'QA','RE','RS','RO','RU','RW','SA','SB','SC','SD','SH','SI','SJ','SK','SL','SM','SN','SO','SR','ST','SV','SY','SZ', 9649 'TC','TD','TF','TG','TJ','TM','TN','TO','TP','TR','TT','TV','TW','TZ','UG','UM','UY','UZ', 9650 'VA','VE','VC','VG','VI','VN','VU','WF','WS','YE','YT','ZA','ZM','ZR','ZW' 9651 ); 9652 9653 // Check country 9654 if (!in_array($country, $bot_countries)) { 9655 return false; 9656 } 9657 9658 if ($resto) { 9659 $query = 'SELECT time FROM %s WHERE resto = %d ORDER BY no DESC LIMIT 1'; 9660 9661 $res = mysql_board_call($query, $board, $resto); 9662 9663 if (!$res) { 9664 return false; 9665 } 9666 9667 $last_time = mysql_fetch_row($res); 9668 9669 if (!$last_time) { 9670 return false; 9671 } 9672 9673 $last_time = (int)$last_time[0]; 9674 9675 if ($_SERVER['REQUEST_TIME'] - $last_time < 14400) { 9676 return false; 9677 } 9678 } 9679 9680 return true; 9681 } 9682 9683 function log_qa_spam_filter($is_hit, $thread_id, $ip, $country, $captcha_resp) { 9684 $_bot_headers = ''; 9685 9686 foreach ($_SERVER as $_h_name => $_h_val) { 9687 if (substr($_h_name, 0, 5) == 'HTTP_') { 9688 $_bot_headers .= "$_h_name: $_h_val\n"; 9689 } 9690 } 9691 9692 $_bot_headers .= "_Captcha: " . $captcha_resp['hostname'] . "\n"; 9693 9694 $_bot_headers .= "_Country: $country\n"; 9695 9696 log_spam_filter_trigger($is_hit ? 'blocked_qa' : 'ok_qa', BOARD_DIR, $thread_id, $ip, 1, $_bot_headers); 9697 } 9698 9699 function log_spam_filter_trigger($action, $board, $thread_id, $ip, $arg_num, $meta = '') { 9700 $query = <<<SQL 9701 INSERT INTO event_log(`type`, `board`, `arg_num`, `thread_id`, `ip`, `meta`) 9702 VALUES('%s', '%s', %d, %d, '%s', '%s') 9703 SQL; 9704 9705 mysql_global_call($query, $action, $board, $arg_num, $thread_id, $ip, $meta); 9706 } 9707 9708 function preview_html() { 9709 if ($_SERVER['REQUEST_METHOD'] != 'POST') { 9710 updating_index(); 9711 return; 9712 } 9713 9714 if (!has_level('mod')) { 9715 updating_index(); 9716 return; 9717 } 9718 9719 if (!has_flag('html') && !has_flag('developer')) { 9720 updating_index(); 9721 return; 9722 } 9723 9724 header('Content-type: application/json'); 9725 9726 if (isset($_POST['com'])) { 9727 $html = $_POST['com']; 9728 } 9729 else { 9730 $data = array('status' => 'error', 'message' => 'Nothing to do.'); 9731 echo json_encode($data); 9732 return; 9733 } 9734 9735 $html = purify_html($html); 9736 9737 $data = array('status' => 'success', 'data' => $html); 9738 9739 echo json_encode($data); 9740 } 9741 9742 function purify_html($html) { 9743 static $purifier = null; 9744 9745 if ($purifier === null) { 9746 require_once 'lib/htmlpurifier/HTMLPurifier.standalone.php'; 9747 9748 $config = HTMLPurifier_Config::createDefault(); 9749 $config->set('Cache.DefinitionImpl', null); 9750 9751 $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); 9752 9753 $config->set('URI.AllowedSchemes', array('http' => true, 'https' => true)); 9754 $config->set('HTML.SafeIframe', true); 9755 $config->set('URI.SafeIframeRegexp', HTML_IFRAME_WHITELIST); 9756 $config->set('HTML.Allowed', HTML_WHITELIST); 9757 $config->set('CSS.AllowTricky', true); 9758 $config->set('CSS.Trusted', true); 9759 $config->set('Attr.AllowedFrameTargets', array('_blank')); 9760 9761 $def = $config->getHTMLDefinition(true); 9762 $def->addAttribute('iframe', 'allowfullscreen', 'Bool'); 9763 9764 $def->addElement('video', 'Block', 'Flow', 'Common', array( 9765 'controls' => 'Bool', 9766 'height' => 'Length', 9767 'width' => 'Length', 9768 'poster' => 'URI', 9769 'autoplay' => 'Bool', 9770 'loop' => 'Bool', 9771 'muted' => 'Bool', 9772 'src' => 'URI' 9773 )); 9774 9775 $css_def = $config->getDefinition('CSS'); 9776 9777 $css_def->info['color'] = new HTMLPurifier_AttrDef_CSS_Composite( 9778 array( 9779 new HTMLPurifier_AttrDef_Enum(array('transparent')), 9780 new HTMLPurifier_AttrDef_CSS_Color() 9781 ) 9782 ); 9783 9784 $purifier = new HTMLPurifier($config); 9785 9786 if (!$purifier) { 9787 error('Internal Server Error'); 9788 } 9789 } 9790 9791 return $purifier->purify($html); 9792 } 9793 9794 function count_thread_replies($board, $thread_id) { 9795 $thread_id = (int)$thread_id; 9796 9797 if ($thread_id <= 0) { 9798 return 0; 9799 } 9800 9801 $sql = "SELECT COUNT(*) as cnt FROM `%s` WHERE resto = $thread_id"; 9802 9803 $res = mysql_board_call($sql, $board); 9804 9805 if (!$res) { 9806 return 0; 9807 } 9808 9809 return (int)mysql_fetch_row($res)[0]; 9810 } 9811 9812 // TODO: remove later 9813 function check_safe_ua_sig($ua, $sig) { 9814 if (!$ua || !$sig) { 9815 return true; 9816 } 9817 9818 $thres = 3; 9819 9820 $ua_sig = "$ua.$sig"; 9821 9822 $sql = "SELECT 1 FROM event_log WHERE type = 'log_safe_ua' AND ua_sig = '%s' LIMIT $thres"; 9823 9824 $res = mysql_global_call($sql, $ua_sig); 9825 9826 if (!$res) { 9827 return true; 9828 } 9829 9830 if (mysql_num_rows($res) < $thres) { 9831 return false; 9832 } 9833 9834 return true; 9835 } 9836 9837 // April 2024 9838 function april_2024_parse_email($email) { 9839 if ($email[0] != '$') { 9840 return 0; 9841 } 9842 9843 $tag = substr(trim($email), 1); 9844 9845 $stocks = april_2024_get_stock_list(); 9846 9847 $idx = array_search($tag, $stocks); 9848 9849 if ($idx === false) { 9850 return 0; 9851 } 9852 9853 $count = april_2024_get_stock_count($tag); 9854 9855 if ($count < 10) { 9856 return 0; 9857 } 9858 9859 return 10000 + $idx; 9860 } 9861 9862 function april_2024_get_stock_list() { 9863 static $stocks = [ 9864 'PEPE', 'WOJK', 'ANIME', 'CHAD', 'CLOWN', 'LOL', 'SICP', 'AUTSM', 'BANE', 9865 'CIA', 'BOOB', 'RDDT', 'DESU', 'JANNY', 'GME', 'CHUCK', 'YTSB', 'GACHI' 9866 ]; 9867 9868 return $stocks; 9869 } 9870 9871 function april_2024_get_stock_from_s4p($since4pass) { 9872 if ($since4pass < 10000) { 9873 return false; 9874 } 9875 9876 $val = $since4pass - 10000; 9877 9878 if ($val < 0) { 9879 return false; 9880 } 9881 9882 $badges = april_2024_get_stock_list(); 9883 9884 if ($val >= 0 && $val < count($badges)) { 9885 return $badges[$val]; 9886 } 9887 else { 9888 return false; 9889 } 9890 } 9891 9892 function april_2024_get_name() { 9893 $net_worth = april_2024_get_net_worth(); 9894 9895 if ($net_worth < 500) { 9896 return 'Destitute Investor'; 9897 } 9898 else if ($net_worth < 1500) { 9899 return 'Helpless Investor'; 9900 } 9901 else if ($net_worth < 5000) { 9902 return 'Poor Investor'; 9903 } 9904 else if ($net_worth < 50000) { 9905 return 'Fledgling Investor'; 9906 } 9907 else if ($net_worth < 500000) { 9908 return 'Aspiring Investor'; 9909 } 9910 else if ($net_worth < 2000000) { 9911 return 'Rich Investor'; 9912 } 9913 else if ($net_worth < 5000000) { 9914 return 'Anonymous Magnate'; 9915 } 9916 else { 9917 return 'Anonymous Mogul'; 9918 } 9919 } 9920 9921 function april_2024_get_post_cls($since4pass) { 9922 $stock = april_2024_get_stock_from_s4p($since4pass); 9923 9924 if ($stock) { 9925 return " p-xa24-$stock"; 9926 } 9927 else { 9928 return ''; 9929 } 9930 } 9931 9932 function april_2024_get_name_badge($since4pass) { 9933 $stock = april_2024_get_stock_from_s4p($since4pass); 9934 9935 if ($stock) { 9936 return " <span data-tip=\"$stock\" class=\"n-xa24 n-xa24-$stock\"></span>"; 9937 } 9938 else { 9939 return ''; 9940 } 9941 } 9942 9943 function april_2024_get_stock_count($stock) { 9944 $userpwd = UserPwd::getSession(); 9945 9946 if (!$userpwd || $userpwd->isNew()) { 9947 return 0; 9948 } 9949 9950 $user_id = $userpwd->getPwd(); 9951 9952 $sql =<<<SQL 9953 SELECT SUM(amount) as amount FROM april_stock_users 9954 WHERE user_id = '%s' AND stock = '%s' 9955 SQL; 9956 9957 $res = mysql_global_call($sql, $user_id, $stock); 9958 9959 if (!$res) { 9960 return 0; 9961 } 9962 9963 $val = (int)mysql_fetch_row($res)[0]; 9964 9965 if ($val < 0) { 9966 $val = 0; 9967 } 9968 9969 return $val; 9970 } 9971 9972 function april_2024_get_net_worth() { 9973 $userpwd = UserPwd::getSession(); 9974 9975 if (!$userpwd || $userpwd->isNew()) { 9976 return 0; 9977 } 9978 9979 $user_id = $userpwd->getPwd(); 9980 9981 $sql =<<<SQL 9982 SELECT stock, SUM(amount) as amount FROM april_stock_users 9983 WHERE user_id = '%s' GROUP BY stock HAVING amount > 0 9984 SQL; 9985 9986 $res = mysql_global_call($sql, $user_id); 9987 9988 if (!$res) { 9989 return 0; 9990 } 9991 9992 $stocks = []; 9993 9994 while ($row = mysql_fetch_row($res)) { 9995 $stocks[$row[0]] = (int)$row[1]; 9996 } 9997 9998 $sql =<<<SQL 9999 SELECT stock, price FROM april_stock_prices 10000 ORDER BY id DESC LIMIT 30 10001 SQL; 10002 10003 $res = mysql_global_call($sql); 10004 10005 if (!$res) { 10006 return 0; 10007 } 10008 10009 $prices = []; 10010 10011 while ($row = mysql_fetch_row($res)) { 10012 if (isset($prices[$row[0]])) { 10013 continue; 10014 } 10015 $prices[$row[0]] = (int)$row[1]; 10016 } 10017 10018 $net_worth = $stocks['_']; 10019 10020 foreach ($stocks as $stock => $count) { 10021 if (isset($prices[$stock])) { 10022 $net_worth += ($prices[$stock] * $count); 10023 } 10024 } 10025 10026 return $net_worth; 10027 } 10028 10029 // --- 10030 10031 function clear_no_captcha_token() { 10032 setcookie('_ct', null, -3600, '/', '.' . L::d(BOARD_DIR)); 10033 } 10034 10035 function generate_no_captcha_token() { 10036 if (BOARD_DIR === 'pol' || BOARD_DIR === 'b' || BOARD_DIR === 'r9k' || BOARD_DIR === 'bant') { 10037 return false; 10038 } 10039 10040 $long_ip = ip2long($_SERVER['REMOTE_ADDR']); 10041 10042 if (!$long_ip) { 10043 return false; 10044 } 10045 10046 if (!spam_filter_is_user_known($long_ip, BOARD_DIR, null, 15)) { 10047 return false; 10048 } 10049 10050 $salt = file_get_contents_cached(SALTFILE); 10051 10052 if (!$salt) { 10053 return false; 10054 } 10055 10056 $time = $_SERVER['REQUEST_TIME']; 10057 10058 $msg = $_SERVER['REMOTE_ADDR'] . '.' . $time; 10059 10060 $msg = hash_hmac('sha1', $msg, $salt); 10061 10062 if (!$msg) { 10063 return false; 10064 } 10065 10066 $msg = substr($msg, 0, 20) . '.' . $time; 10067 10068 setcookie('_ct', $msg, $time + 300, '/', '.' . L::d(BOARD_DIR)); // 5 minutes 10069 } 10070 10071 function verify_no_captcha_token($token) { 10072 list($hash, $ts) = explode('.', $token); 10073 10074 $ts = (int)$ts; 10075 10076 if (!$hash || !$ts) { 10077 return false; 10078 } 10079 10080 if ($ts < $_SERVER['REQUEST_TIME'] - 300) { // 5 minutes 10081 return false; 10082 } 10083 10084 $salt = file_get_contents_cached(SALTFILE); 10085 10086 if (!$salt) { 10087 return false; 10088 } 10089 10090 $msg = $_SERVER['REMOTE_ADDR'] . '.' . $ts; 10091 10092 if (substr(hash_hmac('sha1', $msg, $salt), 0, 20) === $hash) { 10093 return true; 10094 } 10095 10096 return false; 10097 } 10098 10099 function get_random_real_name() { 10100 $first_name_nid = mt_rand(1, 1000); 10101 10102 if (mt_rand(0, 999) < 10) { 10103 $type = 2; 10104 } 10105 else { 10106 $type = 1; 10107 } 10108 10109 $query = "SELECT data FROM april_names WHERE nid = $first_name_nid AND type = $type"; 10110 $res = mysql_global_call($query); 10111 $first_name = mysql_fetch_row($res)[0]; 10112 10113 if (!$first_name) { 10114 $first_name = 'Alberto'; 10115 } 10116 10117 $last_name_nid = mt_rand(1, 1000); 10118 10119 $query = "SELECT data FROM april_names WHERE nid = $last_name_nid AND type = 3"; 10120 $res = mysql_global_call($query); 10121 $last_name = mysql_fetch_row($res)[0]; 10122 10123 if (!$last_name) { 10124 $last_name = 'Barbosa'; 10125 } 10126 10127 return "$first_name $last_name"; 10128 } 10129 10130 10131 function log_mod_action($action_type, $post, $vip_capcode = false) { 10132 $mask_shift = 128; 10133 $action_id = $mask_shift + $action_type; 10134 10135 $query =<<<SQL 10136 INSERT INTO actions_log (oldmask, newmask, postno, board, name, sub, com, filename, admin) 10137 VALUES (0, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s') 10138 SQL; 10139 10140 mysql_global_call($query, 10141 $action_id, 10142 $post['no'], 10143 BOARD_DIR, 10144 $post['name'], 10145 $post['sub'], 10146 $post['com'], 10147 $post['filename'] . $post['ext'], 10148 $vip_capcode === false ? $_COOKIE['4chan_auser'] : '' 10149 ); 10150 10151 return true; 10152 } 10153 10154 function get_request_xff() { 10155 $xff = $_SERVER['HTTP_X_FORWARDED_FOR']; 10156 10157 if (!$xff) { 10158 return false; 10159 } 10160 10161 if ($xff === $_SERVER['REMOTE_ADDR']) { 10162 return false; 10163 } 10164 10165 // For Cloudflare 10166 if (strpos($xff, ',') !== false) { 10167 $xff = explode(',', $xff); 10168 return end($xff); 10169 } 10170 else { 10171 return $xff; 10172 } 10173 } 10174 10175 function validate_otp() { 10176 if (!isset($_POST['otp']) || $_POST['otp'] == '') { 10177 error("Incorrect or expired OTP."); 10178 } 10179 10180 $otp = $_POST['otp']; 10181 10182 $query = "SELECT auth_secret FROM mod_users WHERE username = '%s' LIMIT 1"; 10183 10184 $res = mysql_global_call($query, $_COOKIE['4chan_auser']); 10185 10186 if (!$res) { 10187 error("Database error."); 10188 } 10189 10190 $user = mysql_fetch_assoc($res); 10191 10192 if (!$user || !$user['auth_secret']) { 10193 error("Incorrect or expired OTP."); 10194 } 10195 10196 require_once 'lib/GoogleAuthenticator.php'; 10197 10198 $ga = new PHPGangsta_GoogleAuthenticator(); 10199 10200 $dec_secret = auth_decrypt($user['auth_secret']); 10201 10202 if ($dec_secret === false) { 10203 error('Internal Server Error.'); 10204 } 10205 10206 if (!$ga->verifyCode($dec_secret, $otp, 1)) { 10207 error("Incorrect or expired OTP."); 10208 } 10209 } 10210 10211 function validate_csrf() { 10212 if ($_SERVER['REQUEST_METHOD'] != 'POST') { 10213 error('Bad Request.'); 10214 } 10215 10216 if (!isset($_COOKIE['_tkn']) || !isset($_POST['_tkn']) 10217 || $_COOKIE['_tkn'] == '' || $_POST['_tkn'] == '' 10218 || $_COOKIE['_tkn'] !== $_POST['_tkn']) { 10219 10220 if (!is_local()) { 10221 error('Bad Request.'); 10222 } 10223 } 10224 } 10225 10226 function validate_referer($strict = false) { 10227 if (!$strict && (!isset($_SERVER['HTTP_REFERER']) || $_SERVER['HTTP_REFERER'] == '')) { 10228 return; 10229 } 10230 10231 if (!preg_match('/^https?:\/\/([_a-z0-9]+)\.(4chan|4channel)\.org(\/|$)/', $_SERVER['HTTP_REFERER'])) { 10232 error('Bad Request.'); 10233 } 10234 } 10235 10236 function dev_make_remote_thumbnail() { 10237 if (!is_local()) { 10238 die('403'); 10239 } 10240 10241 $infile = $_FILES['file']['tmp_name']; 10242 $file_ext = $_POST['file_ext']; 10243 $src_width = $_POST['src_width']; 10244 $src_height = $_POST['src_height']; 10245 $th_width = $_POST['th_width']; 10246 $th_height = $_POST['th_height']; 10247 10248 if (!$infile || !$file_ext || !$src_width || !$src_height || !$th_width || !$th_height) { 10249 return; 10250 } 10251 10252 $jpeg_quality = 65; 10253 10254 switch ($file_ext) { 10255 case 'gif': 10256 $img_in = ImageCreateFromGIF($infile); 10257 break; 10258 case 'jpg': 10259 $img_in = ImageCreateFromJPEG($infile); 10260 break; 10261 case 'png': 10262 $img_in = ImageCreateFromPNG($infile); 10263 break; 10264 default : 10265 return; 10266 } 10267 10268 if (!$img_in) { 10269 return; 10270 } 10271 10272 $img_out = ImageCreateTrueColor($th_width, $th_height); 10273 10274 if (!$img_out) { 10275 return; 10276 } 10277 10278 ImageCopyResampled($img_out, $img_in, 0, 0, 0, 0, $th_width, $th_height, $src_width, $src_height); 10279 10280 ImageDestroy($img_in); 10281 10282 ImageJPEG($img_out, NULL, $jpeg_quality); 10283 10284 ImageDestroy($img_out); 10285 } 10286 10287 /*-----------Main-------------*/ 10288 switch( $mode ) { 10289 case 'make_remote_thumbnail': 10290 dev_make_remote_thumbnail(); 10291 die(); 10292 case 'purgejsontails': 10293 if (has_flag('developer')) { 10294 echo purge_json_tails() ? 'OK' : 'ERROR'; 10295 } 10296 die(); 10297 case 'listarchive': 10298 if (has_flag('developer')) { 10299 rebuild_archive_list(true); 10300 } 10301 die(); 10302 case 'search': 10303 if (has_flag('developer')) { 10304 rebuild_search_page(true); 10305 } 10306 die(); 10307 case 'rebuildsearchpage': 10308 if (has_flag('developer') || has_level('manager')) { 10309 rebuild_search_page(); 10310 echo 'done'; 10311 } 10312 die(); 10313 case 'rebuildsyncframepage': 10314 if (has_flag('developer') || has_level('manager')) { 10315 rebuild_syncframe_page(isset($_GET['print'])); 10316 echo 'done'; 10317 } 10318 die(); 10319 case 'rebuildarchivedthread': 10320 if (has_flag('developer') || has_level('manager')) { 10321 rebuild_archived_thread((int)$_GET['id']); 10322 echo 'done'; 10323 } 10324 die(); 10325 10326 case 'regist': 10327 case 'post': 10328 require_request_method( "POST" ); 10329 validate_referer(); 10330 new_post( $name, $email, $sub, $com, '', $pwd, $upfile, $upfile_name, $resto, $age, $filetag ); 10331 break; 10332 case 'report': 10333 report(); 10334 break; 10335 10336 case 'preview_html': 10337 preview_html(); 10338 break; 10339 10340 case 'rebuild': 10341 require_request_method( "GET" ); 10342 rebuild(); 10343 break; 10344 case 'rebuildall': 10345 rebuild( 1 ); 10346 break; 10347 10348 case 'rebuildadmin': 10349 rebuild_deletions( array($no => '1') ); 10350 echo '<span style="display: none;">Rebuilt OK!</span>'; // shut rpc up 10351 break; 10352 10353 case 'rebuildcatalog': 10354 if (ENABLE_CATALOG) { 10355 rebuild_catalog(); 10356 } 10357 break; 10358 10359 case 'rebuildboardsjson': 10360 if (has_flag('developer')) { 10361 rebuild_boards_json(); 10362 } 10363 break; 10364 10365 case 'rebuildthumb': 10366 rebuildallthumb(isset($_GET['archiveonly']) || isset($_ENV['archiveonly'])); 10367 break; 10368 10369 case 'cataloginfo': 10370 get_catalog_info(); 10371 break; 10372 10373 case 'admindel': case 'admindelete': 10374 user_delete( $no, $pwd ); 10375 echo "<meta http-equiv=\"refresh\" content=\"0;URL=admin.php\">"; 10376 break; 10377 case 'updatelog': 10378 updatelog_remote( $no, $noidx ); 10379 break; 10380 case 'nothing': 10381 break; 10382 case 'arcdel': 10383 require_request_method( "POST" ); 10384 validate_referer(); 10385 arcdel($no, true, $res); 10386 break; 10387 case 'usrdel': case 'delete': 10388 require_request_method( "POST" ); 10389 validate_referer(); 10390 user_delete( $no, $pwd, true, $res ); 10391 break; 10392 case 'rebuild_threads_by_id': 10393 require_request_method( "POST" ); 10394 if (is_local()) { 10395 rebuild_threads_by_id(); 10396 } 10397 else { 10398 updating_index(); 10399 } 10400 break; 10401 case 'forcearchive': 10402 require_request_method( "POST" ); 10403 validate_referer(); //validate_csrf(); 10404 forcearchive(); 10405 break; 10406 case 'copythreads': 10407 require_request_method( "GET" ); 10408 do_copy_threads(); 10409 break; 10410 case 'movethread': 10411 validate_csrf(); 10412 do_move_thread(); 10413 break; 10414 case 'latest': 10415 if (has_level('janitor')) { 10416 get_last_post_no(); 10417 } 10418 die(); 10419 case 'rake_post': 10420 //april_rake_commit(); 10421 die('0\nNo'); 10422 break; 10423 default: 10424 require_request_method( "GET" ); 10425 if( JANITOR_BOARD == 1 && !has_level( 'janitor' ) ) { 10426 die( '' ); 10427 } 10428 if( $res ) { 10429 resredir( $res ); 10430 } else { 10431 updating_index(); 10432 } 10433 }