/ imgboard.php
imgboard.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) error( S_MAYNOTDELSTICKY ); 2511 if( !has_level() ) error( S_MAYNOTDEL ); 2512 } 2513 2514 if( BOARD_DIR == 'vg' && !$row['resto'] && !$admin_ok && !$archived_deletion ) error( S_MAYNOTDEL ); 2515 2516 if( !$row['resto'] && !$automatic && !$admin_ok ) { 2517 foreach( $row['children'] as $child => $unused ) { 2518 if( $log[$child]['capcode'] != 'none' ) error( S_MAYNOTDEL ); 2519 } 2520 } 2521 2522 if( !$automatic && !$admin_ok && !$can_flood_delete ) { 2523 if ($user_is_known) { 2524 $_renzoku_del = RENZOKU_DEL; 2525 } 2526 else { 2527 $_renzoku_del = 600; // FIXME: 10 minutes 2528 } 2529 if( ( time() - (int)$row['time'] ) < $_renzoku_del ) { 2530 error(S_RENZOKU_DEL); 2531 } 2532 } 2533 2534 // User is authed staff 2535 if ($admin_ok && !IS_REBUILDD) { 2536 $auser = $_COOKIE['4chan_auser']; 2537 2538 // Use POSTed IP instead of the local one if the deletion was triggered via RPC 2539 if (isset($_POST['remote_addr']) && is_local()) { 2540 $remote_addr = $_POST['remote_addr']; 2541 } 2542 else { 2543 $remote_addr = $_SERVER['REMOTE_ADDR']; 2544 } 2545 2546 // Authed user is deleting a post that isn't his 2547 if (!$pass_ok && !$host_ok) { 2548 $adfsize = ( $row['fsize'] > 0 ) ? 1 : 0; 2549 $adname = str_replace( '</span> <span class="postertrip">!', '#', $row['name'] ); 2550 if( $imgonly ) { 2551 $imgonly = 1; 2552 } else { 2553 $imgonly = 0; 2554 } 2555 2556 if (isset($_POST['template_id'])) { 2557 $template_id = (int)$_POST['template_id']; 2558 } 2559 else { 2560 $template_id = 0; 2561 } 2562 2563 if ($post_exists && (!$automatic || $automatic === 2)) { 2564 validate_admin_cookies(); 2565 2566 if (!$tool || !in_array($tool, array('search', 'ban', 'ban-req', 'autopurge', 'threadban'))) { 2567 $tool = ''; 2568 } 2569 2570 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 ); 2571 } 2572 2573 // Clear the report queue if only the file is deleted 2574 if ($imgonly) { 2575 $query = "DELETE FROM reports WHERE board = '" . SQLLOG . "' AND no = " . (int)$resno; 2576 2577 $res = mysql_global_call($query); 2578 2579 $query = "DELETE FROM reports_for_posts WHERE board = '" . SQLLOG . "' AND postid = " . (int)$resno; 2580 2581 $res = mysql_global_call($query); 2582 } 2583 } 2584 // Staff member is deleting a post that is his, or other type of deletions 2585 else if (!$automatic || $automatic === 2) { 2586 if ($auser) { 2587 log_staff_event('staff_self_del', $auser, $remote_addr, $pwd, BOARD_DIR, $row); 2588 } 2589 else { 2590 log_staff_event('staff_auto_del', 'Auto-ban', $remote_addr, $pwd, BOARD_DIR, $row); 2591 } 2592 } 2593 } 2594 2595 $delete_children = $row['resto'] == 0 && $children && !$imgonly; 2596 2597 $restoq = $delete_children ? "OR (archived = 0 and resto=$resno) OR (archived = 1 and resto=$resno)" : ''; 2598 2599 if (UPLOAD_BOARD) { 2600 $up_col = ',filename'; 2601 } 2602 else { 2603 $up_col = ''; 2604 } 2605 2606 if (MOBILE_IMG_RESIZE) { 2607 $up_col .= ',m_img'; 2608 } 2609 2610 $result = mysql_board_call( "select no,resto,tim,ext$up_col from `" . SQLLOG . "` where no=$resno $restoq" ); 2611 2612 // Array of threads to update after one or more replies were deleted. 2613 $updated_threads = array(); 2614 // Array of post number for report and xff clearing 2615 $deleted_threads = array(); 2616 $deleted_replies = array(); 2617 2618 $purge_files = array(); 2619 2620 $img_webroot = 'http://i.4cdn.org/' . BOARD_DIR . '/'; 2621 2622 while( $delrow = mysql_fetch_array( $result ) ) { 2623 // delete 2624 if( $delrow['ext'] ) { 2625 if (UPLOAD_BOARD) { 2626 $delfile = IMG_DIR . $delrow['filename'] . $delrow['ext']; //path to delete 2627 @unlink($delfile); // delete image 2628 if( CLOUDFLARE_PURGE_ON_DEL ) { 2629 cloudflare_purge_url($img_webroot . rawurlencode($delrow['filename']) . $delrow['ext'], true); 2630 } 2631 } 2632 else { 2633 $delfile = IMG_DIR . $delrow['tim'] . $delrow['ext']; //path to delete 2634 $delthumb = THUMB_DIR . $delrow['tim'] . 's.jpg'; 2635 @unlink( $delfile ); // delete image 2636 @unlink( $delthumb ); // delete thumb 2637 2638 if (ENABLE_OEKAKI_REPLAYS && file_exists(IMG_DIR . $delrow['tim'] . '.tgkr')) { 2639 unlink(IMG_DIR . $delrow['tim'] . '.tgkr'); 2640 2641 if (CLOUDFLARE_PURGE_ON_DEL) { 2642 $purge_files[] = $img_webroot . $delrow['tim'] . '.tgkr'; 2643 } 2644 } 2645 2646 if (MOBILE_IMG_RESIZE && $delrow['m_img']) { 2647 @unlink(IMG_DIR . $delrow['tim'] . 'm.jpg'); // delete mobile 2648 } 2649 2650 if (CLOUDFLARE_PURGE_ON_DEL) { 2651 $purge_files[] = $img_webroot . $delrow['tim'] . $delrow['ext']; 2652 $purge_files[] = $img_webroot . $delrow['tim'] . 's.jpg'; 2653 2654 if (MOBILE_IMG_RESIZE && $delrow['m_img']) { 2655 $purge_files[] = $img_webroot . $delrow['tim'] . 'm.jpg'; 2656 } 2657 2658 } 2659 } 2660 } 2661 if( $imgonly ) { 2662 mysql_board_call( "UPDATE `" . SQLLOG . "` SET filedeleted=1,root=root,last_modified=%d WHERE no=%d", $_SERVER['REQUEST_TIME'], $delrow['no'] ); 2663 $log[$delrow['no']]['filedeleted'] = TRUE; 2664 2665 if ($delrow['resto']) { 2666 mysql_board_call( "UPDATE `" . SQLLOG . "` SET root=root,last_modified=%d WHERE no=%d", $_SERVER['REQUEST_TIME'], $delrow['resto'] ); 2667 if (isset($log[$delrow['resto']])) 2668 $log[$delrow['resto']]['last_modified'] = (int)$_SERVER['REQUEST_TIME']; 2669 } 2670 2671 //cloudflare_purge_by_basename(BOARD_DIR, $delrow['tim'] . $delrow['ext']); 2672 } 2673 else { 2674 // Thread 2675 if (!$delrow['resto']) { 2676 $thread_key = @array_search( $delrow['no'], $log['THREADS'] ); 2677 if( $thread_key !== false ) { 2678 unset( $log['THREADS'][$thread_key] ); 2679 } 2680 2681 if( USE_GZIP == 1 ) { 2682 @unlink( RES_DIR . $delrow['no'] . PHP_EXT . '.gz' ); 2683 @unlink( RES_DIR . $delrow['no'] . '.json.gz' ); 2684 } 2685 else { 2686 @unlink( RES_DIR . $delrow['no'] . PHP_EXT ); 2687 @unlink( RES_DIR . $delrow['no'] . '.json' ); 2688 } 2689 2690 update_json_tail_deletion($delrow['no'], true); 2691 2692 $deleted_threads[] = (int)$delrow['no']; 2693 } 2694 // Reply. Thread's last_modified field will need to be updated 2695 else if (!isset($updated_threads[$delrow['resto']])) { 2696 $updated_threads[$delrow['resto']] = true; 2697 2698 $deleted_replies[] = (int)$delrow['no']; 2699 } 2700 2701 unset( $log[$delrow['no']] ); 2702 } 2703 } 2704 2705 if (!empty($purge_files)) { 2706 cloudflare_purge_url($purge_files, true); 2707 } 2708 2709 // Updating last_modified field (threads) 2710 foreach ($updated_threads as $thread_id => $true) { 2711 mysql_board_call("UPDATE `".SQLLOG."` set root=root,last_modified=%d where no=%d", $_SERVER['REQUEST_TIME'], $thread_id); 2712 2713 if (isset($log[$thread_id])) 2714 $log[$thread_id]['last_modified'] = (int)$_SERVER['REQUEST_TIME']; 2715 2716 unset( $log[$thread_id]['children'][$delrow['no']] ); 2717 } 2718 2719 // Clearing reports and xff 2720 if ($deleted_replies) { 2721 $in_clause = 'IN(' . implode(',', $deleted_replies) . ')'; 2722 mysql_global_do("DELETE FROM reports WHERE board='" . BOARD_DIR . "' AND no " . $in_clause); 2723 mysql_global_do("DELETE FROM reports_for_posts WHERE board='" . BOARD_DIR . "' AND postid " . $in_clause); 2724 2725 if (SAVE_XFF) { 2726 mysql_global_do("UPDATE xff SET is_live = 0 WHERE board='" . BOARD_DIR . "' AND postno " . $in_clause); 2727 } 2728 } 2729 2730 if ($deleted_threads) { 2731 $in_clause = 'IN(' . implode(',', $deleted_threads) . ')'; 2732 mysql_global_do("DELETE FROM reports WHERE board='" . BOARD_DIR . "' AND (no $in_clause OR resto $in_clause)"); 2733 mysql_global_do("DELETE FROM reports_for_posts WHERE board='" . BOARD_DIR . "' AND (postid $in_clause OR threadid $in_clause)"); 2734 2735 if (SAVE_XFF) { 2736 mysql_global_do("UPDATE xff SET is_live = 0 WHERE board='" . BOARD_DIR . "' AND postno " . $in_clause); 2737 } 2738 } 2739 2740 // Halloween 2017 2741 /* 2742 if ($tool && defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky2017') { 2743 if ($tool === 'ban') { 2744 decrease_halloween_score($resno); 2745 } 2746 else if ($tool === 'ban-req') { 2747 decrease_halloween_score($resno, 0.90); 2748 } 2749 } 2750 */ 2751 2752 //delete from DB 2753 if( $delete_children ) // delete thread and children 2754 $result = mysql_board_call( "delete from `" . SQLLOG . "` where no=$resno or resto=$resno" ); 2755 elseif( !$imgonly ) // just delete the post 2756 $result = mysql_board_call( "delete from `" . SQLLOG . "` where no=$resno" ); 2757 2758 rpc_task(); 2759 if( $imgonly && $row['resto'] == 0 ) { 2760 return $resno; // return thread number to stop deletion silliness 2761 } 2762 2763 return $row['resto']; // so the caller can know what pages need to be rebuilt 2764 } 2765 2766 function rebuild_deletions($rebuild, $lazy_rebuild = false) 2767 { 2768 global $log; 2769 2770 foreach( $rebuild as $key => $val ) { 2771 log_cache( 0, $key ); 2772 if (!isset($log[$key]['children'])) { 2773 internal_error_log("rebuild_deletions", "missing children for OP /" . BOARD_DIR . "/$key"); 2774 continue; 2775 } 2776 updatelog( $key, 1 ); // leaving the second parameter as 0 rebuilds the index each time! 2777 update_json_tail_deletion($key); 2778 } 2779 2780 if( STATIC_REBUILD ) return; 2781 2782 updatelog(0, 0, $lazy_rebuild); // update the index page last 2783 } 2784 2785 /** 2786 * Removes old archived posts 2787 */ 2788 function trim_archive() { 2789 if (STATIC_REBUILD && !IS_REBUILDD) { 2790 return; 2791 } 2792 2793 if (!ARCHIVE_MAX_AGE) { 2794 return; 2795 } 2796 2797 $interval = (int)ARCHIVE_MAX_AGE; 2798 2799 $query = <<<SQL 2800 SELECT no FROM `%s` 2801 WHERE archived = 1 2802 AND resto = 0 2803 AND root < DATE_SUB(NOW(), INTERVAL $interval HOUR) 2804 SQL; 2805 2806 $res = mysql_board_call($query, BOARD_DIR); 2807 2808 if (!$res || !mysql_num_rows($res)) { 2809 return; 2810 } 2811 2812 while ($row = mysql_fetch_row($res)) { 2813 delete_post((int)$row[0], '', 0, 1, 1, 0, false, true); 2814 } 2815 } 2816 2817 // purge old posts 2818 // should be called whenever a new post is added. 2819 function trim_db() 2820 { 2821 global $mode; 2822 if( JANITOR_BOARD == 1 ) return; 2823 if( STATIC_REBUILD && !IS_REBUILDD ) return; 2824 2825 if (!IS_REBUILDD) { 2826 log_cache(); 2827 } 2828 2829 $maxposts = LOG_MAX; 2830 // max threads = max pages times threads-per-page 2831 $maxthreads = ( PAGE_MAX > 0 ) ? ( PAGE_MAX * DEF_PAGES ) : 0; 2832 2833 $threads = array(); 2834 2835 $rebuild_archive_list = false; 2836 2837 $rebuild_archive_json = false; 2838 2839 if (ENABLE_ARCHIVE) { 2840 if (IS_REBUILDD) { 2841 clearstatcache(true, INDEX_DIR . 'archive' . PHP_EXT . '.gz'); 2842 } 2843 2844 if (filemtime(INDEX_DIR . 'archive' . PHP_EXT . '.gz') < time() - (int)ARCHIVE_REBUILD_DELAY) { 2845 $rebuild_archive_list = true; 2846 } 2847 } 2848 2849 // New max-page method 2850 if( $maxthreads ) { 2851 $exp_order = 'no'; 2852 if( EXPIRE_NEGLECTED == 1 ) $exp_order = 'root'; 2853 //logtime( 'trim_db before select threads' ); 2854 $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" ); 2855 //logtime( 'trim_db after select threads' ); 2856 $threadcount = mysql_num_rows( $result ); 2857 2858 if (!$threadcount && $rebuild_archive_list) { 2859 $rebuild_archive_list = false; 2860 } 2861 2862 while( $row = mysql_fetch_array( $result ) and $threadcount > $maxthreads ) { 2863 if (ENABLE_ARCHIVE) { 2864 $rebuild_archive_json = true; 2865 archive_thread($row['no']); 2866 } 2867 else { 2868 delete_post( $row['no'], 'trim', 0, 1 ); // imgonly=0, automatic=1, children=1 2869 } 2870 $threads[$row['no']] = 1; 2871 $threadcount--; 2872 } 2873 2874 mysql_free_result( $result ); 2875 2876 if (ENABLE_ARCHIVE) { 2877 if ($rebuild_archive_list) { 2878 rebuild_archive_list(); 2879 } 2880 2881 if ($rebuild_archive_json && ENABLE_JSON_THREADS) { 2882 generate_board_archived_json(); 2883 } 2884 } 2885 2886 // Original max-posts method (note: cleans orphaned posts later than parent posts) 2887 } else { 2888 // make list of stickies 2889 $stickies = array(); // keys are stickied thread numbers 2890 $undead = array(); 2891 // COMBINE FOR MAXIMUM EFFICIENCY! 2892 $result = mysql_board_call( "SELECT no from `" . SQLLOG . "` where (sticky=1 OR undead=1) and resto=0" ); 2893 while( $row = mysql_fetch_array( $result ) ) { 2894 if( $row['sticky'] ) $stickies[$row['no']] = 1; 2895 if( $row['undead'] ) $undead[$row['no']] = 1; 2896 } 2897 2898 // FIXME these if ... continue checks need to be SQL conditions! 2899 $result = mysql_board_call( "SELECT no,resto,sticky FROM `" . SQLLOG . "` ORDER BY no ASC" ); 2900 $postcount = mysql_num_rows( $result ); 2901 while( $row = mysql_fetch_array( $result ) and $postcount >= $maxposts ) { 2902 // don't delete if this is a sticky thread or is undeletable 2903 if( $row['sticky'] == 1 || $row['undead'] == 1 ) continue; 2904 // don't delete if this is a REPLY to a sticky or is in an undeletable thread 2905 if( $row['resto'] != 0 && ( $stickies[$row['resto']] == 1 || $undead[$row['resto']] == 1 ) ) continue; 2906 delete_post( $row['no'], 'trim', 0, 1, 0 ); // imgonly=0, automatic=1, children=0 2907 $threads[$row['no']] = 1; 2908 $postcount--; 2909 } 2910 mysql_free_result( $result ); 2911 } 2912 } 2913 2914 // FIXME archives 2915 // debug function, deletes all archived threads 2916 function purge_archive() { 2917 $query = "SELECT no FROM `test` WHERE archived = 1 AND resto = 0"; 2918 2919 $res = mysql_board_call($query); 2920 2921 if (!$res) { 2922 return; 2923 } 2924 2925 while ($thread = mysql_fetch_assoc($res)) { 2926 echo "Deleting {$thread['no']}<br>"; 2927 delete_post((int)$thread['no'], '', 0, 0, 1, true, false, true); 2928 } 2929 } 2930 2931 function rebuild_archived_thread($thread_id) { 2932 global $log; 2933 2934 log_cache(0, $thread_id, 1); 2935 2936 if (!isset($log[$thread_id])) { 2937 return false; 2938 } 2939 2940 // Build the JSON 2941 if (ENABLE_JSON) { 2942 $tailSize = get_json_tail_size($thread_id); 2943 2944 if ($tailSize) { 2945 generate_thread_json($thread_id, false, false, false, $tailSize); 2946 } 2947 else { 2948 update_json_tail_deletion($thread_id); 2949 } 2950 2951 generate_thread_json($thread_id); 2952 } 2953 2954 // Build the HTML 2955 $dat = ''; 2956 2957 head($dat, $thread_id); 2958 form($dat, $thread_id); 2959 2960 $dat .= '<hr> 2961 <form name="delform" id="delform" action="' . SELF_PATH_ABS . '" method="post"> 2962 <div class="board"> 2963 '; 2964 2965 $reply_count = $log[$thread_id]['replycount']; 2966 2967 // Open thread tag and render OP 2968 $sorted_replies = $log[$thread_id]['children']; 2969 ksort($sorted_replies); 2970 2971 $dat .= '<div class="thread" id="t' . $thread_id . '">' 2972 . renderPostHtml($thread_id, $thread_id, $sorted_replies, $reply_count, null, true); 2973 2974 // Render replies 2975 $repCount = 0; 2976 2977 while (list($resrow) = each($sorted_replies)) { 2978 if (!$log[$resrow]['no']) { 2979 break; 2980 } 2981 2982 $dat .= renderPostHtml($resrow, $thread_id, null, null, null, true); 2983 2984 $repCount++; 2985 } 2986 2987 // Close thread tag 2988 $dat .= ' 2989 </div> 2990 <hr> 2991 '; 2992 2993 $dat .= '<div class="navLinks navLinksBot desktop">[<a href="/' 2994 . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' 2995 . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#top">' 2996 . S_TOP . '</a>] </div><hr class="desktop">'; 2997 2998 // Close board tag 2999 $lang = S_FORM_REPLY; 3000 3001 $dat .= ' 3002 <div class="mobile center"><a class="mobilePostFormToggle button" href="#">' 3003 . $lang . '</a></div> 3004 </div>'; 3005 3006 $dat .= '<div class="navLinks mobile"><span class="mobileib button"><a href="/' 3007 . BOARD_DIR . '/" accesskey="a">' 3008 . S_RETURN . '</a></span> <span class="mobileib button"><a href="/' 3009 . BOARD_DIR . '/catalog">' 3010 . S_CATALOG . '</a></span> <span class="mobileib button"><a href="#top">' 3011 . S_TOP . '</a></span> <span class="mobileib button"><a href="#bottom_r" id="refresh_bottom">' 3012 . S_REFRESH . '</a></span></div><hr class="mobile">'; 3013 3014 /** 3015 * ADS 3016 */ 3017 3018 if (defined('AD_ADGLARE_BOTTOM') && AD_ADGLARE_BOTTOM) { 3019 $dat .= '<div class="adg-rects desktop"><div class="adg adp-90" id=zone' . AD_ADGLARE_BOTTOM . '></div><hr></div>'; 3020 } 3021 3022 if (defined('AD_ADGLARE_BOTTOM_MOBILE') && AD_ADGLARE_BOTTOM_MOBILE) { 3023 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" id=zone' . AD_ADGLARE_BOTTOM_MOBILE . '></div><hr></div>'; 3024 } 3025 3026 if (defined('AD_RC_BOTTOM') && AD_RC_BOTTOM) { 3027 $dat .= '<div class="adg-rects desktop"><div class="adg adp-228" data-rc="' . AD_RC_BOTTOM . '" id="rcjsload_bottom"></div><hr></div>'; 3028 } 3029 3030 if (defined('AD_BSA_BOTTOM') && AD_BSA_BOTTOM) { 3031 $dat .= AD_BSA_BOTTOM; 3032 } 3033 3034 if (defined('AD_RC_BOTTOM_MOBILE') && AD_RC_BOTTOM_MOBILE) { 3035 $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>'; 3036 } 3037 3038 if (defined('AD_ADNIUM_BOTTOM_MOBILE') && AD_ADNIUM_BOTTOM_MOBILE) { 3039 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" id="adn-' . AD_ADNIUM_BOTTOM_MOBILE . '" data-adn></div><hr></div>'; 3040 } 3041 3042 if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 3043 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="' . AD_ABC_BOTTOM_MOBILE . '"></div><hr></div>'; 3044 } 3045 /* 3046 if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 3047 $dat .= '<div class="adc-resp-bg" data-ad-bg="' . AD_BIDGEAR_BOTTOM . '"></div>'; 3048 } 3049 else if (defined('AD_TERRA_BOTTOM_DESKTOP') && AD_TERRA_BOTTOM_DESKTOP) { 3050 $_ad_info = explode(',', AD_TERRA_BOTTOM_DESKTOP); 3051 $dat .= '<div class="adt-800" data-d="' . $_ad_info[0] . '" id="container-' . $_ad_info[1] . '" style="max-width:800px;margin:auto;"></div>'; 3052 } 3053 */ 3054 if (defined('ADS_DANBO') && ADS_DANBO) { 3055 $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>'; 3056 } 3057 if (defined('AD_CUSTOM_BOTTOM') && AD_CUSTOM_BOTTOM) { 3058 $dat .= '<div>' . AD_CUSTOM_BOTTOM . '<hr></div>'; 3059 } 3060 3061 $resredir = '<input type="hidden" name="res" value="' . $thread_id . '">'; 3062 3063 // deletion mode is "arcdel" instead of "usrdel" 3064 $dat .= '<div class="bottomCtrl desktop"><span class="deleteform"><input type="hidden" name="mode" value="arcdel">' 3065 . S_REPDEL . $resredir . ' [<input type="checkbox" name="onlyimgdel" value="on">' 3066 . S_DELPICONLY . ']<input type="hidden" id="delPassword" name="pwd"> <input type="submit" value="' 3067 . S_DELETE . '"><input id="bottomReportBtn" type="button" value="Report"></span>'; 3068 3069 if (!defined('CSS_FORCE')) { 3070 $dat .= '<span class="stylechanger">Style: 3071 <select id="styleSelector"> 3072 <option value="Yotsuba New">Yotsuba</option> 3073 <option value="Yotsuba B New">Yotsuba B</option> 3074 <option value="Futaba New">Futaba</option> 3075 <option value="Burichan New">Burichan</option> 3076 <option value="Tomorrow">Tomorrow</option> 3077 <option value="Photon">Photon</option>'; 3078 3079 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3080 $dat .= '<option value="_special">Special</option>'; 3081 } 3082 3083 $dat .= '</select> 3084 </span>'; 3085 } 3086 3087 $dat .= '</div></form>'; 3088 3089 foot($dat); 3090 3091 // Write the page 3092 print_page(RES_DIR . $thread_id . PHP_EXT, $dat); 3093 } 3094 3095 function calculate_indexes_to_rebuild( $updated_thread ) 3096 { 3097 global $index_rbl; 3098 $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 ); 3099 $index_rbl = floor( mysql_result( $query, 0, 0 ) / DEF_PAGES ); 3100 } 3101 3102 function rebuild_indexes_daemon() 3103 { 3104 global $index_rbl, $index_last_thread, $index_last_post, $log; 3105 static $index_arr = array(); 3106 3107 $index_rbl = PAGE_MAX; 3108 3109 // Get latest thread 3110 $query = mysql_board_call( "SELECT max(no) last_post, max(resto) last_thread FROM `%s` WHERE archived = 0", SQLLOG ); 3111 $q = mysql_fetch_assoc( $query ); 3112 3113 $latest_thread = $q['last_thread']; 3114 $latest_post = $q['last_post']; 3115 3116 if( $index_last_thread != $latest_thread ) { 3117 // cry :( 3118 $index_last_thread = $latest_thread; 3119 $index_last_post = $latest_post; 3120 3121 updatelog(); 3122 3123 if (ENABLE_JSON_THREADS && ENABLE_ARCHIVE) { 3124 generate_board_archived_json(); 3125 } 3126 3127 return; 3128 } 3129 3130 if( $index_last_post != $latest_post ) { 3131 $index_last_thread = $latest_thread; 3132 $index_last_post = $latest_post; 3133 3134 $post_arr = $log['THREADS']; 3135 3136 // Now we know we're not going to have identical arrays. 3137 $count = count( $post_arr ); 3138 $last_seen_thread = 0; 3139 3140 for( $i = 0; $i < $count; $i++ ) { 3141 if( $post_arr[$i] != $index_arr[$i] ) $last_seen_thread = $i; 3142 } 3143 3144 $index_rbl = floor( $last_seen_thread / DEF_PAGES ); 3145 $index_arr = $post_arr; 3146 3147 updatelog(); 3148 3149 return; 3150 } 3151 3152 // YAY NOTHING TO UPDATE! 3153 return; 3154 } 3155 3156 function style_group() 3157 { 3158 return ( DEFAULT_BURICHAN == 1 ) ? "ws_style" : "nws_style"; 3159 } 3160 3161 function rebuildd_stats() 3162 { 3163 if (!IS_REBUILDD) return ""; 3164 3165 global $update_avg_secs; 3166 global $rpc_chs; 3167 3168 $avgtime = $update_avg_secs; 3169 3170 $memuse = (int)(memory_get_usage(true) / 1024); 3171 $peakmemuse = (int)(memory_get_peak_usage(true) / 1024); 3172 $rpccount = count($rpc_chs); 3173 3174 return "<!-- t $avgtime m $memuse $peakmemuse rc $rpccount -->"; 3175 } 3176 3177 // Changes relative board urls to absolute //board.4chan.org urls 3178 // mostly for /j/ and error pages on sys.4chan 3179 function fix_board_nav($nav, $fix_protocol = false) { 3180 if ($fix_protocol) { 3181 $protocol = (stripos($_SERVER["HTTP_REFERER"], "https") === 0) ? 'https:' : 'http:'; 3182 } 3183 else { 3184 $protocol = ''; 3185 } 3186 3187 return preg_replace('/href="\/([a-z0-9]+)\/"/', "href=\"$protocol//boards." . L::d(BOARD_DIR) . "/$1/\"", $nav); 3188 } 3189 3190 // Same but for /archive lmao 3191 function fix_board_nav_archive($nav) { 3192 $nav = preg_replace('/href="\/([a-z0-9]+)\/"/', 'href="/$1/archive"', $nav); 3193 $nav = preg_replace('/href="\/f\/archive"/', 'href="/f/"', $nav); 3194 $nav = preg_replace('/href="\/b\/archive"/', 'href="/b/"', $nav); 3195 3196 return $nav; 3197 } 3198 3199 function head( &$dat, $res, $error = 0, $page = 0, $npages = 0, $is_arclist = false ) 3200 { 3201 //( $dat, 0, 0, 0, 0, true ) 3202 global $log, $thread_unique_ips; 3203 3204 $titlepart = $rta = $favicon = $css = $rss = $subtitle = $extra = ''; 3205 3206 $includenav = file_get_contents_cached(NAV_TXT); 3207 3208 if( JANITOR_BOARD == 1 ) { 3209 $dat .= broomcloset_head( $dat ); 3210 $includenav = fix_board_nav($includenav); 3211 } 3212 else if ($error) { 3213 $includenav = fix_board_nav($includenav, true); 3214 } 3215 else if ($is_arclist) { 3216 $includenav = fix_board_nav_archive($includenav); 3217 } 3218 3219 if( TITLE_IMAGE_TYPE == 1 ) { 3220 $titleimg = rand_from_flatfile( YOTSUBA_DIR, 'title_banners.txt' ); 3221 //$titleimg = STATIC_SERVER . 'image/title/' . $titleimg; 3222 3223 $titlepart .= '<div id="bannerCnt" class="title desktop" data-src="' . $titleimg . '"></div>'; 3224 } elseif( TITLE_IMAGE_TYPE == 2 ) { 3225 $titlepart .= '<img class="title" src="' . TITLEIMG . '" onclick="this.src = this.src;">'; 3226 } 3227 3228 if( defined( 'SUBTITLE' ) ) { 3229 $subtitle = '<div class="boardSubtitle">' . SUBTITLE . '</div>'; 3230 } 3231 3232 // CSS Workings 3233 $cssVersion = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION; 3234 $defaultcss = ( DEFAULT_BURICHAN == 1 ) ? 'yotsubluenew' : 'yotsubanew'; 3235 $mobilecss = ( ( DEFAULT_BURICHAN == 1 ) ? 'yotsublue' : 'yotsuba' ) . 'mobile.' . $cssVersion . '.css'; 3236 3237 $styles = array( 3238 'Yotsuba New' => "yotsubanew.$cssVersion.css", 3239 //'Yotsuba' => "yotsuba.$cssVersion.css", 3240 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 3241 'Futaba New' => "futabanew.$cssVersion.css", 3242 'Burichan New' => "burichannew.$cssVersion.css", 3243 'Photon' => "photon.$cssVersion.css", 3244 'Tomorrow' => "tomorrow.$cssVersion.css" 3245 ); 3246 3247 // /j/ versioning fix 3248 if( BOARD_DIR == 'j' ) { 3249 $css = '<link rel="stylesheet" type="text/css" href="' . STATIC_SERVER . 'css/janichan.' . $cssVersion . '.css" title="Yotsuba New">'; 3250 $extra = <<<JJS 3251 <script type="text/javascript"> 3252 document.addEventListener('mousedown', function(e) { 3253 var t = e.target; 3254 if (t === document) { 3255 return; 3256 } 3257 if (/^>>>\//.test(t.textContent) && /sys\.4chan/.test(t.href)) { 3258 t.href = t.href.replace('sys.4chan', 'boards.4chan'); 3259 } 3260 }, false); 3261 </script> 3262 JJS; 3263 } else { 3264 if( defined( 'CSS_FORCE' ) ) { 3265 foreach( $styles as $style => $stylecss ) { 3266 $rel = ( $style == 'Yotsuba New' ) ? 'stylesheet' : 'alternate stylesheet'; 3267 $css .= '<link rel="' . $rel . '" type="text/css" href="' . CSS_FORCE . '" title="' . $style . '">'; 3268 } 3269 } 3270 else { 3271 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 3272 $css .= '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 3273 foreach( $styles as $style => $stylecss ) { 3274 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' . $stylecss . '" title="' . $style . '">'; 3275 } 3276 3277 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3278 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' 3279 . CSS_EVENT_NAME . '.' . $cssVersion . '.css" title="_special">'; 3280 } 3281 } 3282 3283 // Christmas 2021 3284 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'tomorrow') { 3285 $extra = <<<JJS 3286 <script src='//s.4cdn.org/js/snow.js'></script> 3287 <script> 3288 function fc_tomorrow_init() { 3289 if (window.matchMedia && window.matchMedia('(min-width: 481px)').matches) { 3290 fc_spawn_snow(Math.floor(Math.random() * 50) + 50); 3291 } 3292 } 3293 function fc_tomorrow_cleanup() { 3294 fc_remove_snow(); 3295 } 3296 </script> 3297 <style> 3298 .boardBanner, #delform, .navLinksBot.desktop { 3299 border-image-slice: 50 0 50 0; 3300 border-image-width: 40px 0px 0px 0px; 3301 border-image-outset: 0px 0px 0px 0px; 3302 border-image-repeat: repeat repeat; 3303 border-image-source: url('https://s.4cdn.org/image/temp/garland.png'); 3304 border-style: solid; 3305 padding-top: 50px; 3306 } 3307 </style> 3308 JJS; 3309 } 3310 3311 // Halloween spooky.css 3312 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky') { 3313 $extra = <<<JJS 3314 <script> 3315 function fc_skelrot(e) { 3316 var el, idx, thres, max; 3317 if (e && e.detail && e.detail.count) { 3318 thres = 0.33; 3319 } 3320 else { 3321 thres = 0.0; 3322 } 3323 max = 23; 3324 if (Math.random() < thres) { 3325 return; 3326 } 3327 if (el = document.getElementById('skellington')) { 3328 el.parentNode.removeChild(el); 3329 } 3330 idx = 1 + Math.floor(Math.random() * max); 3331 el = document.createElement('img'); 3332 el.id = 'skellington'; 3333 el.className = 'desktop' + (Math.random() < 0.25 ? ' topskel' : ''); 3334 el.alt = ''; 3335 if (Math.random() < 0.01) { 3336 el.src = '//s.4cdn.org/image/temp/dinosaur.gif'; 3337 } 3338 else { 3339 el.src = '//s.4cdn.org/image/skeletons/' + idx + '.gif'; 3340 } 3341 document.body.insertBefore(el, document.body.firstElementChild); 3342 } 3343 function fc_spooky_init() { 3344 if (window.matchMedia && window.matchMedia('(min-width: 481px)').matches) { 3345 document.addEventListener('4chanThreadUpdated', fc_skelrot, false); 3346 window.dark_captcha = true; 3347 fc_skelrot(); 3348 } 3349 } 3350 function fc_spooky_cleanup() { 3351 var el = document.getElementById('skellington'); 3352 window.dark_captcha = false; 3353 document.removeEventListener('4chanThreadUpdated', fc_skelrot, false); 3354 el && el.parentNode.removeChild(el); 3355 } 3356 </script> 3357 JJS; 3358 } 3359 } 3360 3361 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/' . $mobilecss . '">'; 3362 3363 // April 2024 3364 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/xa24extra.css">'; 3365 3366 if (SHOW_COUNTRY_FLAGS) { 3367 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/flags.' . CSS_VERSION_FLAGS . '.css">'; 3368 } 3369 3370 if (ENABLE_BOARD_FLAGS) { 3371 $_flags_type = (defined('BOARD_FLAGS_TYPE') && BOARD_FLAGS_TYPE) ? BOARD_FLAGS_TYPE : BOARD_DIR; 3372 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'image/flags/' . $_flags_type . '/flags.' . CSS_VERSION_BOARD_FLAGS . '.css">'; 3373 } 3374 3375 if( CODE_TAGS ) { 3376 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'js/prettify/prettify.' . CSS_VERSION . '.css">'; 3377 } 3378 3379 // Various optional tags 3380 if( USE_RSS == 1 ) { 3381 $rss = '<link rel="alternate" title="RSS feed" href="/' . BOARD_DIR . '/index.rss" type="application/rss+xml">'; 3382 } 3383 3384 if( RTA == 1 ) { 3385 $rta = '<meta name="rating" content="adult">'; 3386 } 3387 3388 if( defined( 'FAVICON' ) ) { 3389 $favicon = '<link rel="shortcut icon" href="' . FAVICON . '">'; 3390 } 3391 3392 $thread_unique_ips = 0; 3393 $jsUniqueIps = ''; 3394 3395 if (SHOW_THREAD_UNIQUES) { 3396 if ($res) { 3397 $thread_unique_ips = get_unique_ip_count($res); 3398 } 3399 3400 if ($thread_unique_ips) { 3401 $jsUniqueIps = 'var unique_ips = ' . $thread_unique_ips . ';'; 3402 } 3403 } 3404 3405 // js tags 3406 $jsVersion = TEST_BOARD ? JS_VERSION_TEST : JS_VERSION; 3407 $comLen = MAX_COM_CHARS; 3408 $styleGroup = style_group(); 3409 $maxFilesize = MAX_KB * 1024; 3410 $maxLines = MAX_LINES; 3411 $jsCooldowns = json_encode(array( 3412 'thread' => RENZOKU3, 3413 'reply' => RENZOKU, 3414 'image' => RENZOKU2 3415 )); 3416 3417 $tailSizeJs = ''; 3418 3419 if ($res) { 3420 $tailSize = get_json_tail_size($res); 3421 3422 if ($tailSize) { 3423 $tailSizeJs = ",tailSize = $tailSize"; 3424 } 3425 } 3426 3427 $title = TITLE; 3428 3429 $scriptjs = <<<JS 3430 <script type="text/javascript"> 3431 var style_group = "$styleGroup", 3432 cssVersion = $cssVersion, 3433 jsVersion = $jsVersion, 3434 comlen = $comLen, 3435 maxFilesize = $maxFilesize, 3436 maxLines = $maxLines, 3437 clickable_ids = 1, 3438 cooldowns = $jsCooldowns 3439 $tailSizeJs; 3440 $jsUniqueIps 3441 JS; 3442 3443 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 3444 $scriptjs .= 'var css_event = "' . CSS_EVENT_NAME . '";'; 3445 3446 if (defined('CSS_EVENT_VERSION')) { 3447 $_css_event_version = (int)CSS_EVENT_VERSION; 3448 } 3449 else { 3450 $_css_event_version = 1; 3451 } 3452 3453 $scriptjs .= 'var css_event_v = ' . $_css_event_version . ';'; 3454 } 3455 3456 if ((int)MAX_WEBM_FILESIZE < (int)MAX_KB) { 3457 $scriptjs .= 'var maxWebmFilesize = ' . (MAX_WEBM_FILESIZE * 1024) . ';'; 3458 } 3459 3460 $_is_archived = false; 3461 3462 if (ENABLE_ARCHIVE) { 3463 $scriptjs .= 'var board_archived = true;'; 3464 3465 if ($res && $log[$res]['archived']) { 3466 $scriptjs .= 'var thread_archived = true;'; 3467 $_is_archived = true; 3468 } 3469 } 3470 3471 if (DISP_ID) { 3472 $scriptjs .= 'var user_ids = true;'; 3473 } 3474 3475 if (JSMATH) { 3476 $scriptjs .= 'var math_tags = true;'; 3477 } 3478 3479 if (SJIS_TAGS) { 3480 $scriptjs .= 'var sjis_tags = true;'; 3481 } 3482 3483 if (SPOILERS) { 3484 $scriptjs .= 'var spoilers = true;'; 3485 } 3486 3487 if (CAPTCHA_TWISTER) { 3488 $scriptjs .= 'var t_captcha = true;'; 3489 } 3490 3491 if( $res && $log[$res]['bumplimit'] ) { 3492 $scriptjs .= 'var bumplimit = 1;'; 3493 } 3494 3495 if( $res && $log[$res]['imagelimit'] ) { 3496 $scriptjs .= 'var imagelimit = 1;'; 3497 } 3498 3499 if( AD_PLEA ) $scriptjs .= 'var check_for_block = ' . (int)AD_PLEA . ';'; 3500 3501 if( $error ) $scriptjs .= 'is_error = "true";'; 3502 3503 // Danbo ads 3504 if (defined('ADS_DANBO') && ADS_DANBO) { 3505 if (DEFAULT_BURICHAN) { 3506 $scriptjs .= "var danbo_rating = '__SFW__';"; 3507 } 3508 else { 3509 $scriptjs .= "var danbo_rating = '__NSFW__';"; 3510 } 3511 3512 // Set up fallbacks 3513 $_danbo_fallbacks = []; 3514 3515 if (defined('AD_BIDGEAR_TOP') && AD_BIDGEAR_TOP) { 3516 $_danbo_fallbacks['t_bg'] = AD_BIDGEAR_TOP; 3517 } 3518 else { 3519 if (defined('AD_ABC_TOP_DESKTOP') && AD_ABC_TOP_DESKTOP) { 3520 $_danbo_fallbacks['t_abc_d'] = AD_ABC_TOP_DESKTOP; 3521 } 3522 3523 if (defined('AD_ABC_TOP_MOBILE') && AD_ABC_TOP_MOBILE) { 3524 $_danbo_fallbacks['t_abc_m'] = AD_ABC_TOP_MOBILE; 3525 } 3526 } 3527 3528 if (defined('AD_BIDGEAR_BOTTOM') && AD_BIDGEAR_BOTTOM) { 3529 $_danbo_fallbacks['b_bg'] = AD_BIDGEAR_BOTTOM; 3530 } 3531 else if (defined('AD_ABC_BOTTOM_MOBILE') && AD_ABC_BOTTOM_MOBILE) { 3532 $_danbo_fallbacks['b_abc_m'] = AD_ABC_BOTTOM_MOBILE; 3533 } 3534 3535 if (!$_danbo_fallbacks) { 3536 $_danbo_fallbacks = 'null'; 3537 } 3538 else { 3539 $_danbo_fallbacks = json_encode($_danbo_fallbacks); 3540 } 3541 3542 $scriptjs .= 'var danbo_fb = ' . $_danbo_fallbacks . ';'; 3543 3544 // Tag closed further below 3545 $scriptjs .= '</script><script src="https://static.danbo.org/publisher/q2g345hq2g534-4chan/js/preload.4chan.js" defer>'; 3546 } 3547 3548 // Close the main script tag /!\ 3549 $scriptjs .= '</script>'; 3550 3551 // PubFuture 3552 if (DEFAULT_BURICHAN) { 3553 $scriptjs .= '<script async data-cfasync="false" src="https://cdn.pubfuture-ad.com/v2/unit/pt.js"></script>'; 3554 } 3555 3556 $testjs = ( TEST_BOARD ) ? 'test/core-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'core.min.' . JS_VERSION_CORE . '.js'; 3557 $testextra = ( TEST_BOARD ) ? 'test/extension-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'extension.min.' . JS_VERSION_EXT . '.js'; 3558 3559 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testjs . '"></script>'; 3560 3561 if( !$error ) $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testextra . '"></script>'; 3562 3563 // April 2022 3564 //$scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/emotes2022.js?8"></script>'; 3565 3566 if (TEST_BOARD) { 3567 $stylejs = ''; 3568 } 3569 else { 3570 $stylejs = ''; 3571 } 3572 3573 if (ENABLE_PAINTERJS && $_GET['mode'] != 'oe_finish') { 3574 if (TEST_BOARD) { 3575 $scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/test/tegaki-8psvqAqszI.' . JS_VERSION_TEST . '.js"></script>'; 3576 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki-8psvqAqszI.' . CSS_VERSION_TEST . '.css">'; 3577 } 3578 else { 3579 $scriptjs .= '<script type="text/javascript" src="' . STATIC_SERVER . 'js/tegaki.min.' . JS_VERSION_PAINTER . '.js"></script>'; 3580 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/tegaki.' . CSS_VERSION_PAINTER . '.css">'; 3581 } 3582 } 3583 /* 3584 if (!$is_arclist && defined('CSS_MATERIAL') && CSS_MATERIAL) { 3585 $css .= '<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type="text/css">'; 3586 } 3587 */ 3588 if( !$res ) { 3589 $prev = ( $page - DEF_PAGES ) / DEF_PAGES; 3590 $next = ( $page + DEF_PAGES ) / DEF_PAGES; 3591 3592 if( $prev == 0 ) { 3593 $prev_link = SELF_PATH2; 3594 } else if( $prev > 0 ) { 3595 $prev_link = $prev . PHP_EXT2; 3596 } 3597 3598 // maybe >= ? 3599 if( ( $npages - $page ) > DEF_PAGES ) { 3600 $next_link = $next . PHP_EXT2; 3601 } 3602 } 3603 3604 if ($is_arclist) { 3605 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR.'/archive">'; 3606 } 3607 else if (!$res) { 3608 if ($page > 0) { 3609 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR.'/' . (($page / DEF_PAGES) + 1) . '">'; 3610 } 3611 else { 3612 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' .BOARD_DIR.'/">'; 3613 } 3614 } 3615 elseif ($res) { 3616 $href_context = $log[$res]['semantic_url']; 3617 3618 if ($href_context !== '') { 3619 $href_context = "/$href_context"; 3620 } 3621 3622 $canonical = '<link rel="canonical" href="https://boards.' . L::d(BOARD_DIR) . '/' .BOARD_DIR.'/thread/'.$res.$href_context . '">'; 3623 } 3624 else { 3625 $canonical = ''; 3626 } 3627 3628 $clean_title = strip_tags(TITLE); 3629 3630 if ($res) { 3631 $page_metatags = generate_page_metatags($log[$res]['sub'], $log[$res]['com']); 3632 3633 if ($page_metatags) { 3634 $page_description = $page_metatags[0] . ' - ' . META_DESCRIPTION; 3635 $page_keywords = META_KEYWORDS . $page_metatags[1]; 3636 } 3637 else { 3638 $page_description = META_DESCRIPTION; 3639 $page_keywords = META_KEYWORDS; 3640 } 3641 3642 $page_title = generate_page_title($res, $log[$res]['sub'], $log[$res]['com']) 3643 . ' - ' . preg_replace('/^[\[\/][a-z0-9]+[\]\/] - /i', '', $clean_title); 3644 } 3645 else { 3646 $page_description = META_DESCRIPTION; 3647 $page_keywords = META_KEYWORDS; 3648 3649 $page_title = $clean_title; 3650 3651 if ($is_arclist) { 3652 $page_title .= ' - Archive'; 3653 } 3654 else if ($page > 0) { 3655 $page_title .= ' - Page ' . (($page / DEF_PAGES) + 1); 3656 } 3657 } 3658 3659 $page_title .= ' - 4chan'; 3660 3661 if (!$_is_archived) { 3662 $_delegate_ch = '<meta http-equiv="Delegate-CH" content="Sec-CH-UA-Model https://sys.4chan.org">'; 3663 } 3664 else { 3665 $_delegate_ch = ''; 3666 } 3667 3668 $dat .= '<!DOCTYPE html> 3669 <html> 3670 <head> 3671 <meta charset="utf-8"> 3672 <meta name="robots" content="' . META_ROBOTS . '"> 3673 <meta name="description" content="' . $page_description . '"> 3674 <meta name="keywords" content="' . $page_keywords . '"> 3675 <meta name="viewport" content="width=device-width,initial-scale=1"> 3676 ' . $rta . ' 3677 ' . $favicon . ' 3678 ' . $css . ' 3679 ' . $canonical . ' 3680 ' . $rss . $_delegate_ch . ' 3681 <title>' . $page_title . '</title>' . $scriptjs . $extra; 3682 3683 $embedearly = EMBEDEARLY; 3684 3685 $adembedearly = AD_EMBEDEARLY; 3686 3687 if (AD_ADBLOCK_TEXT && (DEFAULT_BURICHAN || BOARD_DIR === 'pol' || BOARD_DIR === 'bant')) { 3688 $adembedearly .= file_get_contents_cached(AD_ADBLOCK_TEXT); 3689 } 3690 3691 $board_class = 'board_' . BOARD_DIR; 3692 3693 if (!$res) { 3694 if ($is_arclist) { 3695 $board_class = 'is_arclist ' . $board_class; 3696 } 3697 else { 3698 $board_class = 'is_index ' . $board_class; 3699 } 3700 } 3701 else { 3702 $board_class = 'is_thread ' . $board_class; 3703 } 3704 3705 if (TEXT_ONLY) { 3706 $board_class = 'text_only ' . $board_class; 3707 } 3708 3709 if (!$is_arclist) { 3710 $abovePostForm = '<hr class="abovePostForm">'; 3711 } 3712 else { 3713 $abovePostForm = ''; 3714 } 3715 3716 $dat .= <<<HTML 3717 $embedearly 3718 $adembedearly 3719 <noscript><style type="text/css">#postForm { display: table !important; }#g-recaptcha { display: none; }</style></noscript> 3720 </head> 3721 <body class="$board_class"> 3722 <span id="id_css"></span> 3723 $stylejs 3724 $includenav 3725 3726 <div class="boardBanner"> 3727 $titlepart 3728 <div class="boardTitle">$title</div> 3729 $subtitle 3730 </div> 3731 $abovePostForm 3732 HTML; 3733 3734 if (!$error && !$is_arclist) { 3735 /* 3736 if (defined('ADS_BIDGLASS_TOP_MOBILE') && ADS_BIDGLASS_TOP_MOBILE) { 3737 $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>'; 3738 } 3739 else if (defined('AD_ABC_TOP_MOBILE') && AD_ABC_TOP_MOBILE) { 3740 $dat .= '<div class="adg-rects mobile"><div class="adg-m adp-250" data-abc="' . AD_ABC_TOP_MOBILE . '"></div><hr class="belowLeaderboard"></div>'; 3741 }*/ 3742 } 3743 } 3744 3745 function delete_uploaded_files() 3746 { 3747 global $upfile_name, $upfile, $dest, $pchfile; 3748 if( $dest || $upfile ) { 3749 @unlink( $dest ); 3750 @unlink( $upfile ); 3751 } 3752 } 3753 3754 /* Footer */ 3755 function foot( &$dat, $error = false, $is_arclist = false ) 3756 { 3757 global $update_avg_secs; 3758 3759 $includenav = file_get_contents_cached(NAV2_TXT); 3760 3761 $dat .= $includenav; 3762 $dat .= rebuildd_stats(); 3763 3764 if( CODE_TAGS ) { 3765 $dat .= '<script type="text/javascript" src="' 3766 . STATIC_SERVER . 'js/prettify/prettify.' 3767 . JS_VERSION . '.js"></script><script type="text/javascript">prettyPrint();</script>'; 3768 } 3769 3770 $dat .= EMBEDLATE . '</body></html>'; 3771 } 3772 3773 function error($mes, $unused = '') { 3774 global $mode; 3775 3776 if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json') { 3777 error_json($mes); 3778 } 3779 3780 if( $mode == "report" ) fancydie( $mes ); 3781 3782 delete_uploaded_files(); 3783 3784 head( $dat, 0, 1 ); 3785 3786 $protocol = (stripos($_SERVER["HTTP_REFERER"], "https") === 0) ? 'https:' : 'http:'; 3787 3788 $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='; 3789 3790 if (preg_match('#^' . $protocol . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/thread/([0-9]+)#', $_SERVER["HTTP_REFERER"], $m)) { 3791 $thread_part = 'thread/' . (int)$m[1]; 3792 } 3793 else { 3794 $thread_part = ''; 3795 } 3796 3797 $dat .= $protocol . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/' . $thread_part . ">" . S_RELOAD . "</a>]</td></tr></table><br><br><hr size=1>"; 3798 3799 foot( $dat, true ); 3800 3801 if (TEST_BOARD==1) { 3802 internal_error_log("post error", $mes); 3803 } 3804 3805 die( $dat ); 3806 } 3807 3808 function error_json($msg) { 3809 delete_uploaded_files(); 3810 3811 header('Content-Type: application/json'); 3812 3813 echo json_encode(['error' => $msg]); 3814 3815 die(); 3816 } 3817 3818 function error_redirect($mes, $redirect, $timeout = 3000) { 3819 delete_uploaded_files(); 3820 head( $dat, 0, 1 ); 3821 $dat .= <<<HTML 3822 <script type="text/javascript"> 3823 setTimeout(function() { window.location = "$redirect"; }, $timeout); 3824 </script> 3825 HTML; 3826 $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='; 3827 $dat .= '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/>" 3828 . S_RELOAD . "</a>]</td></tr></table><br><br><hr size=1>"; 3829 foot( $dat ); 3830 3831 if (TEST_BOARD==1) { 3832 internal_error_log("post error", $mes); 3833 } 3834 3835 die( $dat ); 3836 } 3837 3838 /* Auto Linker */ 3839 function normalize_link_cb( $m ) 3840 { 3841 3842 $subdomain = $m[1]; 3843 $original = $m[0]; 3844 $board = strtolower( $m[2] ); 3845 $m[0] = $m[1] = $m[2] = ''; 3846 3847 $count = count( $m ) - 1; 3848 for( $i = $count; $i > 2; $i-- ) { 3849 if( $m[$i] ) { 3850 $no = $m[$i]; 3851 break; 3852 } 3853 } 3854 3855 if( $subdomain != 'boards') { 3856 return $original; 3857 } 3858 3859 if( stripos( $no, 'catalog' ) === 0 ) { 3860 3861 if( ( $pos = stripos( $no, '#s=' ) ) !== false ) { 3862 $term = substr( $no, $pos + 3 ); 3863 } else { 3864 $term = 'catalog'; 3865 } 3866 3867 return ">>>/$board/$term"; 3868 } 3869 3870 if( $board == BOARD_DIR && $no && $no != 'catalog' ) { 3871 return ">>$no"; 3872 } else { 3873 return ">>>/$board/$no"; 3874 } 3875 } 3876 3877 function normalize_links( $proto ) 3878 { 3879 // change http://xxx.4chan.org/board/res/no links into plaintext >># or >>>/board/# 3880 $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 ); 3881 3882 return $proto; 3883 } 3884 3885 // TODO merge with get_resto_for 3886 function post_resto($no) { 3887 global $log; 3888 3889 if (isset($log[$no])) { 3890 return $log[$no]['resto']; 3891 } 3892 3893 $q = mysql_board_call('SELECT resto FROM `%s` WHERE no=%d', SQLLOG, $no); 3894 if (!mysql_num_rows($q)) { 3895 $log[$no] = array('resto' => false); 3896 return false; 3897 } 3898 $r = (int)mysql_fetch_row($q)[0]; 3899 $log[$no] = array('resto' => $r); 3900 return $r; 3901 3902 return false; 3903 } 3904 3905 // FIXME 3906 // This might not be used anywhere anymore 3907 function intraboard_link_cb( $m ) 3908 { 3909 global $intraboard_cb_resno, $log; 3910 $no = $m[1]; 3911 $resno = $intraboard_cb_resno; 3912 $resto = post_resto( $no ); // doesn't like assignment in condition 3913 if( $resto !== false ) { 3914 $resdir = ( $resno ? '' : RES_DIR2 ); 3915 $ext = PHP_EXT2; 3916 $id = NEW_HTML ? "p$no" : "$no"; 3917 if( $resno && $resno == $resto ) // linking to a reply in the same thread 3918 return "<a href=\"#$id\" class=\"quotelink\">>>$no</a>"; 3919 elseif( $resto == 0 ) // linking to a thread 3920 return "<a href=\"$resdir$no$ext#$id\" class=\"quotelink\">>>$no</a>"; else // linking to a reply in another thread 3921 return "<a href=\"$resdir$resto$ext#$id\" class=\"quotelink\">>>$no</a>"; 3922 } 3923 3924 return '<span class="deadlink">' . $m[0] . '</span>'; 3925 } 3926 3927 // FIXME 3928 // This might not be used anywhere anymore, see parse_intraboard_link() 3929 function intraboard_links( $proto, $resno ) 3930 { 3931 global $intraboard_cb_resno; 3932 3933 $intraboard_cb_resno = $resno; 3934 3935 $proto = preg_replace_callback( '/>>([0-9]+)/', 'intraboard_link_cb', $proto ); 3936 3937 return $proto; 3938 } 3939 3940 function other_board_resto( $board, $resno ) 3941 { 3942 // this function is a little slow 3943 // board requirements - either is the current board (for /test/) or is public (in boardlist) 3944 // returns - 3945 // FALSE if resno does not exist 3946 // 0 if resno is a thread 3947 // an id if resno is a reply to a thread 3948 3949 static $boardlist = array(); 3950 3951 if( !$boardlist ) 3952 $boardlist = array_flip( mysql_column_array( mysql_global_call( "select sql_cache dir from boardlist" ) ) ); 3953 3954 if( $board != BOARD_DIR && !isset( $boardlist[$board] ) ) 3955 return false; 3956 3957 $q = mysql_board_call( "select resto from `%s` where no=%d", $board, $resno ); 3958 if( !mysql_num_rows( $q ) ) 3959 return false; 3960 $r = mysql_result( $q, 0 ); 3961 3962 return $r; 3963 } 3964 3965 function interboard_link_cb( $m ) 3966 { 3967 // on one hand, we can link to imgboard.php, using any old subdomain, 3968 // and let apache & imgboard.php handle it when they click on the link 3969 // on the other hand, we can use the database to fetch the proper subdomain 3970 // and even the resto to construct a proper link to the html file (and whether it exists or not) 3971 3972 // for now, we'll assume there's more interboard links posted than interboard links visited. 3973 $board = '/'; 3974 $otherboard = mb_strtolower( $m[1] ); 3975 if( $m[2] ) { 3976 $resto = other_board_resto( $otherboard, $m[2] ); 3977 $id = "#p"; 3978 3979 if( $resto === false ) 3980 $url = ""; 3981 else if( $resto ) 3982 $url = $board . $otherboard . '/thread/' . $resto . $id . $m[2]; 3983 else 3984 $url = $board . $otherboard . '/thread/' . $m[2] . $id . $m[2]; 3985 } else $url = $board . $otherboard . '/'; 3986 3987 $b = $m[1]; 3988 $mlp_hack = BOARD_DIR == 'mlp' && ( $b == 'b' || $b == 'co' ); 3989 $original = mb_strtolower( $m[0] ); 3990 if( !$url || $mlp_hack ) 3991 return '<span class="deadlink">' . $original . '</span>'; 3992 else 3993 return "<a href=\"$url\" class=\"quotelink\">{$original}</a>"; 3994 } 3995 3996 function interboard_catalog_link_cb( $m ) 3997 { 3998 $board = $m[1]; 3999 if( $board == 'f' ) return $m[0]; 4000 4001 $lsearchquery = strtolower( urlencode( urldecode( $m[2] ) ) ); 4002 $original = mb_strtolower( str_replace( ">", "> ", $m[0] ) ); 4003 4004 if( $lsearchquery == "catalog" ) { 4005 return "<a href=\"/$board/catalog\" class=\"quotelink\">$original</a>"; 4006 } elseif( $lsearchquery == 'rules' ) { 4007 return '<a href="//www.' . L::d($board) . '/rules#' . $board . '" class="quotelink">' . $original . '</a>'; 4008 } else { 4009 return "<a href=\"/$board/catalog#s=$lsearchquery\" class=\"quotelink\">$original</a>"; 4010 } 4011 } 4012 4013 function boards_matching_arr() 4014 { 4015 static $boards_matching_arr; 4016 global $valid_boards; 4017 4018 if( empty( $boards_matching_arr ) ) $boards_matching_arr = explode( '|', $valid_boards ); 4019 4020 return $boards_matching_arr; 4021 } 4022 4023 // Normalize and linkify internal and non-quote links 4024 // before inserting the post into the database. 4025 function normalize_and_linkify($proto) { 4026 4027 if (strpos($proto, "4chan") !== false || strpos($proto, "4cdn.org") !== false) { 4028 // normalize long links 4029 $proto = normalize_links($proto); 4030 4031 // linkify other internal links 4032 if ((strpos($proto, "4chan") !== false && strpos($proto, "/derefer") === false) || strpos($proto, "4cdn.org") !== false) { 4033 $proto = preg_replace_callback( '/(https?:\/\/(?:[A-Za-z]*\.)?)(4chan|4channel|4cdn)(\.org)(\/[\w\-\.,@?^=%&;:\/~\+#\(\)]*[\w\-\@?^=%&;\/~\+#])?/i', 'clean_internal_link', $proto ); 4034 $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 ); 4035 } 4036 } 4037 4038 if (strpos($proto, '>>>') !== false) { 4039 $proto = preg_replace_callback('#>>>/([a-z0-9]+)/([a-z0-9+/,l\-]*)#', 'auto_link_static_cb', $proto); 4040 } 4041 4042 return $proto; 4043 } 4044 4045 // Removes >> links from internal 4chan.org links 4046 function clean_internal_link($matches) { 4047 $link = preg_replace('/>>>|>>/', '', $matches[0]); 4048 return "<a href=\"$link\" target=\"_blank\">$link</a>"; 4049 } 4050 4051 function auto_link_static_cb($matches) { 4052 $post = $matches[0]; 4053 $inter_board = $matches[1]; 4054 $no = $matches[2]; 4055 4056 $boards_matching_arr = boards_matching_arr(); 4057 4058 $full_link = $post; 4059 4060 $inter_board = strtolower( $inter_board ); 4061 4062 $is_board_link = ($no == ''); 4063 4064 $resno = $no; 4065 4066 // Text boards 4067 // Catalog, rules, and /rs/ links 4068 if (!is_numeric( $resno ) || $is_board_link) { 4069 $url = urlencode( urldecode( $resno ) ); 4070 4071 $target = ''; 4072 4073 if( strpos( $resno, 'rules' ) === 0 ) { 4074 $ruleno = ''; 4075 $ruleloc = strpos( $resno, '/' ); 4076 4077 if( $ruleloc !== false ) { 4078 $ruleno = substr( $resno, $ruleloc + 1 ); 4079 } 4080 4081 $parsed_link = '//www.' . L::d($inter_board) . "/rules#$inter_board$ruleno"; 4082 $target = ' target="_blank"'; 4083 } 4084 else if (in_array($inter_board, $boards_matching_arr)) { 4085 $parsed_link = '//boards.' . L::d($inter_board) . "/$inter_board/"; 4086 4087 if( $inter_board == 'f' && $url == 'catalog' ) return $full_link; 4088 4089 if( !$is_board_link ) { 4090 $parsed_link .= ($url == 'catalog' ? 'catalog' : "catalog#s=$url"); 4091 } 4092 } 4093 else { 4094 return $full_link; 4095 } 4096 4097 return '<a href="' . $parsed_link . '" class="quotelink"' . $target . '>' . $full_link . '</a>'; 4098 } 4099 4100 return $full_link; 4101 } 4102 4103 function auto_link( $proto, $resno ) 4104 { 4105 global $current_resno; 4106 static $has_gen = 0; 4107 4108 if( !$has_gen ) { 4109 boards_matching_arr(); 4110 $has_gen = 1; 4111 } 4112 4113 // The majority of posts don't contain links, so don't go there and waste time on preg junk 4114 if( strpos( $proto, '>>' ) !== false ) { 4115 $current_resno = $resno; 4116 $proto = preg_replace_callback( '#(>>[0-9]+|>>>/[a-z0-9]+/[a-z0-9+/-]*)#', 'auto_link_cb', $proto ); 4117 } 4118 4119 return $proto; 4120 } 4121 4122 function auto_link_cb( $post ) 4123 { 4124 global $current_resno; 4125 4126 //var_dump($post); 4127 $is_inter = ( strpos( $post[0], '>>>/' ) === 0 ); 4128 $post = $post[0]; 4129 4130 if( $is_inter ) { 4131 preg_match( '#>>>/([a-z0-9]+)/(.*)#', $post, $match ); 4132 4133 return parse_interboard_link( $match[0], $match[1], $match[2] ); 4134 } else { 4135 $no = explode( '>>', $post ); 4136 4137 return parse_intraboard_link( $post, $no[1], $current_resno ); 4138 } 4139 } 4140 4141 function parse_intraboard_link( $post, $no, $resno ) 4142 { 4143 $full_link = $post; 4144 //$no = substr( $post, $i - $in_link_char, $in_link_char ); 4145 $resto = post_resto( $no ); 4146 4147 if( $resto === false ) { 4148 $parsed_link = '<span class="deadlink">' . $full_link . '</span>'; 4149 4150 return $parsed_link; 4151 } 4152 4153 $ext = PHP_EXT2; 4154 $id = NEW_HTML ? "p$no" : "$no"; 4155 4156 // linking to a reply or the OP in the same thread 4157 if ($resno && ($resno == $resto || $resno == $no)) { 4158 $parsed_link = "<a href=\"#$id\" class=\"quotelink\">>>$no</a>"; 4159 } 4160 // linking to an OP in another thread or from indexes 4161 elseif ($resto == 0) { 4162 $parsed_link = "<a href=\"/" . BOARD_DIR . "/" . RES_DIR2 . "$no$ext#$id\" class=\"quotelink\">>>$no</a>"; 4163 } 4164 // linking to a reply in another thread or from indexes 4165 else { 4166 $parsed_link = "<a href=\"/" . BOARD_DIR . "/" . RES_DIR2 . "$resto$ext#$id\" class=\"quotelink\">>>$no</a>"; 4167 } 4168 4169 return $parsed_link; 4170 } 4171 4172 function parse_interboard_link( $post, $inter_board, $no ) 4173 { 4174 global $valid_boards; 4175 4176 // make sure to account for > not > 4177 $full_link = $post; 4178 $inter_board = strtolower( $inter_board ); 4179 4180 // Are we a board link? 4181 $is_board_link = ( $no == '' ); 4182 4183 // ... to get resno! 4184 $resno = $no; 4185 4186 $valid_rule = $inter_board == 'global' && strpos( $resno, 'rules' ) !== false; 4187 4188 // Skip static links (boards, catalog) 4189 if ($is_board_link 4190 || $valid_rule 4191 || !is_numeric($resno) 4192 || !in_array($inter_board, boards_matching_arr()) 4193 ) { 4194 return $full_link; 4195 } 4196 4197 // Valid board, now check post number 4198 $resto = other_board_resto( $inter_board, $resno ); 4199 4200 if( $resto === false ) { // dead link 4201 $url = ''; 4202 } elseif( $resto ) { // different thread 4203 $url = '//boards.' . L::d($inter_board) . "/{$inter_board}/thread/$resto#p$resno"; 4204 } else { // same thread 4205 $url = '//boards.' . L::d($inter_board) . "/{$inter_board}/thread/$resno#p$resno"; 4206 } 4207 4208 $disable = BOARD_DIR == 'mlp' && ( $inter_board == 'b' || $inter_board == 'co' ); 4209 if( !$url || $disable ) { 4210 $parsed_link = '<span class="deadlink">' . $full_link . '</span>'; 4211 } else { 4212 $parsed_link = "<a href=\"$url\" class=\"quotelink\">$full_link</a>"; 4213 } 4214 4215 return $parsed_link; 4216 } 4217 4218 function trans_same_board_links( &$com ) 4219 { 4220 $match = '>>>/' . BOARD_DIR . '/'; 4221 $len = strlen( $match ); 4222 $i = stripos( $com, $match ); 4223 $boardlen = strlen( BOARD_DIR ) - 1; 4224 $dir = BOARD_DIR; 4225 $com .= '~'; 4226 4227 while( isset( $com{$i} ) ) { 4228 4229 if( is_numeric( $com{$i + $len} ) ) { 4230 // Match, replace out 4231 $com = substr_replace( $com, '>>', $i, $len ); 4232 4233 } 4234 4235 $i = stripos( $com, $match, $i + $len ); 4236 if( $i === false ) { 4237 $com = substr( $com, 0, strlen( $com ) - 1 ); 4238 4239 return; 4240 } 4241 } 4242 } 4243 4244 function auto_link_parser( $post, $resno ) 4245 { 4246 $post .= '<'; 4247 4248 $i = 0; 4249 $in_link = false; 4250 $in_link_char = 0; 4251 4252 $is_inter = false; 4253 $inter_found_board = false; 4254 $inter_board = ''; 4255 $inter_is_catalog = false; 4256 4257 $is_intra = false; 4258 $gt_count = 0; 4259 $mbcl = 10; // forgot about dis links :( 4260 4261 $dbg = ""; 4262 4263 while( isset( $post{$i} ) ) { 4264 $seen_gt_this = false; 4265 $c = $post{$i}; 4266 4267 if( !$in_link ) { 4268 // Not in a link, find > 4269 4270 if( $c == '&' ) { 4271 if( $post{$i + 1} == 'g' && $post{$i + 2} == 't' && $post{$i + 3} == ';' ) { 4272 if( $gt_count < 3 ) $gt_count++; 4273 4274 $i = $i + 4; 4275 continue; 4276 } 4277 } 4278 4279 if( ( $c == '/' && $gt_count == 3 ) || ( $gt_count > 1 && is_numeric( $c ) ) ) { 4280 $in_link_char = 0; 4281 $in_link = true; 4282 $i--; // shift us back a char to get the right match... 4283 } 4284 4285 $gt_count = 0; 4286 } else { 4287 // We can be sure we have a valid character for our link 4288 if( $in_link_char == 0 ) { 4289 4290 if( $c == '/' ) { 4291 $is_inter = true; 4292 $is_intra = false; 4293 } else { 4294 $is_intra = true; 4295 $is_inter = false; 4296 } 4297 } 4298 4299 if( $is_inter ) { 4300 4301 if( $in_link_char == 0 ) { 4302 $in_link_char++; 4303 $i++; 4304 continue; 4305 } 4306 4307 if( $in_link_char > $mbcl && $c != '/' && !$inter_found_board ) { 4308 // yup :( 4309 4310 $in_link = false; 4311 $is_inter = false; 4312 4313 $i++; 4314 continue; 4315 } 4316 4317 if( !$inter_found_board && $c == '/' ) { 4318 $inter_board = substr( $post, $i - ( $in_link_char - 1 ), $in_link_char - 1 ); 4319 4320 $inter_found_board = true; 4321 $in_link_char++; 4322 $i++; 4323 continue; 4324 } 4325 4326 if( $inter_found_board ) { 4327 $match = ( 4328 ctype_alnum( $c ) || 4329 $c == '+' || 4330 $c == '/' || 4331 $c == '-' 4332 ); 4333 4334 if( $match ) { 4335 $in_link_char++; 4336 $i++; 4337 continue; 4338 } 4339 } 4340 4341 if( !$inter_found_board ) { 4342 $in_link_char++; 4343 $i++; 4344 continue; 4345 } 4346 4347 4348 parse_interboard_link( $post, $i, $in_link_char, $inter_board ); 4349 4350 $is_inter = false; 4351 $in_link = false; 4352 $inter_found_board = false; 4353 } 4354 4355 if( $is_intra ) { 4356 if( is_numeric( $c ) ) { 4357 $in_link_char++; 4358 } else { 4359 // reached the end 4360 parse_intraboard_link( $post, $i, $in_link_char, $resno ); 4361 4362 $in_link = false; 4363 $is_intra = false; 4364 } 4365 } 4366 } 4367 4368 $i++; 4369 } 4370 4371 return substr( $post, 0, strlen( $post ) - 1 ) . $dbg; 4372 } 4373 4374 /** 4375 * New version of check_blacklist() 4376 * $post must be an array with the following fields: 4377 * resto, filename, name, tripcode, password, 4pass_id 4378 */ 4379 function check_md5_blacklist($md5, $original_md5, $post, $dest) { 4380 if (!$md5) { 4381 return false; 4382 } 4383 4384 $board = BOARD_DIR; 4385 4386 if (DEFAULT_BURICHAN) { 4387 $ws_clause = " OR boardrestrict = '_ws_'"; 4388 } 4389 else { 4390 $ws_clause = ''; 4391 } 4392 4393 $sql =<<<SQL 4394 SELECT SQL_NO_CACHE * FROM blacklist 4395 WHERE active = 1 AND (boardrestrict = '' OR boardrestrict = '$board'$ws_clause) 4396 AND field = 'md5' AND contents = '%s' LIMIT 1 4397 SQL; 4398 4399 // Check MD5 4400 $query = mysql_global_call($sql, $md5); 4401 4402 // Check original MD5 if provided 4403 if (!mysql_num_rows($query)) { 4404 if ($original_md5 && $original_md5 !== $md5) { 4405 $query = mysql_global_call($sql, $original_md5); 4406 4407 if (!mysql_num_rows($query)) { 4408 return false; 4409 } 4410 4411 $md5 = $original_md5; 4412 } 4413 else { 4414 return false; 4415 } 4416 } 4417 4418 $row = mysql_fetch_assoc($query); 4419 4420 if (!$row) { 4421 return false; 4422 } 4423 4424 // Private reason 4425 $private_reason = "Blacklisted md5 - " . htmlspecialchars($md5) 4426 . ' - Filename: ' . htmlspecialchars($post['filename']); 4427 4428 // Ban name 4429 if (isset($post['name'])) { 4430 $ban_name = $post['name']; 4431 } 4432 else { 4433 $ban_name = S_ANONAME; 4434 } 4435 4436 if (isset($post['tripcode'])) { 4437 $ban_name .= " #{$post['tripcode']}"; 4438 } 4439 4440 // Ban password 4441 if (isset($post['password']) && $post['password']) { 4442 $pwd = $post['password']; 4443 } 4444 else { 4445 $pwd = null; 4446 } 4447 4448 // Ban 4chan pass 4449 if (isset($post['4pass_id']) && $post['4pass_id']) { 4450 $pass_id = $post['4pass_id']; 4451 } 4452 else { 4453 $pass_id = null; 4454 } 4455 4456 // Thread id for redirection 4457 if (isset($post['resto'])) { 4458 $resto = (int)$post['resto']; 4459 } 4460 else { 4461 $resto = 0; 4462 } 4463 4464 // -------- 4465 4466 // Reject 4467 if (!$row['ban']) { 4468 if (TEST_BOARD) { 4469 error($private_reason, $dest); 4470 } 4471 } 4472 // Auto-ban 4473 else if ($row['ban'] == '1') { 4474 auto_ban_poster($ban_name, $row['banlength'], 1, $private_reason, $row['banreason'], false, $pwd, $pass_id); 4475 } 4476 // Show error (DMCA requests) 4477 else if ($row['ban'] == '2') { 4478 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4479 4480 $query = "SELECT ip FROM user_actions WHERE action = 'fail_dmca' AND ip = %d AND time >= DATE_SUB(NOW(), INTERVAL 1 DAY)"; 4481 4482 $res = mysql_global_call($query, $ip); 4483 4484 if ($res && mysql_num_rows($res) > 0) { 4485 $private_reason = "DMCA complaint from {$row['description']} (blacklist ID: {$row['id']})"; 4486 auto_ban_poster($ban_name, 3, 1, $private_reason, S_DMCABANREASON, true, $pwd, $pass_id); 4487 error_redirect(S_BANNED, 'https://www.' . L::d(BOARD_DIR) . '/banned'); 4488 } 4489 else { 4490 $query = "INSERT INTO user_actions (board,postno,ip,time,uploaded,action) VALUES ('%s', %d, %d, NOW(), 0, 'fail_dmca')"; 4491 mysql_global_call($query, $board, 0, $ip); 4492 } 4493 4494 error(S_DMCAFAIL, $dest); 4495 } 4496 4497 if ($row['quiet']) { 4498 show_post_successful_fake($resto); 4499 die(); 4500 } 4501 4502 error(S_FAILEDUPLOAD, $dest); 4503 } 4504 4505 function check_blacklist($post, $dest, $file_ext = '', $resto = 0, $pwd = null, $pass_id = null) { 4506 //if( has_level() ) return; 4507 4508 $board = BOARD_DIR; 4509 4510 if (DEFAULT_BURICHAN) { 4511 $ws_clause = " OR boardrestrict = '_ws_'"; 4512 } 4513 else { 4514 $ws_clause = ''; 4515 } 4516 4517 $querystr = "SELECT SQL_NO_CACHE * FROM blacklist WHERE active=1 AND (boardrestrict='' or boardrestrict='$board'$ws_clause) AND (0 "; 4518 foreach( $post as $field => $contents ) { 4519 if( $contents ) { 4520 $contents = mysql_real_escape_string( html_entity_decode( $contents ) ); 4521 $querystr .= "OR (field='$field' AND contents='$contents') "; 4522 } 4523 } 4524 $querystr .= ") LIMIT 1"; 4525 4526 $query = mysql_global_call( $querystr ); 4527 if( mysql_num_rows( $query ) == 0 ) return false; 4528 4529 $row = mysql_fetch_assoc( $query ); 4530 $prvreason = "Blacklisted ${row['field']} - " . htmlspecialchars( $row['contents'] ); 4531 4532 if ($row['field'] == 'md5') { 4533 $prvreason .= ' - Filename: ' . htmlspecialchars($post['filename']) . $file_ext; 4534 } 4535 4536 if (!$row['ban']) { 4537 if (TEST_BOARD) { 4538 error( "Blacklisted: " . $prvreason, $dest ); 4539 } 4540 } 4541 // Auto-ban 4542 else if ($row['ban'] == '1') { 4543 auto_ban_poster($post['trip'] ? $post['nametrip'] : $post['name'], $row['banlength'], 1, $prvreason, $row['banreason'], false, $pwd, $pass_id); 4544 } 4545 // Show error (DMCA requests) 4546 else if ($row['ban'] == '2') { 4547 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4548 4549 $query = "SELECT ip FROM user_actions WHERE action = 'fail_dmca' AND ip = %d AND time >= DATE_SUB(NOW(), INTERVAL 1 DAY)"; 4550 4551 $res = mysql_global_call($query, $ip); 4552 4553 if ($res && mysql_num_rows($res) > 0) { 4554 $prvreason = "DMCA complaint from {$row['description']} (blacklist ID: {$row['id']})"; 4555 4556 auto_ban_poster($post['trip'] ? $post['nametrip'] : $post['name'], 3, 1, $prvreason, S_DMCABANREASON, true, $pwd, $pass_id); 4557 4558 error_redirect(S_BANNED, 'https://www.' . L::d(BOARD_DIR) . '/banned'); 4559 } 4560 else { 4561 $query = "INSERT INTO user_actions (board,postno,ip,time,uploaded,action) VALUES ('%s',%d,%d,NOW(),0,'fail_dmca')"; 4562 mysql_global_call($query, $board, 0, $ip); 4563 } 4564 4565 error(S_DMCAFAIL, $dest); 4566 } 4567 /* 4568 { 4569 $ip = $_SERVER['REMOTE_ADDR']; 4570 quick_log_to("/www/perhost/blacklist.log", "IP $ip board /$board/: $prvreason"); 4571 } 4572 */ 4573 4574 if ($row['quiet']) { 4575 show_post_successful_fake($resto); 4576 die(); 4577 } 4578 4579 error(S_FAILEDUPLOAD, $dest); 4580 } 4581 4582 // we've already failed the floodcheck, check if they're a repeat offender and ban them 4583 function check_fail_floodcheck($info) 4584 { 4585 $ip = ip2long($_SERVER['REMOTE_ADDR']); 4586 mysql_global_call("insert into user_actions (ip,board,action,time) values (%d,'%s','fail_floodcheck',now())", $ip, ''); 4587 $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); 4588 quick_log_to("/www/perhost/floodchecks.log", $info); 4589 if(mysql_result($query,0,0)) { 4590 auto_ban_poster("Anonymous", 1, 1, "got a flood check warning 5 times in an hour", "Sending an excessive number of server requests"); 4591 } 4592 } 4593 4594 // word-wrap without touching things inside of tags 4595 function wordwrap2( $str, $cols, $cut ) 4596 { 4597 // if there's no runs of $cols non-space characters, wordwrap is a no-op 4598 if( mb_strlen( $str ) < $cols || !preg_match( '/[^ <>]{' . $cols . '}/', $str ) ) { 4599 return $str; 4600 } 4601 $sections = preg_split( '/[<>]/', $str ); 4602 $str = ''; 4603 for( $i = 0; $i < count( $sections ); $i++ ) { 4604 if( $i % 2 ) { // inside a tag 4605 $str .= '<' . $sections[$i] . '>'; 4606 } else { // outside a tag 4607 $words = explode( ' ', $sections[$i] );/* 4608 $exclude = array( 4609 'http://', 4610 'https://', 4611 'www.' 4612 ); 4613 */ 4614 foreach( $words as &$word ) {/* 4615 foreach( $exclude as $match ) { 4616 if( stripos( $word, $match ) === 0 && stripos( $word, '4chan.org' ) !== false ) continue 2; 4617 }*/ 4618 4619 $word = htmlspecialchars_decode( $word, ENT_QUOTES ); 4620 $word = utf8_wordwrap( $word, $cols, $cut, true ); 4621 $word = htmlspecialchars( $word, ENT_QUOTES ); 4622 4623 } 4624 4625 $str .= implode( ' ', $words ); 4626 } 4627 } 4628 4629 return $str; 4630 } 4631 4632 function logtime( $desc ) 4633 { 4634 static $run = -1; 4635 if( !PROFILING ) return; 4636 if( $run == -1 ) { 4637 $run = getmypid_cached(); 4638 } 4639 $board = BOARD_DIR; 4640 $time = microtime( true ); 4641 mysql_global_call( "INSERT INTO profiling_times VALUES ('$board',$run,$time,'$desc')" ); 4642 } 4643 4644 function time_log($r) { 4645 if (TEST_BOARD && $_SERVER['HTTP_ACCEPT'] !== 'application/json') { 4646 echo "<!-- $r " . microtime(true) . " " . memory_get_usage(true) . " -->\n"; 4647 } 4648 } 4649 4650 function is_bad_xff( $xff ) 4651 { 4652 if ($xff === '8.8.8.8' || $xff === '62.210.138.29' || $xff === '212.129.0.228') { 4653 return true; 4654 } 4655 4656 list( $xffs ) = post_filter_get( "xffwhitelist" ); 4657 $ipnum = ip2long( $xff ); 4658 4659 if( !$ipnum ) return true; // text in xff field 4660 4661 return find_ipxff_in( 0, $ipnum, $xffs ); 4662 } 4663 4664 function has_doubles( $id ) 4665 { 4666 if( $id % 1000 == 0 ) return false; 4667 4668 $ones = $id % 10; 4669 $tens = ( $id / 10 ) % 10; 4670 4671 return $ones == $tens; 4672 } 4673 4674 function generate_uid($resto, $time, $ip = false) { 4675 if (DISP_ID_RANDOM) { 4676 $str = mt_rand(); 4677 } 4678 else { 4679 $str = !$ip ? $_SERVER["REMOTE_ADDR"] : $ip; 4680 4681 if (DISP_ID_PER_THREAD) { 4682 $str .= $resto ? $resto : date( 'Ymd', $time ); 4683 } else { 4684 $str .= 'hats'; // we will put a hat on it to confuse people :) 4685 } 4686 } 4687 4688 $salt = file_get_contents_cached( SALTFILE ); 4689 $hash = base64_encode( pack( "H*", sha1( $str . $salt ) ) ); 4690 4691 return substr( $hash, 0, 8 ); 4692 } 4693 4694 function parse_vip_capcode($capcode) { 4695 // Flood check 4696 $longip = ip2long($_SERVER['REMOTE_ADDR']); 4697 4698 $query = <<<SQL 4699 SELECT COUNT(*) FROM user_actions 4700 WHERE ip = %d AND action = 'fail_login' 4701 AND time >= SUBDATE(NOW(), INTERVAL 1 HOUR) 4702 SQL; 4703 4704 $res = mysql_global_call($query, $longip); 4705 4706 if (!$res) { 4707 return false; 4708 } 4709 4710 $count = mysql_fetch_row($res)[0]; 4711 4712 if ($count >= 3) { 4713 return false; 4714 } 4715 4716 // Now check the capcode 4717 list($_, $user_id, $user_key) = explode('!', $capcode, 3); 4718 4719 if (!$user_id || !$user_key) { 4720 return false; 4721 } 4722 4723 $query = "SELECT name, user_key FROM vip_capcodes WHERE active = 1 AND user_id = '%s' LIMIT 1"; 4724 4725 $res = mysql_global_call($query, $user_id); 4726 4727 if (!$res) { 4728 return false; 4729 } 4730 4731 $user = mysql_fetch_assoc($res); 4732 4733 if ($user && password_verify($user_key, $user['user_key'])) { 4734 $query = "UPDATE vip_capcodes SET last_used = %d, last_ip = '%s' WHERE user_id = '%s' LIMIT 1"; 4735 mysql_global_call($query, $_SERVER['REQUEST_TIME'], $_SERVER['REMOTE_ADDR'], $user_id); 4736 return $user['name']; 4737 } 4738 4739 // Log the failure 4740 $query = <<<SQL 4741 INSERT INTO user_actions (ip, board, action, time) 4742 VALUES (%d, '', 'fail_login', NOW()) 4743 SQL; 4744 4745 mysql_global_call($query, $longip); 4746 4747 return false; 4748 } 4749 4750 function parse_capcode($capcode, &$name = null) 4751 { 4752 if ($name !== null && strpos($capcode, 'capcode_!') === 0) { 4753 $vip_name = parse_vip_capcode($capcode); 4754 4755 if ($vip_name !== false) { 4756 $name = $vip_name; 4757 return 'verified'; 4758 } 4759 4760 return 'none'; 4761 } 4762 4763 if (!has_level()) { 4764 return 'none'; 4765 } 4766 4767 $user = strtolower( $_COOKIE['4chan_auser'] ); 4768 4769 $is_developer = has_flag( 'developer' ) && $capcode == 'capcode_dev'; 4770 $is_mod = ( has_level() && $capcode == 'capcode_mod' ); 4771 $is_manager = has_level('manager') && $capcode == 'capcode_manager'; 4772 4773 $is_admin = ( has_level( 'admin' ) && $capcode == 'capcode_admin' ); 4774 $is_founder = ( has_level( 'admin' ) && $capcode == 'capcode_founder' ); 4775 $highlight = ( has_level( 'admin' ) && $capcode == 'capcode_admin_hl' ); 4776 4777 if ($is_founder) { 4778 return 'founder'; 4779 } 4780 4781 if( $is_admin || $highlight ) { 4782 return ( $highlight ) ? 'admin_highlight' : 'admin'; 4783 } 4784 4785 if( $is_developer ) { 4786 return 'developer'; 4787 } 4788 4789 if ($is_manager) { 4790 return 'manager'; 4791 } 4792 4793 if (!has_flag('capcode') && !has_level('manager')) { 4794 if ($capcode !== '') { 4795 error(S_CANTCAPCODE); 4796 } 4797 } 4798 4799 if( $is_mod ) { 4800 return 'mod'; 4801 } 4802 4803 return 'none'; 4804 } 4805 4806 function generate_tim() { 4807 $time = $_SERVER['REQUEST_TIME']; 4808 $micro = substr(microtime(), 2, 4); 4809 $tail = mt_rand(0, 99); 4810 4811 if ($tail < 10) { 4812 $tail = "0$tail"; 4813 } 4814 4815 return "$time$micro$tail"; 4816 } 4817 4818 /* Regist */ 4819 function new_post( $name, $email, $sub, $com, $url, $pwd, $upfile, $upfile_name, $resto, $age, $filetag ) 4820 { 4821 global $pwdc, $textonly, $admin, $spoiler, $dest, $pchfile, $word_filters_enabled, $silent_reject; 4822 global $captcha_bypass, $passid; 4823 global $board_flags_array; 4824 4825 // Fro HTML and capcode posting 4826 $log_mod_action = false; 4827 $log_html_post = false; 4828 $log_capcode_post = false; 4829 4830 $mes = ""; 4831 4832 /* VARIOUS CHECKS BEFORE ANY POSTING TAKES PLACE */ 4833 4834 $oldbanbuster = ( $_SERVER['HTTP_USER_AGENT'] == 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' && $com == 'test' ); 4835 $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'] ) ); 4836 4837 if( BOARD_DIR == 'b' && ( $oldbanbuster || $newbanbuster ) ) { 4838 $msg = $com == 'test' ? 'com = test' : 'com = 15 length string'; 4839 auto_ban_poster( "", -1, 1, 'BanBuster proxy test match (Posting ' . $msg . ' from dodgy UA)', 'Proxy/Tor exit node.' ); 4840 error( S_NOCAPTCHA ); 4841 } 4842 4843 if( $_SERVER["REQUEST_METHOD"] != "POST" ) error( S_UNJUST ); 4844 4845 /* END CHECKS */ 4846 4847 $hasjs = isset( $_POST['hasjs'] ) && $_POST['hasjs'] === 'yes'; 4848 $stats_all = $hasjs ? 'with_js_all' : 'without_js_all'; 4849 $stats_ok = $hasjs ? 'with_js_success' : 'without_js_success'; 4850 4851 4852 $host = $_SERVER["REMOTE_ADDR"]; 4853 4854 time_log( "start" ); 4855 4856 // might be better to do this before the mysql connection 4857 if( !$upfile && !$resto ) { // allow textonly threads for moderators! 4858 if( has_level() ) $textonly = 1; 4859 } 4860 elseif( JANITOR_BOARD == 1 ) { // only allow mods/janitors to post, and textonly is always ok 4861 $textonly = 1; 4862 if( !has_level( 'janitor' ) ) 4863 die(); 4864 } 4865 4866 if (TEXT_ONLY && $upfile && $resto) { 4867 error(S_TEXT_ONLY); 4868 } 4869 4870 if (UPLOAD_BOARD) { 4871 if ($upfile && $resto) { 4872 error(S_FAILEDUPLOAD); 4873 } 4874 4875 $tags = upboard_tags(); 4876 4877 if( !$resto && !array_key_exists( (int)$filetag, $tags ) ) error( "Error: Invalid tag specified.", $filetag ); 4878 if( !$resto && !is_numeric( $filetag ) ) error( "Error: Invalid tag specified.", $filetag ); 4879 //if((int)$filetag==9999) error("No tag selected.",$filetag); 4880 if( !$resto ) $sub = (int)$filetag . '|' . $sub; 4881 4882 //OPs must have files 4883 if (!$upfile && !$resto) { 4884 error(S_NOUPLOAD); 4885 } 4886 } 4887 4888 // Password session 4889 $userpwd = new UserPwd($host, MAIN_DOMAIN, $pwdc); 4890 4891 $pass = $userpwd->getPwd(); 4892 4893 if (!$pass) { 4894 error(S_GENERICERROR, $dest); 4895 } 4896 4897 $resto = (int)$resto; 4898 4899 // time 4900 $time = $_SERVER['REQUEST_TIME']; 4901 $tim = generate_tim(); 4902 4903 $captcha_bypass_allow_credits = false; 4904 4905 $memcached = null; 4906 4907 if (isset($_POST['recaptcha_challenge_field'])) { 4908 error('Legacy captcha is no longer supported.'); 4909 } 4910 else if (isset($_POST['g-recaptcha-response'])) { 4911 if (CAPTCHA_TWISTER) { 4912 error('reCAPTCHA v2 is no longer supported.'); 4913 } 4914 4915 // Recaptcha v2 4916 start_auth_captcha(); 4917 4918 if (!$captcha_bypass) { 4919 $_c_ret = end_recaptcha_verify(); 4920 } 4921 } 4922 else { 4923 if (valid_captcha_bypass() !== true) { 4924 $memcached = create_memcached_instance(); 4925 4926 $_unsolved_count = 0; 4927 4928 // Captcha bypass credits 4929 if ($resto && isset($_POST['t-challenge']) && $_POST['t-challenge'] === 'noop') { 4930 if (use_twister_captcha_credit($memcached, $host, $userpwd) === false) { 4931 error(S_CAPTCHATIMEOUT); 4932 } 4933 } 4934 // Captcha verification failed 4935 else if (is_twister_captcha_valid($memcached, $host, $userpwd, BOARD_DIR, $resto, $_unsolved_count) === false) { 4936 // Silent captcha failure for new suspicious users 4937 //$_bad_actor = spam_filter_is_bad_actor(); 4938 //$_threat_score = spam_filter_get_threat_score($_SERVER['HTTP_X_GEO_COUNTRY'], !$resto, true); 4939 if (isset($_SERVER['HTTP_X_BOT_SCORE'])) { 4940 $_bot_score = (int)$_SERVER['HTTP_X_BOT_SCORE']; 4941 } 4942 else { 4943 $_bot_score = 100; 4944 } 4945 4946 if (!$userpwd || $userpwd->isUserKnownOrVerified(1440, 1) === false) { 4947 if ($_bot_score > 1 && $_bot_score < 80) { 4948 $_meta = spam_filter_format_http_headers(htmlspecialchars($com), $_SERVER['HTTP_X_GEO_COUNTRY'], $upfile_name, $_threat_score); 4949 if (isset($_POST['t-challenge']) && $_POST['t-response'] && isset($_COOKIE['_tcs'])) { 4950 log_failed_captcha($host, $userpwd, BOARD_DIR, $resto, true, $_meta); 4951 } 4952 show_post_successful_fake($resto, false); 4953 die(); 4954 } 4955 } 4956 error(S_BADCAPTCHA); 4957 } 4958 // Captcha verification succeeded 4959 else if ($_unsolved_count < 2 && CAPTCHA_ALLOW_BYPASS && spam_filter_is_bad_actor() === false) { 4960 $captcha_bypass_allow_credits = true; 4961 } 4962 } 4963 } 4964 /* 4965 if (spam_filter_is_bad_actor()) { 4966 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 4967 log_spam_filter_trigger('log_bad_actor', BOARD_DIR, $resto, $host, 1, $_bot_headers); 4968 } 4969 */ 4970 if (PASS_POST_ONLY && !$captcha_bypass) { 4971 error(S_PASS_POST_ONLY); 4972 } 4973 4974 // Validate 2FA if posting html 4975 if ((isset($_POST['html']) && $_POST['html']) && (has_level('manager') || has_flag('html') || has_flag('developer'))) { 4976 validate_otp(); 4977 $log_html_post = $log_mod_action = true; 4978 } 4979 4980 $locked_time = $time; 4981 // check closed 4982 if( $resto ) { 4983 if( !$cchk = mysql_board_call( "select closed,sticky,undead,archived,sub,com from `" . SQLLOG . "` where no=" . $resto ) ) { 4984 echo S_SQLFAIL; 4985 } 4986 list( $closed, $sticky, $undead, $is_archived, $_thread_sub, $_thread_com ) = mysql_fetch_row( $cchk ); 4987 if ($is_archived) { 4988 error(S_MAYNOTREPLY, $upfile); 4989 } 4990 $is_undead_sticky = $sticky == 1 && $undead == 1; 4991 if( $closed == 1 && !has_level() ) error( S_MAYNOTREPLY, $upfile ); 4992 mysql_free_result( $cchk ); 4993 4994 $sub = ''; 4995 } 4996 else { 4997 $is_undead_sticky = false; 4998 } 4999 5000 $has_image = $upfile && file_exists( $upfile ); 5001 5002 $md5 = null; 5003 $original_md5 = null; // MD5 before exif and other metadata stripping 5004 5005 if( $has_image ) { 5006 if (UPLOAD_BOARD) { 5007 if( file_exists( IMG_DIR . $upfile_name ) ) error( "Filename already exists.", $upfile ); 5008 5009 $dest = $upfile; 5010 5011 $upfile_name = sanitize_text( $upfile_name ); 5012 if( !is_file( $dest ) ) error( S_FAILEDUPLOAD, $dest ); 5013 if ($upfile_name[0] === '.') { 5014 error('Error: Invalid filename (first character cannot be a period).', $dest); 5015 } 5016 $size = getimagesize( $dest ); 5017 if( !is_array( $size ) ) error( S_NOREC, $dest ); 5018 5019 $W = $size[0]; 5020 $H = $size[1]; 5021 $fsize = filesize( $dest ); 5022 if( $fsize > MAX_KB * 1024 ) error( S_TOOLARGE, $dest ); 5023 if( $size[2] == 6 || $size[2] == 5 ) { 5024 error( S_FAILEDUPLOAD, $dest ); 5025 } 5026 switch( $size[2] ) { 5027 case 4 : 5028 case 13 : 5029 $ext = ".swf"; 5030 break; 5031 default : 5032 $ext = ".xxx"; 5033 error( S_FAILEDUPLOAD, $dest ); 5034 break; 5035 } 5036 5037 time_log( "sfpi" ); 5038 rpc_task(); 5039 5040 $len = strlen( $ext ); 5041 } 5042 else { 5043 // NOT upload board 5044 5045 // check image limit 5046 if( $resto && !$sticky && !$undead && !has_level() ) { 5047 if( !$result = mysql_board_call( "SELECT COUNT(*) FROM `" . SQLLOG . "` WHERE archived = 0 AND resto=$resto AND fsize!=0 AND filedeleted=0" ) ) { 5048 echo S_SQLFAIL; 5049 } 5050 $countimgres = mysql_result( $result, 0, 0 ); 5051 if( $countimgres >= MAX_IMGRES && !has_level() ) error(S_MAXIMAGESREACHED, $upfile ); 5052 mysql_free_result( $result ); 5053 } 5054 5055 //upload processing 5056 $dest = $upfile; 5057 5058 // TODO: what does that preg_replace do? those are probably utf8 codes 5059 $upfile_name = sanitize_text( preg_replace('/\xe2\x80(\xae|\xad|\x8f|\x8e)/', '', $upfile_name) ); 5060 5061 if (!is_file($dest)) { 5062 error(S_FAILEDUPLOAD, $dest); 5063 } 5064 5065 // Use filesize() later as it's possible to trick jpegtrans to generate a much bigger file 5066 $fsize = $_FILES['upfile']['size']; 5067 5068 if (!$fsize || $fsize > MAX_KB * 1024) { 5069 error( S_TOOLARGE, $dest ); 5070 } 5071 5072 $webm_sar = null; 5073 5074 // PDF processing 5075 if( ENABLE_PDF == 1 && strcasecmp( '.pdf', substr( $upfile_name, -4 ) ) == 0 ) { 5076 $ext = '.pdf'; 5077 $W = $H = 1; 5078 // run through ghostscript to check for validity 5079 if( pclose( popen( "/usr/local/bin/gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage $dest", 'w' ) ) ) { 5080 error( S_FAILEDUPLOAD, $dest ); 5081 } 5082 } 5083 // Webm / MP4 5084 else if (ENABLE_WEBM && preg_match('/\.(webm|mp4)$/i', $upfile_name)) { 5085 if ($fsize > MAX_WEBM_FILESIZE * 1024) { 5086 error(S_TOOLARGE, $dest); 5087 } 5088 5089 $original_md5 = md5_file($dest); 5090 5091 $ext = '.' . strtolower(pathinfo($upfile_name, PATHINFO_EXTENSION)); 5092 5093 //if ($ext == '.mp4' && BOARD_DIR != 'test') { 5094 // error(S_NOREC, $dest); 5095 //} 5096 5097 $size = validate_webm($dest, $ext); 5098 $W = $size[0]; 5099 $H = $size[1]; 5100 $webm_sar = $size[2]; 5101 } 5102 // PNG, GIF, JPEG 5103 else { 5104 $size = getimagesize( $dest ); 5105 if( !is_array( $size ) ) { 5106 quick_log_to( "/www/perhost/bad-upload.log", "unrecognized file $upfile_name"); 5107 5108 error( S_NOREC, $dest ); 5109 } 5110 5111 $W = $size[0]; 5112 $H = $size[1]; 5113 switch( $size[2] ) { 5114 case 1 : 5115 $ext = ".gif"; 5116 break; 5117 case 2 : 5118 $ext = ".jpg"; 5119 break; 5120 case 3 : 5121 $ext = ".png"; 5122 break; 5123 case 4 : 5124 $ext = ".swf"; 5125 error( S_FAILEDUPLOAD, $dest ); 5126 break; 5127 case 5 : 5128 $ext = ".psd"; 5129 error( S_FAILEDUPLOAD, $dest ); 5130 break; 5131 case 6 : 5132 $ext = ".bmp"; 5133 error( S_FAILEDUPLOAD, $dest ); 5134 break; 5135 case 7 : 5136 $ext = ".tiff"; 5137 error( S_FAILEDUPLOAD, $dest ); 5138 break; 5139 case 8 : 5140 $ext = ".tiff"; 5141 error( S_FAILEDUPLOAD, $dest ); 5142 break; 5143 case 9 : 5144 $ext = ".jpc"; 5145 error( S_FAILEDUPLOAD, $dest ); 5146 break; 5147 case 10 : 5148 $ext = ".jp2"; 5149 error( S_FAILEDUPLOAD, $dest ); 5150 break; 5151 case 11 : 5152 $ext = ".jpx"; 5153 error( S_FAILEDUPLOAD, $dest ); 5154 break; 5155 case 13 : 5156 $ext = ".swf"; 5157 error( S_FAILEDUPLOAD, $dest ); 5158 break; 5159 default : 5160 $ext = ".xxx"; 5161 error( S_FAILEDUPLOAD, $dest ); 5162 break; 5163 } 5164 if (GIF_ONLY == 1 && $size[2] != 1 && $ext != '.webm') error(S_FAILEDUPLOAD, $dest); 5165 } // end PDF processing -else 5166 5167 // This doesn't seem to use the $md5 arg 5168 if ($upfile_name === '') { 5169 error('Blank file names are not supported.', $dest); 5170 } 5171 5172 time_log( "sfpi" ); 5173 rpc_task(); 5174 5175 // Picture reduction 5176 if( !$resto ) { 5177 $maxw = MAX_W; 5178 $maxh = MAX_H; 5179 } else { 5180 $maxw = MAXR_W; 5181 $maxh = MAXR_H; 5182 } 5183 if( defined( 'MIN_W' ) && MIN_W > $W ) error( S_TOOSMALL, $dest ); 5184 if( defined( 'MIN_H' ) && MIN_H > $H ) error( S_TOOSMALL, $dest ); 5185 if( defined( 'MAX_DIMENSION' ) ) 5186 $maxdimension = MAX_DIMENSION; 5187 else 5188 $maxdimension = 5000; 5189 if( $W > $maxdimension || $H > $maxdimension ) { 5190 error( S_TOOLARGERES, $dest ); 5191 } elseif( $W > $maxw || $H > $maxh ) { 5192 $W2 = $maxw / $W; 5193 $H2 = $maxh / $H; 5194 ( $W2 < $H2 ) ? $key = $W2 : $key = $H2; 5195 $TN_W = ceil( $W * $key ) + 1; 5196 $TN_H = ceil( $H * $key ) + 1; 5197 } 5198 5199 // Strip metadata, exif, comments and other embeddded extra data 5200 if ($ext === '.jpg') { 5201 // Embed detection is done later below if STRIP_EXIF is disabled 5202 if (STRIP_EXIF) { 5203 $original_md5 = md5_file($dest); 5204 5205 if (strip_jpeg_exif($dest) === false) { 5206 error(S_FAILEDUPLOAD, $dest); 5207 } 5208 5209 clearstatcache(true, $dest); 5210 } 5211 } 5212 else if ($ext === '.png') { 5213 $original_md5 = md5_file($dest); 5214 $_ret = strip_png_chunks($dest, MAX_KB * 1024); 5215 if ($_ret < 0) { 5216 if ($_ret === -2) { 5217 error('APNG format not supported.', $dest); 5218 } 5219 else { 5220 error(S_FAILEDUPLOAD, $dest); 5221 } 5222 } 5223 else if ($_ret > 0) { 5224 clearstatcache(true, $dest); 5225 } 5226 } 5227 else if ($ext === '.gif') { 5228 $original_md5 = md5_file($dest); 5229 $_ret = strip_gif_extra_data($dest, $fsize); 5230 if ($_ret < 0) { 5231 error(S_FAILEDUPLOAD, $dest); 5232 } 5233 clearstatcache(true, $dest); 5234 } 5235 5236 // It should be safe to check the filesize now 5237 // clearstatcache() must be called if the file was modified 5238 $fsize = filesize($dest); 5239 5240 if (!$fsize) { 5241 error(S_TOOLARGE, $dest); 5242 } 5243 else if ($ext === '.webm' && $fsize > MAX_WEBM_FILESIZE * 1024) { 5244 error(S_TOOLARGE, $dest); 5245 } 5246 else if ($fsize > MAX_KB * 1024) { 5247 error(S_TOOLARGE, $dest); 5248 } 5249 5250 // Check for JPEG embedded data. jpegtran seems to remove unknown data 5251 // so this only needs to be done if STRIP_EXIF is disabled 5252 if (!STRIP_EXIF && $ext === '.jpg' && $fsize > 204800) { 5253 validate_jpeg_size($dest, $fsize); 5254 } 5255 } 5256 5257 $insfile = preg_replace('/\.[a-z0-9]+$/i', '', $upfile_name); 5258 5259 $md5 = md5_file( $dest ); 5260 $mes = $upfile_name . ' ' . S_UPGOOD; 5261 } 5262 5263 if( $_FILES["upfile"]["error"] > 0 ) { 5264 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_INI_SIZE ) 5265 error( S_TOOLARGE, $dest ); 5266 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_FORM_SIZE ) 5267 error( S_TOOLARGE, $dest ); 5268 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_PARTIAL ) 5269 error( S_FAILEDUPLOAD, $dest ); 5270 if( $_FILES["upfile"]["error"] == UPLOAD_ERR_CANT_WRITE ) 5271 error( S_FAILEDUPLOAD, $dest ); 5272 } 5273 5274 if( $upfile_name && $_FILES["upfile"]["size"] == 0 ) { 5275 error( S_TOOLARGEORNONE, $dest ); 5276 } 5277 5278 if( ENABLE_EXIF == 1 ) { 5279 $exif = htmlspecialchars( shell_exec( "/usr/local/bin/exiftags $dest" ) ); 5280 } 5281 5282 $resto = (int)$resto; 5283 if( $resto ) { 5284 if( !mysql_result( mysql_board_call( "select count(no) from `" . SQLLOG . "` where root>0 and no=$resto" ), 0, 0 ) ) 5285 error( S_NOTHREADERR, $dest ); 5286 } 5287 5288 $pass_is_bannable = !$userpwd->isNew(); 5289 5290 // Most common errors checked, now check for post-block from ban requests 5291 if (BLOCK_ON_BR && !has_level()) { 5292 check_for_ban_request($host, $pass_is_bannable ? $pass : null); 5293 } 5294 5295 // Standardize new character lines 5296 $com = str_replace( "\r\n", "\n", $com ); 5297 $com = str_replace( "\r", "\n", $com ); 5298 5299 $comlim = has_level() ? MAX_COM_CHARS_AUTHED : MAX_COM_CHARS; 5300 $longlim = has_level() ? 255 : 100; 5301 5302 if( mb_strlen( $com ) > $comlim ) error( S_TOOLONG, $dest ); 5303 if( strlen( $name ) > $longlim ) error( S_TOOLONG, $dest ); 5304 if( strlen( $email ) > $longlim ) error( S_TOOLONG, $dest ); 5305 if( strlen( $sub ) > $longlim ) error( S_TOOLONG, $dest ); 5306 if( strlen( $resto ) > 10 ) error( S_GENERICERROR, $dest ); 5307 if( strlen( $url ) > 10 ) error( S_GENERICERROR, $dest ); 5308 5309 $sub = normalize_content( $sub ); 5310 5311 // start of some attempt to get rid of *all* zero width bollocks 5312 if( BOARD_DIR != 'jp' && BOARD_DIR != 'a' && !SJIS_TAGS) { 5313 $com = normalize_content( $com ); 5314 $com = strip_zerowidth( $com ); 5315 } 5316 5317 // strip no break spaces and soft hyphens 5318 $com = str_replace(array("\xC2\xAD", "\xC2\xA0"), '', $com); 5319 5320 // name/subject too! 5321 $sub = strip_zerowidth( $sub ); 5322 $name = strip_zerowidth( $name ); 5323 5324 // Strip unicode emoticons 5325 $name = strip_emoticons($name, SJIS_TAGS); 5326 5327 if ($sub !== '') { 5328 $sub = strip_emoticons($sub, SJIS_TAGS); 5329 } 5330 5331 if ($com !== '') { 5332 $com = strip_emoticons($com, SJIS_TAGS); 5333 } 5334 5335 // strip out ltr junk from name 5336 $name = preg_replace( '#([\x{2000}-\x{200F}]|[\x{2028}-\x{202F}])#u', '', $name ); 5337 5338 if ($sub !== '') { 5339 $sub = strip_fake_capcodes($sub); 5340 } 5341 5342 if( !strlen( $name ) || preg_match( "/^[ | |]*$/", $name ) ) $name = ""; 5343 if( !strlen( $com ) || preg_match( "/^[ | |\t]*$/", $com ) ) $com = ""; 5344 if( !strlen( $sub ) || preg_match( "/^[ | |]*$/", $sub ) ) $sub = ""; 5345 5346 //$name = str_replace( S_MANAGEMENT, "\"" . S_MANAGEMENT . "\"", $name ); 5347 //$name = str_replace( S_DELETION, "\"" . S_DELETION . "\"", $name ); 5348 5349 // Remove intra spoilers 5350 if (SPOILERS && stripos($com, '[spoiler]') !== false) { 5351 //$com = preg_replace( '/\[spoiler\]\s+\[\/spoiler\]/', '', $com ); 5352 $com = preg_replace('/(\S)\[spoiler\](.*?)\[\/spoiler\](\S)/', '\\1\\2\\3', $com); 5353 } 5354 5355 //lol /b/ 5356 $xff = get_request_xff(); 5357 //if( is_bad_xff( $xff ) ) $xff = ""; 5358 5359 $youbi = array(S_SUN, S_MON, S_TUE, S_WED, S_THU, S_FRI, S_SAT); 5360 $yd = $youbi[date( "w", $time )]; 5361 if( SHOW_SECONDS == 1 ) { 5362 $now = date( "m/d/y", $time ) . "(" . (string)$yd . ")" . date( "H:i:s", $time ); 5363 } else { 5364 $now = date( "m/d/y", $time ) . "(" . (string)$yd . ")" . date( "H:i", $time ); 5365 } 5366 5367 $c_name = $name; 5368 $c_email = $email; 5369 5370 if (JANITOR_BOARD == 1) { 5371 $name = get_hashed_mod_name($_COOKIE['4chan_auser']); 5372 $email = ''; 5373 } 5374 5375 // April 2023 5376 //$_has_xa23_content = $com && preg_match('/^[^>]{8,}/m', $com) > 0; 5377 5378 $com = preg_replace( '#>>>/' . BOARD_DIR . '/([0-9]+)#', '>>$1', $com ); 5379 5380 $sub = sanitize_text( $sub ); 5381 $sub = preg_replace( "/[\r\n]/", "", $sub ); 5382 $sub = strip_private_unicode($sub); 5383 $url = sanitize_text( $url ); 5384 $url = preg_replace( "/[\r\n]/", "", $url ); 5385 $resto = sanitize_text( $resto ); 5386 $resto = preg_replace( "/[\r\n]/", "", $resto ); 5387 $com = sanitize_text( $com, 1, true ); 5388 $com = strip_private_unicode($com); 5389 5390 if( FORCED_ANON == 1 ) { 5391 if( !has_level('admin') ) $name = S_ANONAME; 5392 $sub = ''; 5393 } 5394 5395 if (UPLOAD_BOARD) { 5396 if( NO_TEXTONLY == 1 ) { 5397 if( !$resto && !$has_image ) error( S_NOPIC, $dest ); 5398 } else { 5399 if( !$resto && !$textonly && !$has_image ) error( S_NOPIC, $dest ); 5400 } 5401 } else { 5402 if (!TEXT_ONLY) { 5403 if( NO_TEXTONLY == 1 && (!has_level() || $email === '') ) { 5404 if( !$resto && !$has_image ) error( S_NOPIC, $dest ); 5405 } else { 5406 if( !$resto && !$textonly && !$has_image ) error( S_NOPIC, $dest ); 5407 } 5408 } 5409 5410 if( REQUIRE_SUBJECT && !$resto && !strlen( $sub ) ) error( S_NOSUB, $dest ); 5411 } 5412 // Check for sage, nonoko and nonokosage 5413 $is_sage = false; 5414 5415 if (stripos($email, 'sage') !== false) { 5416 $is_sage = true; 5417 $email = str_ireplace('sage', '', $email); 5418 } 5419 5420 $email_lower = strtolower($email); 5421 5422 if ($email_lower === 'nonoko') { 5423 $is_nonoko = true; 5424 } 5425 5426 if( SPOILERS == 1 && $spoiler ) { 5427 $sub = "SPOILER<>$sub"; 5428 } 5429 5430 if( !has_level() ) { 5431 $match = array(); 5432 if( substr_count( $com, "\n" ) > 6 ) preg_match_all( '#([^\n]+\n+)\1{5,}#', $com, $match ); 5433 if( !empty( $match[0] ) ) { 5434 foreach( $match[1] as $key => $var ) { 5435 //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.' ); 5436 error( S_REJECTTEXTBAN ); 5437 } 5438 } 5439 } 5440 5441 // FIXME sanitize_text() replaces repeated \n too, is this duplicate? 5442 // disable on code tag boards (we replace multiple brs instead) 5443 if (!CODE_TAGS && !SJIS_TAGS) { 5444 $com = preg_replace( "/\n(( | )*\n){3,}/", "\n", $com ); 5445 } 5446 5447 if( !has_level() && substr_count( $com, "\n" ) > MAX_LINES ) error(S_TOOMANYLINES, $dest ); 5448 5449 if( ENABLE_EXIF == 1 && $exif ) { 5450 //turn exif into a table 5451 $exiflines = explode( "\n", $exif ); 5452 $exif = "<table class=\"exif\" id=\"exif$tim\">"; 5453 foreach( $exiflines as $exifline ) { 5454 list( $exiftag, $exifvalue ) = explode( ': ', $exifline ); 5455 if( $exifvalue != '' ) 5456 $exif .= "<tr><td>$exiftag</td><td>$exifvalue</td></tr>"; 5457 else 5458 $exif .= "<tr><td colspan=\"2\"><b>$exiftag</b></td></tr>"; 5459 } 5460 $exif .= '</table>'; 5461 $exiftext .= '<br><br><span class="abbr">' . sprintf(S_EXIF_TOGGLE, $tim) . '</span><br>'; 5462 $exiftext .= "$exif"; 5463 } 5464 5465 $name = preg_replace( "/[\r\n]/", "", $name ); 5466 5467 $names = mb_convert_encoding($name, 'CP932', 'UTF-8'); // convert to Windows Japanese #kami 5468 5469 //start new tripcode crap 5470 list ( $name ) = explode( "#", $name ); 5471 5472 // Strip unicode point-of-interest and # characters 5473 if (preg_match('/[\x{2318}\x{ff03}\x{FE5F}]/u', $name)) { 5474 $name = preg_replace('/[\x{2318}\x{ff03}\x{FE5F}]/u', '', $name); 5475 } 5476 5477 $name = normalize_content( $name ); 5478 $name = sanitize_text( $name ); 5479 $name = strip_private_unicode($name); 5480 5481 if (preg_match('/^\s+$/', $name)) { 5482 $name = ''; 5483 } 5484 5485 $name = str_replace('!', '', $name); 5486 5487 if( preg_match( "/\#+$/", $names ) ) { 5488 $names = preg_replace( "/\#+$/", "", $names ); 5489 } 5490 if( preg_match( "/\#/", $names ) ) { 5491 5492 $names = str_replace( "&#", "&&", htmlspecialchars( $names, ENT_COMPAT | ENT_HTML401, 'Shift_JIS' ) ); # otherwise HTML numeric entities screw up explode()! 5493 5494 list ( $nametemp, $trip, $sectrip ) = str_replace( "&&", "&#", explode( "#", $names, 3 ) ); 5495 5496 if ($sectrip != '') { 5497 $trip = ''; 5498 } 5499 5500 $names = $nametemp; 5501 if( STRIP_TRIPCODE == 0 ) $name .= "</span>"; 5502 5503 if ($trip != "" && STRIP_TRIPCODE == 0) { 5504 $salt = strtr( preg_replace( "/[^\.-z]/", ".", substr( $trip . "H.", 1, 2 ) ), ":;<=>?@[\\]^_`", "ABCDEFGabcdef" ); 5505 $trip = substr( crypt( $trip, $salt ), -10 ); 5506 $name .= " <span class=\"postertrip\">!" . $trip; 5507 } 5508 5509 if( $sectrip != "" && STRIP_TRIPCODE == 0 ) { 5510 $salt = file_get_contents_cached( SALTFILE ); 5511 $sha = base64_encode( pack( "H*", sha1( $sectrip . $salt ) ) ); 5512 $sha = substr( $sha, 0, 11 ); 5513 $name .= " <span class=\"postertrip\">!!" . $sha; 5514 } 5515 } //end new tripcode crap 5516 5517 // Check the length of the name field again to prevent truncation in the middle of tripcode HTML 5518 if (strlen($name) > 255) { 5519 error(S_TOOLONG, $dest); 5520 } 5521 5522 //Cookies 5523 $cookie_domain = '.' . L::d(BOARD_DIR); 5524 setrawcookie( "4chan_name", rawurlencode( $c_name ), $time + ( $c_name ? ( 7 * 24 * 3600 ) : -3600 ), '/', $cookie_domain ); 5525 5526 if( !strlen( $name ) ) $name = S_ANONAME; 5527 if( !strlen( $com ) ) $com = S_ANOTEXT; 5528 if( !strlen( $sub ) ) $sub = S_ANOTITLE; 5529 5530 /* since4pass */ 5531 if ($captcha_bypass && $passid && $email && strpos(" $email ", ' since4pass ') !== false) { 5532 $since4pass = get_since_4chan($passid); 5533 } 5534 else { 5535 $since4pass = 0; 5536 } 5537 5538 // April 2024 5539 /* 5540 if ($email) { 5541 $_xa24_since4pass = april_2024_parse_email($email); 5542 $since4pass = $_xa24_since4pass; 5543 } 5544 else { 5545 $_xa24_since4pass = false; 5546 } 5547 */ 5548 if (FORTUNE_TRIP == 1 && $email == 'fortune') { 5549 $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"); 5550 // Christmas 2021 5551 /* 5552 $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."); 5553 */ 5554 $fortunenum = rand( 0, sizeof( $fortunes ) - 1 ); 5555 $fortcol = "#" . sprintf( "%02x%02x%02x", 5556 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) ), 5557 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) + 2 / 3 * M_PI ), 5558 127 + 127 * sin( 2 * M_PI * $fortunenum / sizeof( $fortunes ) + 4 / 3 * M_PI ) ); 5559 $com .= "<span class=\"fortune\" style=\"color:$fortcol\"><br><br><b>Your fortune: " . $fortunes[$fortunenum] . "</b></span>"; 5560 } 5561 5562 if (DICE_ROLL == 1) { 5563 if( $email ) { 5564 if( preg_match( "/dice[ +](\\d+)[ d+](\\d+)(([ +-]+?)(-?\\d+))?/", $email, $match ) ) { 5565 $dicetxt = S_DICE_PFX . ' '; 5566 $dicenum = min( 25, $match[1] ); 5567 $diceside = $match[2]; 5568 $diceaddexpr = $match[3]; 5569 $dicesign = $match[4]; 5570 $diceadd = intval( $match[5] ); 5571 5572 for( $i = 0; $i < $dicenum; $i++ ) { 5573 $dicerand = mt_rand( 1, $diceside ); 5574 if( $i ) $dicetxt .= ", "; 5575 $dicetxt .= $dicerand; 5576 $dicesum += $dicerand; 5577 } 5578 5579 if( $diceaddexpr ) { 5580 if( strpos( $dicesign, "-" ) > 0 ) $diceadd *= -1; 5581 $diceadd_formatted = ( $diceadd >= 0 ? " + " : " - " ) . abs( $diceadd ); 5582 $dicetxt .= $diceadd_formatted; 5583 $dicesum += $diceadd; 5584 } 5585 5586 if ($dicenum > 1) { 5587 $dicetxt .= " = $dicesum"; 5588 } 5589 5590 $dicetxt .= " ({$dicenum}d{$diceside}" . ($diceaddexpr ? $diceadd_formatted : "") . ")<br><br>"; 5591 5592 $com = "<b>$dicetxt</b>" . $com; 5593 } 5594 } 5595 } 5596 5597 // fixme: this is needed for bypassing the r9k filter. $email gets reset below. 5598 $options_field = $email; 5599 5600 $emails = $email; 5601 $admin_highlight = false; 5602 $uid = null; 5603 $capcode = 'none'; 5604 $delay_refresh = false; 5605 if (strpos($email, 'capcode_') === 0) { 5606 // are we trying to capcode? 5607 if (!has_level('admin') && !has_flag('capcodename')) { 5608 $name = S_ANONAME; 5609 } 5610 // Only pass and authed users can use VIP capcodes 5611 if ($captcha_bypass) { 5612 $capcode = parse_capcode($email, $name); 5613 } 5614 else { 5615 $capcode = parse_capcode($email); 5616 } 5617 5618 $ma = ( $capcode == 'admin_highlight') ? 'admin' : $capcode; 5619 $ma = ucfirst( $ma ); 5620 5621 if( DISP_ID == 1 && $ma != 'None' ) { 5622 $uid = $ma; 5623 } 5624 5625 if( $ma != 'None' && STRIP_EXIF_ON_CAPCODE && $ext == ".jpg" ) { 5626 system( "/usr/local/bin/jpegtran -copy none -outfile '$dest' '$dest'" ); 5627 $md5 = md5_file( $dest ); 5628 } 5629 5630 if ($capcode !== 'none') { 5631 $log_mod_action = $log_capcode_post = true; 5632 } 5633 5634 setcookie('options', $email, $time + (7 * 24 * 3600), '/', $cookie_domain); 5635 } 5636 else if (isset($_COOKIE['options'])) { 5637 setcookie('options', null, $time -3600, '/', $cookie_domain); 5638 } 5639 5640 $email = ''; 5641 5642 $nameparts = explode( '</span> <span class="postertrip">!', $name ); 5643 5644 time_log( "trip" ); 5645 5646 //logtime( "starting autoban checks" ); 5647 5648 /** 5649 * Ban check step 5650 */ 5651 $ban_fields = array(); 5652 5653 if ($pass_is_bannable) { 5654 $ban_fields['password'] = $pass; 5655 } 5656 /* 5657 if ($nameparts[1]) { 5658 $ban_fields['tripcode'] = $nameparts[1]; 5659 } 5660 */ 5661 if ($passid) { 5662 $ban_fields['4pass_id'] = $passid; 5663 } 5664 5665 $user_is_banned = check_for_ban($host, $ban_fields, $resto, $userpwd && $userpwd->verifiedLevel()); 5666 5667 if ($user_is_banned) { 5668 if (!$captcha_bypass) { 5669 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'); 5670 } 5671 5672 $redirect = 'https://www.' . L::d(BOARD_DIR) . '/banned'; 5673 5674 if ($user_is_banned == 1) { 5675 // Banned 5676 error_redirect(S_BANNED, $redirect); 5677 } 5678 else if ($user_is_banned == 2) { 5679 // Warned 5680 error_redirect(S_WARNED, $redirect); 5681 } 5682 else { 5683 // Ban evasion 5684 error("Error: $user_is_banned"); 5685 } 5686 } 5687 5688 /** 5689 * Validate the maximum number of allowed threads per user 5690 */ 5691 if (!$resto) { 5692 validate_user_thread_limit( 5693 $_SERVER['REMOTE_ADDR'], 5694 isset($ban_fields['password']) ? $pass : null, 5695 $passid 5696 ); 5697 } 5698 5699 /* 5700 * Embedded data detection and banned re-post block 5701 */ 5702 if ($has_image) { 5703 // Check if the file contains embedded data. 5704 if (false && CLEANUP_UPLOADS) { 5705 // Update the size if the file was modified 5706 if (cleanup_uploaded_file($dest, $ext)) { 5707 clearstatcache(true, $dest); 5708 $fsize = filesize($dest); 5709 if (!$fsize) { 5710 error(S_TOOLARGE, $dest); 5711 } 5712 $md5 = md5_file($dest); 5713 } 5714 } 5715 5716 // Check if the uploaded file should be blocked because of previous bans 5717 if (check_for_banned_upload($md5)) { 5718 //log_spam_filter_trigger('block_banned_reupload', BOARD_DIR, $resto, $host, 1, $md5); 5719 error(S_FAILEDUPLOAD, $dest); 5720 } 5721 } 5722 5723 // --- 5724 5725 $autosage = false; 5726 5727 // See bellow wordwrap2() 5728 if (strpos($com, 'rep?~') !== false) { 5729 $com = str_replace(array('~?rep?~', '~?erep?~'), '', $com); 5730 } 5731 5732 if (!has_level() || $capcode === 'none' || BOARD_DIR == 'test') { 5733 $autosage = spam_filter_post_content_new(BOARD_DIR, $resto, $com, $sub, $name, $upfile_name, $pass, ($captcha_bypass && $passid) ? $passid : null); 5734 spam_filter_post_ip($userpwd, $resto, $has_image); 5735 } 5736 5737 if (!$capcode || $capcode == 'none') { 5738 check_md5_blacklist($md5, $original_md5, [ 5739 'resto' => $resto, 5740 'name' => $nameparts[0], 5741 'tripcode' => $nameparts[1], 5742 'filename' => "$insfile$ext", 5743 'password' => $pass, 5744 '4pass_id' => ($captcha_bypass && $passid) ? $passid : null 5745 ], $dest); 5746 } 5747 5748 spam_filter_post_trip( $name, $trip, $dest ); 5749 5750 time_log( "ab" ); 5751 5752 // Only process linebreaks for non-html posts 5753 if (!$log_html_post) { 5754 $com = nl2br($com, false); 5755 $com = str_replace("\n", "", $com); //\n is erased 5756 } 5757 5758 $com .= $exiftext; // must be done after spam filter, since it has a javascript: link 5759 5760 if (SJIS_TAGS) { 5761 $com = sjis_parse($com); 5762 } 5763 5764 if( SPOILERS == 1 ) { 5765 $com = spoiler_parse( $com ); 5766 if (stripos( $com, '<s>') !== false) { 5767 $com = preg_replace('/<s>(\s|<br>|(?R))*<\/s>/', '', $com); 5768 //$com = preg_replace('/(\S)<s>(.*?)<\/s>(\S)/', '\\1\\2\\3', $com); 5769 } 5770 } 5771 /* 5772 if( JSMATH == 1 ) { 5773 $com = jsmath_parse( $com ); 5774 } 5775 */ 5776 if( CODE_TAGS ) { 5777 $com = preg_replace( '#(\[code\](.{0,6})\[\/code\])#', '\\2', $com ); 5778 5779 $com = code_parse( $com ); 5780 5781 $com = str_replace( '<pre class="prettyprint"><br>', '<pre class="prettyprint">', $com ); 5782 $com = preg_replace( '#(<br>){4,}#', '<br><br><br>', $com ); 5783 } 5784 5785 if (OP_MARKUP && (!$resto || is_poster_op($host, $pass, $resto))) { 5786 $com = parse_op_markup($com); 5787 } 5788 5789 // pull this down to here to get rid of any shenanigans 5790 if (!$resto) { // new threads require subject or comment 5791 if ($sub === '' && ($com === '' || preg_match('/^(?:<br>|\s)+$/', $com))) { 5792 if ($options_field === '' || !$has_image || !has_level()) { 5793 error(S_NOTEXT_OP, $dest); 5794 } 5795 } 5796 else if (TEXT_ONLY && $sub === '') { 5797 error(S_NOSUB, $dest); 5798 } 5799 } 5800 else if (!$has_image && ($com === '' || preg_match('/^(?:<br>|\s)+$/', $com))) { // replies without image 5801 error(S_NOTEXT, $dest); 5802 } 5803 5804 if( WORD_FILT && $word_filters_enabled ) { 5805 $com = word_filter( $com, "com" ); 5806 if( $sub ) 5807 $sub = word_filter( $sub, "sub" ); 5808 $namearr = explode( '</span> <span class="postertrip">', $name ); 5809 if( strstr( $name, '</span> <span class="postertrip">' ) ) { 5810 $nametrip = '</span> <span class="postertrip">' . $namearr[1]; 5811 } else { 5812 $nametrip = ""; 5813 } 5814 if( $namearr[0] != S_ANONAME ) 5815 $name = word_filter( $namearr[0], "name" ) . $nametrip; 5816 } 5817 5818 /*if( $html != 1 || ( !has_level('manager') ) ) { 5819 $com = wordwrap2( $com, 35, "{{w_br}}" ); 5820 }*/ 5821 5822 // April 2022 5823 /* 5824 if (strpos($com, ':') !== false) { 5825 $com = april_process_post_emotes($com, $_COOKIE['xa_sid']); 5826 } 5827 */ 5828 $com = normalize_and_linkify($com); 5829 5830 //$html = isset( $_POST['html'] ) && $_POST['html'] == 1; 5831 $html = 0; 5832 if( (!has_level('manager') && !has_flag('html')) || $html != 1 ) { 5833 $com = wordwrap2( $com, 35, "{{w_br}}" ); 5834 } 5835 5836 $com = preg_replace( '#(>>>/[a-z0-9]+/[^ <$]*|>>[0-9]+)#', '~?rep?~\\1~?erep?~', $com ); 5837 $com = preg_replace( "!(^|r>|r> )(>[^<]*)!", "\\1<span class=\"quote\">\\2</span>", $com ); 5838 $com = preg_replace( '#~?rep?~<span class="quote">(.+?)</span>~?erep?~#', '\\1', $com ); 5839 5840 $com = str_replace( array( '~?rep?~', '~?erep?~' ), '', $com ); 5841 5842 $com = str_replace( '{{w_br}}', '<wbr>', $com ); 5843 5844 $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 5845 if( $admin_highlight ) { 5846 $com = "<div style=\"$admin_style\">$com</div>"; 5847 } 5848 5849 if( DISP_ID == 1 && !$uid ) { 5850 $uid = $is_sage && !META_BOARD && !DISP_ID_NO_HEAVEN ? "Heaven" : generate_uid( $resto, $time ); 5851 } 5852 5853 // Validate comment for OPs by regex 5854 if (!$resto && COM_REGEX) { 5855 if (preg_match(COM_REGEX, $com) === 0) { 5856 error(S_INVALID_COM); 5857 } 5858 } 5859 5860 //post text is now completely created, thumbnail not 5861 5862 if( !$silent_reject ) { 5863 //logtime( "Before flood check" ); 5864 $may_flood = has_level( 'janitor' ); 5865 5866 if (!$may_flood || (!has_level() && (META_BOARD || $_POST['name'] != ''))) { 5867 if( $com ) { 5868 // Check for duplicate comments 5869 $query = "select sql_no_cache max(time) from `%s` where com='%s' " . 5870 "and host='%s' " . 5871 "and time>%d"; 5872 $result = mysql_board_call( $query, SQLLOG, $com, $host, $time - RENZOKU_DUPE ); 5873 if( $ltime = mysql_result( $result, 0, 0 ) ) { 5874 //check_fail_floodcheck($com); 5875 $str = sprintf(S_RENZOKU_DUP, sec2hms( ( $ltime + RENZOKU_DUPE ) - $time, false, true ) ); 5876 error( $str, $dest ); 5877 } 5878 mysql_free_result( $result ); 5879 } 5880 5881 /** 5882 * Posting cooldowns 5883 */ 5884 if (!$resto) { 5885 /** 5886 * New threads 5887 */ 5888 $query = "select max(time) from `%s` where time>%d " . 5889 "and host='%s' and root>0"; //root>0 == non-sticky 5890 $result = mysql_board_call( $query, SQLLOG, ( $time - RENZOKU3 ), $host ); 5891 if( $ltime = mysql_result( $result, 0, 0 ) ) { 5892 $str = sprintf(S_RENZOKU3, sec2hms( ( $ltime + RENZOKU3 ) - $time, false, true ) ); 5893 error( $str, $dest ); 5894 } 5895 mysql_free_result( $result ); 5896 // Cross-board cooldown 5897 $query = "SELECT 1 FROM user_actions WHERE ip = %d AND action = 'new_thread' AND board != '%s' AND time >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)"; 5898 $result = mysql_global_call($query, ip2long($host), BOARD_DIR); 5899 if (mysql_num_rows($result) > 0) { 5900 error( S_RENZOKU3, $dest ); // You must wait longer before posting another thread 5901 } 5902 } 5903 else { 5904 /** 5905 * Replies 5906 * Pass users have lower cooldowns 5907 */ 5908 5909 // Check for same image flood first 5910 if ($has_image && $resto) { 5911 $query = "SELECT time FROM `%s` WHERE host = '%s' AND md5 = '%s' AND resto != %d ORDER BY no DESC LIMIT 1"; 5912 5913 $result = mysql_board_call($query, SQLLOG, $host, $md5, $resto); 5914 5915 if ($flood_row = mysql_fetch_assoc($result)) { 5916 $last_time = (int)$flood_row['time']; 5917 5918 $cooldown = RENZOKU_DUPE; 5919 $cooldown_error = S_RENZOKU2_DUP; 5920 5921 if ($captcha_bypass) { 5922 $cooldown = ceil((int)$cooldown / 2); 5923 } 5924 else { 5925 $cooldown_error .= S_RENZOKU_PASS; 5926 } 5927 5928 if ($last_time > $time - $cooldown) { 5929 error(sprintf($cooldown_error, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 5930 } 5931 } 5932 } 5933 5934 // Now the standard cooldown 5935 $query = "SELECT time, resto, fsize FROM `%s` WHERE host = '%s' AND resto > 0 ORDER BY no DESC LIMIT 1"; 5936 5937 $result = mysql_board_call($query, SQLLOG, $host); 5938 5939 if ($flood_row = mysql_fetch_assoc($result)) { 5940 $last_time = (int)$flood_row['time']; 5941 5942 if ($has_image) { 5943 $cooldown = RENZOKU2; 5944 $cooldown_error = S_RENZOKU2; 5945 } 5946 else { 5947 $cooldown = RENZOKU; 5948 $cooldown_error = S_RENZOKU; 5949 } 5950 5951 if ($captcha_bypass) { 5952 $cooldown = ceil((int)$cooldown / 2); 5953 } 5954 else { 5955 $cooldown_error .= S_RENZOKU_PASS; 5956 } 5957 5958 if ($last_time > $time - $cooldown) { 5959 error(sprintf($cooldown_error, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 5960 } 5961 } 5962 } 5963 /* 5964 if (SAVE_XFF == 1 && $xff) { 5965 // Check for multiple ips with same xff 5966 $result = mysql_global_call( "select count(distinct ip)>2 from xff where xff='%s' and is_live=1", $xff ); 5967 if( mysql_result( $result, 0, 0 ) ) { 5968 auto_ban_poster( $name, 14, 1, "Detected 3 proxies for same IP", "Proxy/Tor exit node." ); 5969 error( S_GENERICERROR, $dest ); 5970 } 5971 // Check for multiple xffs with same ip? 5972 } 5973 */ 5974 // Check for OP bump limiting 5975 if ($resto && RENZOKU_OP) { 5976 $query = 'SELECT host, time FROM `%s` WHERE no = %d'; 5977 $query = mysql_board_call($query, SQLLOG, $resto); 5978 if ($query) { 5979 $result = mysql_fetch_assoc($query); 5980 // Poster is OP 5981 if ($result && $result['host'] === $host) { 5982 // OP can only bump his thread RENZOKU_OP_TIME seconds after its creation 5983 if ($result['time'] > ($time - RENZOKU_OP_TIME)) { 5984 $is_sage = 1; 5985 } 5986 // OP can only bump his thread every RENZOKU_OP_TIME2 seconds 5987 else { 5988 $query2 = "SELECT time FROM `%s` WHERE host = '%s' AND resto = %d ORDER BY no DESC LIMIT 1"; 5989 $query2 = mysql_board_call($query2, SQLLOG, $host, $resto); 5990 if ($query2) { 5991 $result = mysql_fetch_assoc($query2); 5992 if ($result && $result['time'] > ($time - RENZOKU_OP_TIME2)) { 5993 $is_sage = 1; 5994 } 5995 } 5996 mysql_free_result($query2); 5997 } 5998 } 5999 } 6000 mysql_free_result($query); 6001 } 6002 } 6003 6004 // Minimal cooldowns for authed users (3s) 6005 if ($may_flood) { 6006 $query = "SELECT time FROM `%s` WHERE host = '%s' ORDER BY no DESC LIMIT 1"; 6007 6008 $result = mysql_board_call($query, SQLLOG, $host); 6009 6010 if ($flood_row = mysql_fetch_assoc($result)) { 6011 $last_time = (int)$flood_row['time']; 6012 6013 $cooldown = 5; 6014 6015 if ($last_time > $time - $cooldown) { 6016 error(sprintf(S_RENZOKU, sec2hms($last_time + $cooldown - $time, false, true)), $dest); 6017 } 6018 } 6019 } 6020 6021 time_log( "fc" ); 6022 6023 $tensorchan_score = 0; 6024 6025 // thumbnail 6026 $image_path = ""; 6027 $m_img = false; 6028 if ($has_image) { 6029 if( USE_THUMB && !UPLOAD_BOARD ) { 6030 // Detect and block NSFW content 6031 $_need_inference = tensorchan_is_needed($userpwd, $resto, $W, $H, $ext); 6032 6033 if ($_need_inference) { 6034 $_tensor_png = false; 6035 } 6036 else { 6037 $_tensor_png = null; 6038 } 6039 6040 $ret = make_thumb( $dest, $tim, $ext, $resto, $TN_W, $TN_H, $tmd5, $webm_sar, $_tensor_png); 6041 6042 if (!$ret && $ext != ".pdf") { 6043 error(S_IMGFAIL, $dest); 6044 } 6045 6046 if ($_need_inference && $_tensor_png) { 6047 $tensorchan_score = tensorchan_check_nsfw($_tensor_png); 6048 unset($_tensor_png); 6049 } 6050 } 6051 6052 $name_part = UPLOAD_BOARD ? $insfile : $tim; 6053 $image_path = IMG_DIR . $name_part . $ext; 6054 if( move_uploaded_file( $dest, $image_path ) === false ) { 6055 error( S_FAILEDUPLOAD, $dest ); 6056 } 6057 chmod( $image_path, 0664 ); 6058 6059 if (MOBILE_IMG_RESIZE) { 6060 $m_img = resize_mobile_image($image_path, $W, $H, $fsize, $tim, $ext); 6061 } 6062 6063 // Oekaki 6064 if (ENABLE_PAINTERJS) { 6065 // Replays 6066 if (ENABLE_OEKAKI_REPLAYS) { 6067 $oe_replay_path = null; 6068 6069 if (isset($_FILES['oe_replay']) && $_FILES['oe_replay']['name'] === 'tegaki.tgkr' && $insfile === 'tegaki') { 6070 if (oekaki_validate_replay($_FILES['oe_replay']) === true) { 6071 $oe_replay_path = IMG_DIR . $tim . '.tgkr'; 6072 6073 if (move_uploaded_file($_FILES['oe_replay']['tmp_name'], $oe_replay_path) === false) { 6074 error(S_FAILEDUPLOAD); 6075 } 6076 6077 chmod($oe_replay_path, 0664); 6078 } 6079 } 6080 6081 // Oekaki meta 6082 if (isset($_POST['oe_time'])) { 6083 if (isset($_POST['oe_src']) && $resto) { 6084 $oe_src_pid = oekaki_get_valid_src_pid($_POST['oe_src'], BOARD_DIR, $resto); 6085 } 6086 else { 6087 $oe_src_pid = null; 6088 } 6089 6090 $com .= oekaki_format_info( 6091 $_POST['oe_time'], 6092 $oe_replay_path ? $tim : null, 6093 $oe_src_pid 6094 ); 6095 } 6096 } 6097 } 6098 } 6099 6100 //logtime( "Thumbnail created" ); 6101 time_log( "t" ); 6102 6103 // Infrequent flood check (dupe image) 6104 if( $has_image && (!$capcode || $capcode === 'none')) { 6105 if ($resto) { 6106 $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); 6107 } 6108 else { 6109 $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); 6110 } 6111 6112 if( mysql_num_rows( $result ) ) { 6113 list( $dupeno, $duperesto ) = mysql_fetch_row( $result ); 6114 if( !$duperesto ) $duperesto = $dupeno; 6115 error( '' . S_DUPE . ' <a href="//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $duperesto . PHP_EXT2 . '#p' . $dupeno . '">here</a>.', $dest ); 6116 } 6117 6118 if ($resto && MAX_IMG_REPOST_COUNT > 0) { 6119 $_query = 'SELECT COUNT(*) FROM `' . SQLLOG . "` WHERE archived = 0 AND resto != 0 AND `md5` = '%s' AND filedeleted = 0"; 6120 6121 $result = mysql_board_call($_query, $md5); 6122 6123 if ($result) { 6124 $_count = (int)mysql_fetch_row($result)[0]; 6125 6126 if ($_count >= MAX_IMG_REPOST_COUNT) { 6127 error(S_DUPE); 6128 } 6129 } 6130 } 6131 6132 if ( defined('SQLLOGMD5') ) { 6133 // TODO: There's a race here. This should just be INSERT and check for failure! 6134 $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); 6135 if ( mysql_num_rows( $result ) ) { 6136 list( $dc_now, $dc_filename, $dc_md5p ) = mysql_fetch_row( $result ); 6137 6138 if( $dc_now ) { 6139 error('Error: You must wait longer before reposting this file.', $dest ); 6140 } 6141 } 6142 } 6143 } 6144 6145 $rootpredicate = $resto ? "0" : "now()"; 6146 6147 // ROBOT9000 6148 if (defined('ROBOT9000') && ROBOT9000) { 6149 // Logged in uses can bypass r9k by using the "bypass_r9k" command in the Options field. 6150 // Capcoded posts always bypass r9k. 6151 if (($options_field !== 'bypass_r9k' || !has_level('janitor')) && $capcode === 'none') { 6152 require_once 'plugins/robot9000.php'; 6153 $r9k_status = r9k_process($com, $md5, ip2long($host)); 6154 if ($r9k_status !== R9K_OK) { 6155 error($r9k_status, $dest ); 6156 } 6157 } 6158 } 6159 6160 // FIX ME, comments with html get truncated and break the layout, but only mods and janitors can bypass the regular limits 6161 if (strlen($com) > 65536) { 6162 error(S_TOOLONG, $dest); 6163 } 6164 6165 //logtime( "Before insertion" ); 6166 6167 //find sticky & autosage 6168 // auto-sticky 6169 //$sticky = false; 6170 // autosagin is now done in spam_filter_post_content 6171 //$autosage = spam_filter_should_autosage( $com, $sub, $name, $fsize, $resto, $W, $H, $dest, $insertid ); 6172 6173 //old auto-sticky code -- disabled 6174 // if(defined('AUTOSTICKY') && AUTOSTICKY) { 6175 // $autosticky = preg_split("/,\s*/", AUTOSTICKY); 6176 // if($resto == 0) { 6177 // if($insertid % 1000000 == 0 || in_array($insertid,$autosticky)) 6178 // $sticky = true; 6179 // } 6180 // } 6181 6182 $flag_cols = ""; 6183 $flag_vals = ""; 6184 6185 if( $captcha_bypass ) { 6186 $flag_cols .= ',4pass_id'; 6187 $flag_vals .= ",'" . $passid . "'"; 6188 } 6189 6190 if ($since4pass) { 6191 $flag_cols .= ',since4pass'; 6192 $flag_vals .= ",$since4pass"; 6193 } 6194 /* 6195 if( $sticky ) { 6196 $flag_cols .= ",sticky"; 6197 $flag_vals .= ",1"; 6198 } 6199 */ 6200 //permasage just means "is sage" for replies 6201 if( $resto ? $is_sage : $autosage ) { 6202 $flag_cols .= ",permasage"; 6203 $flag_vals .= ",1"; 6204 } 6205 6206 if( $capcode ) { 6207 $flag_cols .= ',capcode'; 6208 $flag_vals .= ",'$capcode'"; 6209 } 6210 6211 if ($m_img) { 6212 $flag_cols .= ',m_img'; 6213 $flag_vals .= ",1"; 6214 } 6215 6216 //$country = geoip_country_code_by_addr( $_SERVER['REMOTE_ADDR'] ); 6217 //if( !$country ) $country = 'XX'; 6218 $geo_data = GeoIP2::get_country($_SERVER['REMOTE_ADDR']); 6219 6220 if ($geo_data && isset($geo_data['country_code'])) { 6221 $country = $geo_data['country_code']; 6222 6223 // FIXME: football cups 6224 /* 6225 if (BOARD_DIR === 'sp' && $country === 'GB' && isset($geo_data['sub_code'])) { 6226 if ($geo_data['sub_code'] === 'WLS') { 6227 $country = 'XW'; 6228 } 6229 else if ($geo_data['sub_code'] === 'SCT') { 6230 $country = 'XS'; 6231 } 6232 else if ($geo_data['sub_code'] !== 'NIR') { 6233 $country = 'XE'; 6234 } 6235 } 6236 */ 6237 } 6238 else { 6239 $country = 'XX'; 6240 } 6241 6242 // User Agent ID 6243 $browser_id = spam_filter_get_browser_id(); 6244 6245 // Request Signature 6246 $_req_sig = spam_filter_get_req_sig(); 6247 6248 /** 6249 * Flood checks 6250 */ 6251 $_threat_score = 0; 6252 6253 if (!$captcha_bypass) { 6254 $_pwd_known = $userpwd && $userpwd->isUserKnownOrVerified(360); 6255 $_pwd_trusted = $userpwd && ($userpwd->postCount() > 10 || $userpwd->verifiedLevel()); 6256 $_pwd_verified = $userpwd && $userpwd->verifiedLevel(); 6257 6258 if (!$_pwd_known) { 6259 $_threat_score = spam_filter_get_threat_score($country, !$resto, true); 6260 } 6261 6262 // Sample the user agent of known users 6263 if (false && $userpwd && $userpwd->isUserKnownOrVerified(4320) && $userpwd->postCount() > 10) { 6264 if (mt_rand(0, 9999) < 2000) { 6265 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6266 log_spam_filter_trigger('log_safe_req', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6267 } 6268 } 6269 6270 if ($userpwd && !$userpwd->verifiedLevel() && $userpwd->postCount() > 0 && $userpwd->pwdLifetime() < 86400 && $userpwd->maskChanged()) { 6271 log_spam_filter_trigger('log_mask_changed', BOARD_DIR, $resto, $host, 1); 6272 } 6273 6274 if (!$_pwd_known && ($_threat_score > 0.31)) { 6275 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6276 log_spam_filter_trigger('block_txt_threat', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6277 show_post_successful_fake($resto); 6278 return; 6279 } 6280 6281 if (false && !$_pwd_known && $resto == 18361689 && BOARD_DIR === 'fa' && mt_rand(0, 9) >= 1 && $country != 'US') { 6282 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6283 log_spam_filter_trigger('block_other', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6284 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6285 } 6286 6287 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'g' && strpos($_thread_sub, '/aicg/') !== false) { 6288 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6289 log_spam_filter_trigger('block_aicg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6290 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6291 } 6292 6293 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_com, '/lolg/') !== false) { 6294 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6295 log_spam_filter_trigger('block_lolg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6296 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6297 //show_post_successful_fake($resto); 6298 //return; 6299 } 6300 6301 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_com, '/overwatch') !== false) { 6302 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6303 log_spam_filter_trigger('block_owg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6304 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6305 //show_post_successful_fake($resto); 6306 //return; 6307 } 6308 6309 if (false && !$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'fa' && strpos($_thread_sub, 'Workwear General') !== false) { 6310 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6311 log_spam_filter_trigger('block_denim', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6312 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6313 //show_post_successful_fake($resto); 6314 //return; 6315 } 6316 6317 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/bag/') !== false && $browser_id === '04d2237a2') { 6318 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6319 log_spam_filter_trigger('block_bag', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6320 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6321 } 6322 6323 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)) { 6324 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6325 log_spam_filter_trigger('block_peridot', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6326 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6327 } 6328 6329 if (!$_pwd_trusted && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, 'granblue') !== false) { 6330 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6331 log_spam_filter_trigger('block_gbfg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6332 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6333 //show_post_successful_fake($resto); 6334 //return; 6335 } 6336 6337 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'v' && strpos($_thread_sub, 'gamesdonequick') !== false && $_threat_score >= 0.09 && mt_rand(0, 9) >= 1) { 6338 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6339 log_spam_filter_trigger('block_adgq', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6340 show_post_successful_fake($resto); 6341 return; 6342 } 6343 6344 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/zzz/') !== false && $_threat_score >= 0.09 && mt_rand(0, 9) >= 1) { 6345 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6346 log_spam_filter_trigger('block_zzz', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6347 show_post_successful_fake($resto); 6348 return; 6349 } 6350 6351 if (!$_pwd_verified && $resto && $has_image && BOARD_DIR === 'vg' && strpos($_thread_sub, '/funkg/') !== false && $_threat_score >= 0.09) { 6352 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6353 log_spam_filter_trigger('block_funkg', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6354 show_post_successful_fake($resto); 6355 return; 6356 } 6357 6358 if (!$has_image) { 6359 if (!$_pwd_verified && $_threat_score >= 0.09 && mt_rand(0, 9) >= 5 && BOARD_DIR !== 'f') { 6360 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6361 log_spam_filter_trigger('block_pub_prox', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6362 show_post_successful_fake($resto); 6363 return; 6364 } 6365 } 6366 else { 6367 if (!$resto) { 6368 $_thres = 3; 6369 } 6370 else { 6371 $_thres = 4; 6372 } 6373 6374 if (!$_pwd_verified && $_threat_score >= 0.09 && mt_rand(0, 9) >= $_thres && BOARD_DIR !== 'f') { 6375 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6376 log_spam_filter_trigger('block_pub_prox', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6377 if (mt_rand(0, 1)) { 6378 error(S_IPRANGE_BLOCKED_IMG . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6379 } 6380 else { 6381 show_post_successful_fake($resto); 6382 return; 6383 } 6384 } 6385 } 6386 6387 if (!$_pwd_known && $resto && $has_image && BOARD_DIR === 'vg' && $country === 'US' && isset($_COOKIE['_tcs']) && strpos($_COOKIE['_tcs'], '.America/Bogota.') !== false) { 6388 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6389 log_spam_filter_trigger('block_bag_scat', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6390 show_post_successful_fake($resto); 6391 return; 6392 } 6393 6394 // FLOOD CHECK 6395 $flood_status = 0;//spam_filter_is_post_flood($host, BOARD_DIR, $resto, null); 6396 6397 if ($flood_status === 3) { 6398 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6399 6400 log_spam_filter_trigger('block_flood_check', BOARD_DIR, $resto, $host, $flood_status, $_bot_headers); 6401 6402 // Raw thread flood 6403 if ($flood_status === 3) { 6404 error(S_FAILEDUPLOAD); 6405 } 6406 else { 6407 show_post_successful_fake($resto); 6408 return; 6409 } 6410 } 6411 6412 // Check Cloudflare's bot score and block using the lenient rangeban message 6413 if ($userpwd && !$userpwd->isUserKnownOrVerified(1440) && isset($_SERVER['HTTP_X_BOT_SCORE'])) { 6414 if (spam_filter_is_pwd_blocked($userpwd->getPwd(), 'block_bm_bot', 24)) { 6415 log_spam_filter_trigger('block_bm_bot_pwd', BOARD_DIR, $resto, $host, $_SERVER['HTTP_X_BOT_SCORE']); 6416 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6417 } 6418 6419 if (spam_filter_is_likely_automated($memcached)) { 6420 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6421 write_to_event_log('block_bm_bot', $host, [ 6422 'pwd' => $userpwd->getPwd(), 6423 'arg_num' => $_SERVER['HTTP_X_BOT_SCORE'], 6424 'board' => BOARD_DIR, 6425 'thread_id' => $resto, 6426 'meta' => $_bot_headers 6427 ]); 6428 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6429 } 6430 } 6431 6432 // If the country has changed, log the pwd and then block it for the next 24h 6433 if ($userpwd && !$userpwd->verifiedLevel() && $userpwd->postCount() < 20) { 6434 if (spam_filter_has_country_changed($userpwd->getPwd())) { 6435 log_spam_filter_trigger('block_country_changed', BOARD_DIR, $resto, $host, 1); 6436 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6437 } 6438 6439 if ($userpwd->envChanged()) { 6440 write_to_event_log('country_changed', $host, [ 6441 'pwd' => $userpwd->getPwd(), 6442 'arg_str' => $country, 6443 'board' => BOARD_DIR, 6444 'thread_id' => $resto 6445 ]); 6446 6447 log_spam_filter_trigger('block_country_changed', BOARD_DIR, $resto, $host, 1); 6448 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1); 6449 } 6450 } 6451 } 6452 6453 /** 6454 * Custom flag selection 6455 */ 6456 if (ENABLE_BOARD_FLAGS) { 6457 if ($_POST['flag'] === '0' || !isset($board_flags_array[$_POST['flag']])) { 6458 $board_flag_code = ''; 6459 6460 // FIXME: remove this eventually as we use localStorage now 6461 if (isset($_COOKIE['4chan_flag'])) { 6462 setcookie('4chan_flag', '', $time - 3600, '/', $cookie_domain); 6463 } 6464 } 6465 else { 6466 $board_flag_code = mysql_real_escape_string($_POST['flag']); 6467 } 6468 } 6469 else { 6470 $board_flag_code = ''; 6471 } 6472 6473 if ($board_flag_code) { 6474 $board_flag_col = ',board_flag'; 6475 $board_flag_val = ",'$board_flag_code'"; 6476 } 6477 else { 6478 $board_flag_col = ''; 6479 $board_flag_val = ''; 6480 } 6481 6482 if( $resto ) calculate_indexes_to_rebuild( $resto ); 6483 6484 // Remove old replies if the thread is sticky+undead 6485 if ($is_undead_sticky && STICKY_CAP > 1) { 6486 $query = "SELECT MIN(no) FROM (SELECT no FROM `" . BOARD_DIR . "` WHERE resto = $resto ORDER BY no DESC LIMIT " . (STICKY_CAP - 1) . ") as subsel"; 6487 $result = mysql_board_call($query); 6488 if ($result) { 6489 $prune_row = mysql_fetch_row($result); 6490 6491 mysql_free_result($result); 6492 6493 $min_no = (int)$prune_row[0]; 6494 6495 if ($min_no > $resto) { 6496 $query = "SELECT no FROM `" . BOARD_DIR . "` WHERE resto = $resto AND no < $min_no"; 6497 $result = mysql_board_call($query); 6498 6499 if ($result) { 6500 while ($prune_row = mysql_fetch_assoc($result)) { 6501 delete_post((int)$prune_row['no'], '', 0, 1, 1, 0); 6502 } 6503 6504 mysql_free_result($result); 6505 } 6506 } 6507 } 6508 } 6509 6510 // April 2024 6511 /* 6512 if ($_xa24_since4pass && !UPLOAD_BOARD && !JANITOR_BOARD) { 6513 $name = april_2024_get_name(); 6514 6515 if ($emails == '$DESU' && strlen($com) < 10000) { 6516 $com .= '<div class="xa24desu"></div>'; 6517 } 6518 } 6519 */ 6520 $user_meta = encode_user_meta($browser_id, substr($_req_sig, 0, 8), $userpwd); 6521 6522 $insert_tries = 2; 6523 do { 6524 if( SKIP_DOUBLES == 1 ) mysql_board_call( "START TRANSACTION" ); 6525 $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 (" . 6526 "'" . $now . "'," . 6527 "'" . mysql_real_escape_string( $name ) . "'," . 6528 mysql_nullify( mysql_real_escape_string( $sub ) ) . "," . 6529 "'" . mysql_real_escape_string( $com ) . "'," . 6530 "'" . mysql_real_escape_string( $host ) . "'," . 6531 "'" . mysql_real_escape_string( $pass ) . "'," . 6532 "'" . mysql_real_escape_string($user_meta) . "'," . 6533 "'" . mysql_real_escape_string( $insfile ) . "'," . 6534 mysql_nullify( $ext ) . "," . 6535 (int)$W . "," . 6536 (int)$H . "," . 6537 (int)$TN_W . "," . 6538 (int)$TN_H . "," . 6539 "'" . $tim . "'," . 6540 (int)$time . "," . 6541 (int)$time . "," . 6542 mysql_nullify( $md5 ) . "," . 6543 (int)$fsize . "," . 6544 $rootpredicate . "," . 6545 (int)$resto . 6546 $flag_vals . "," . 6547 mysql_nullify( $tmd5 ) . "," . 6548 mysql_nullify( $uid ) . "," . 6549 "'$country'$board_flag_val)"; 6550 6551 if( !$result = mysql_board_call( $query ) ) { 6552 echo S_SQLFAIL; 6553 } //post registration 6554 time_log( "i" ); 6555 6556 $insertid = mysql_board_insert_id(); 6557 if( SKIP_DOUBLES == 1 ) { 6558 if( has_doubles( $insertid ) ) { 6559 mysql_board_call( "ROLLBACK" ); 6560 // retry 6561 } else { 6562 mysql_board_call( "COMMIT" ); 6563 $insert_tries = 0; 6564 } 6565 } else { 6566 $insert_tries = 0; 6567 } 6568 } while( $insert_tries-- ); 6569 6570 // Captcha bypass token 6571 if ($captcha_bypass_allow_credits && $_threat_score < 0.15) { 6572 set_twister_captcha_credits($memcached, $host, $userpwd, $time); 6573 } 6574 /* 6575 if (!$captcha_bypass) { 6576 if (mt_rand(0, 99) === 0) { 6577 write_to_event_log('known_sample', $host, [ 6578 'board' => BOARD_DIR, 6579 'thread_id' => $resto, 6580 'arg_num' => $userpwd->isUserKnown(), 6581 ]); 6582 } 6583 } 6584 */ 6585 6586 $userpwd->updatePostActivity(!$resto, $has_image); 6587 6588 $userpwd->setCookie($cookie_domain); 6589 6590 // April 2019 6591 /* 6592 if (defined('LIKE_MAX_LIKES') && LIKE_MAX_LIKES > 0) { 6593 like_update_post_score(); 6594 } 6595 */ 6596 // Halloween 2017 6597 /* 6598 if ($resto && defined('CSS_EVENT_NAME') && CSS_EVENT_NAME === 'spooky2017') { 6599 process_halloween_score($com, $resto, $passid, $pass, $pass_is_bannable); 6600 } 6601 */ 6602 // April 2018 6603 //update_april_team_scores(); 6604 6605 // Log mod action if posted with html 6606 if ($log_mod_action) { 6607 $action_log_post = array( 6608 'no' => $insertid, 6609 'name' => $name, 6610 'sub' => $sub, 6611 'com' => $com, 6612 'filename' => $insfile, 6613 'ext' => $ext 6614 ); 6615 6616 if ($log_html_post) { 6617 $action_log_post['com'] = htmlspecialchars($com, ENT_QUOTES); 6618 log_mod_action(4, $action_log_post); 6619 } 6620 // capcode posting 6621 if ($log_capcode_post) { 6622 $action_log_post['name'] .= ' ## ' . ucfirst($capcode); 6623 log_mod_action(5, $action_log_post, $capcode === 'verified'); 6624 } 6625 } 6626 6627 if( $resto ) { //sage or age action 6628 $resline = mysql_board_call( "select count(no) from `" . SQLLOG . "` where archived=0 and resto=" . $resto ); 6629 $countres = mysql_result( $resline, 0, 0 ); 6630 6631 $permasage_hours = (int)PERMASAGE_HOURS; 6632 6633 if ($permasage_hours > 0) { 6634 $time_col = 'time,'; 6635 } 6636 else { 6637 $time_col = ''; 6638 } 6639 6640 // FIXME: a similar query is done at line ~4723 6641 $resline = mysql_board_call( "select {$time_col}sticky,permasage,permaage,root from `" . SQLLOG . "` where no=" . $resto ); 6642 $resline = mysql_fetch_assoc($resline); 6643 6644 if ($resline['sticky'] || $resline['permasage']) { 6645 $root_col = ''; 6646 } 6647 else if ($resline['permaage']) { 6648 $root_col = 'root=now(),'; 6649 } 6650 else if ($is_sage || $countres >= MAX_RES) { 6651 $root_col = ''; 6652 } 6653 else if ($permasage_hours && ($time - ($permasage_hours * 3600) >= $resline['time'])) { 6654 $root_col = ''; 6655 } 6656 else { 6657 $root_col = 'root=now(),'; 6658 6659 if (!$captcha_bypass && BOARD_DIR === 'jp') { 6660 if (!spam_filter_can_bump_thread($resline['root'])) { 6661 $root_col = ''; 6662 $_bot_headers = spam_filter_format_http_headers($com, $country, "$insfile$ext", $_threat_score, $_req_sig); 6663 log_spam_filter_trigger('necrobump', BOARD_DIR, $resto, $host, 1, $_bot_headers); 6664 } 6665 } 6666 } 6667 6668 mysql_board_call("update `" . SQLLOG . "` set {$root_col}last_modified=%d where no=%d", $_SERVER['REQUEST_TIME'], $resto); 6669 } 6670 6671 if( defined( 'AUTOSTICKY' ) && AUTOSTICKY ) { 6672 $autosticky = preg_split( "/,\s*/", AUTOSTICKY ); 6673 if( $resto == 0 ) { 6674 if( $insertid % 1000000 == 0 || in_array( $insertid, $autosticky ) ) { 6675 $sticky = true; 6676 mysql_board_call( "update " . SQLLOG . " set sticky=1,root=root where no=$insertid" ); 6677 } 6678 } 6679 } 6680 6681 if( SAVE_XFF == 1 && $xff ) { 6682 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 ); 6683 } 6684 6685 if (UPLOAD_BOARD && $md5 ) { 6686 $result = mysql_board_call( "insert ignore into `%s` (filename,md5) values('%s','%s')", SQLLOGMD5, $insfile, $md5 ); 6687 } 6688 6689 // determine url to redirect to 6690 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 6691 if( !$is_nonoko && !$resto ) { 6692 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $insertid . PHP_EXT2; 6693 } else if( !$is_nonoko ) { 6694 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $resto . PHP_EXT2 . '#p' . $insertid; 6695 } else { 6696 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/'; 6697 } 6698 6699 // To let the JavaScript thread watcher know the newly created thread ID 6700 if (!$resto && isset($_POST['awt'])) { 6701 setcookie('4chan_awt', $insertid, 0, '/' . BOARD_DIR . '/', $cookie_domain); 6702 } 6703 6704 show_post_successful( $mes, $com, $insertid, $resto, $redirect, $delay_refresh ); 6705 6706 $static_rebuild = ( STATIC_REBUILD == 1 ); 6707 //logtime( "Before trim_db" ); 6708 6709 // trim database 6710 if (!$resto) { 6711 6712 if (!$static_rebuild) { 6713 trim_db(); 6714 6715 if (ENABLE_ARCHIVE && ARCHIVE_MAX_AGE) { 6716 trim_archive(); 6717 } 6718 } 6719 } 6720 6721 //logtime( "After trim_db" ); 6722 //time_log( "tr" ); 6723 6724 $_need_updatelog = true; 6725 6726 if (AUTOARCHIVE_CAP && $resto && !$sticky && !$undead && ENABLE_ARCHIVE) { 6727 if (count_thread_replies(BOARD_DIR, $resto) >= AUTOARCHIVE_CAP) { 6728 $_need_updatelog = false; 6729 archive_thread($resto); 6730 } 6731 } 6732 6733 // update html 6734 if ($_need_updatelog) { 6735 updatelog( $resto ? $resto : $insertid ); 6736 } 6737 //logtime( "Pages rebuilt" ); 6738 //time_log( "r" ); 6739 6740 // late tasks happen below here 6741 iplog_add( BOARD_DIR, $insertid, $host, $time, $resto == 0, $tim, $has_image ); 6742 6743 // Auto-report possibly nsfw post 6744 if ($tensorchan_score && $tensorchan_score > 0.5) { 6745 tensorchan_log(BOARD_DIR, $insertid, $resto, $tim, $ext, $tensorchan_score); 6746 } 6747 } else { 6748 // silent reject 6749 $insertid = 0; 6750 $noko = 0; 6751 } 6752 /* 6753 if( STATS_USER_JS ) { 6754 mysql_global_do( "UPDATE `user_stats` SET `count` = `count`+1 WHERE name='%s'", $stats_ok ); 6755 } 6756 */ 6757 } 6758 6759 // Redirects to the most rcently created thread 6760 // This is to confuse spambots 6761 function show_post_successful_fake($resto = 0, $captcha_passed = true) { 6762 $thread_id = (int)$resto; 6763 $insert_id = 0; 6764 6765 if (!$resto) { 6766 $query = 'SELECT resto FROM `' . BOARD_DIR . '` WHERE resto != 0 ORDER BY resto DESC LIMIT 1'; 6767 $res = mysql_board_call($query); 6768 if ($res) { 6769 $row = mysql_fetch_row($res); 6770 $insert_id = (int)$row[0]; 6771 } 6772 } 6773 else { 6774 $query = 'SELECT no FROM `' . BOARD_DIR . '` ORDER BY no DESC LIMIT 1'; 6775 $res = mysql_board_call($query); 6776 if ($res) { 6777 $row = mysql_fetch_row($res); 6778 $insert_id = (int)$row[0] + 1; 6779 } 6780 } 6781 6782 if (!$thread_id) { 6783 $redirect = 'https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $insert_id . PHP_EXT2; 6784 } 6785 else { 6786 $redirect = 'https://boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $thread_id . PHP_EXT2 . '#p' . $insert_id; 6787 } 6788 6789 $cookie_domain = '.' . L::d(BOARD_DIR); 6790 6791 $now = $_SERVER['REQUEST_TIME']; 6792 6793 // Name cookie 6794 $c_name = $_POST['name']; 6795 setrawcookie('4chan_name', rawurlencode($c_name), $now + ($c_name ? (7 * 24 * 3600) : -3600), '/', $cookie_domain); 6796 6797 // Password cookie 6798 $userpwd = UserPwd::getSession(); 6799 6800 if ($userpwd) { 6801 if ($captcha_passed) { 6802 $userpwd->setCookie($cookie_domain); 6803 } 6804 else if (!$userpwd->isFake() && !$userpwd->isNew()) { 6805 $userpwd->setCookie($cookie_domain); 6806 } 6807 else { 6808 UserPwd::setFakeCookie($now, $cookie_domain); 6809 } 6810 } 6811 6812 show_post_successful(null, null, $insert_id, $thread_id, $redirect); 6813 } 6814 6815 function show_post_successful( $mes, $com, $insertid, $resto, $redirect, $delay_refresh = false ) { 6816 if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json') { 6817 return show_post_successful_json($insertid, $resto); 6818 } 6819 6820 if( !$mes ) $mes = S_POSTING_DONE; 6821 $time_to_refresh = ( $delay_refresh ) ? 10 : 1; 6822 $script = "<meta http-equiv=\"refresh\" content=\"$time_to_refresh;URL=$redirect\">"; 6823 if( defined( 'POST_SUCCESSFUL_FILE' ) ) { 6824 $file = file_get_contents( POST_SUCCESSFUL_FILE ); 6825 $success = str_replace( "@REDIRECT@", $redirect, $file ); 6826 } else { 6827 // FIXME templating 6828 $icon = DEFAULT_BURICHAN ? 'favicon-ws.ico' : 'favicon.ico'; 6829 $defaultcss = DEFAULT_BURICHAN ? 'yotsubluenew' : 'yotsubanew'; 6830 $cssVersion = TEST_BOARD ? CSS_VERSION_TEST : CSS_VERSION; 6831 $sg = style_group(); 6832 6833 $styles = array( 6834 'Yotsuba New' => "yotsubanew.$cssVersion.css", 6835 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 6836 'Futaba New' => "futabanew.$cssVersion.css", 6837 'Burichan New' => "burichannew.$cssVersion.css", 6838 'Photon' => "photon.$cssVersion.css", 6839 'Tomorrow' => "tomorrow.$cssVersion.css" 6840 ); 6841 6842 $css = ''; 6843 6844 if( isset( $_COOKIE[$sg] ) ) { 6845 if( isset( $styles[$_COOKIE[$sg]] ) ) { 6846 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $styles[$_COOKIE[$sg]] . '">'; 6847 } 6848 } else { 6849 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 6850 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 6851 } 6852 6853 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 6854 $css = '<link rel="stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' 6855 . CSS_EVENT_NAME . '.' . $cssVersion . '.css" title="switch">'; 6856 } 6857 6858 if( BOARD_DIR == 'j' ) { 6859 $css = '<link rel="stylesheet" type="text/css" href="' . STATIC_SERVER . 'css/janichan.' . $cssVersion . '.css" title="Yotsuba New">'; 6860 } 6861 6862 6863 $script .= '<link rel="shortcut icon" href="//s.4cdn.org/image/' . $icon . '">'; 6864 $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>"; 6865 } 6866 echo $success; 6867 6868 if ($resto) { 6869 fastcgi_finish_request(); 6870 } 6871 } 6872 6873 function show_post_successful_json($post_id, $thread_id) { 6874 header('Content-Type: application/json'); 6875 6876 echo '{"tid":' . $thread_id . ',"pid":' . $post_id . '}'; 6877 6878 if ($thread_id) { 6879 fastcgi_finish_request(); 6880 } 6881 } 6882 6883 function resredir( $res, $delete = 0, $no_exit = false ) { 6884 if (!$_SERVER["HTTP_REFERER"]) { 6885 $proto = 'https:'; 6886 } 6887 else { 6888 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 6889 } 6890 6891 $res = (int)$res; 6892 //mysql_board_lock( true ); 6893 if( !$redir = mysql_board_call( "select no,resto from `" . SQLLOG . "` where no=" . $res ) ) { 6894 echo S_SQLFAIL; 6895 } 6896 list( $no, $resto ) = mysql_fetch_row( $redir ); 6897 6898 // if we're deleting and no post/resto (thread gone) 6899 if( !$no && $delete ) { 6900 // send us back to the board 6901 updating_index(); 6902 //mysql_board_unlock(); 6903 if (!$no_exit) { 6904 die; 6905 } 6906 else { 6907 return; 6908 } 6909 } 6910 6911 if( !JANITOR_BOARD ) { 6912 header("Cache-Control: public, max-age=2"); 6913 } 6914 6915 if( !$no ) { 6916 // If no < max(no) then this could be 410 Gone. 6917 http_response_code(404); 6918 error( S_NOTHREADERR, $dest ); 6919 } 6920 6921 if( $resto == "0" ) { // thread 6922 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $no . PHP_EXT2 . '#p' . $no; 6923 } else { 6924 $redirect = $proto . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/thread/" . $resto . PHP_EXT2 . '#p' . $no; 6925 } 6926 6927 $redirect = JANITOR_BOARD ? str_replace( 'boards.', 'sys.', $redirect ) : $redirect; 6928 6929 header("Location: $redirect", true, 301); 6930 echo "<meta http-equiv=\"refresh\" content=\"0;URL=$redirect\">"; 6931 //mysql_board_unlock(); 6932 } 6933 6934 function tensorchan_is_needed($userpwd, $resto, $W, $H, $ext) { 6935 // Inference is disabled 6936 if (!defined('TENSORCHAN_MODE') || !TENSORCHAN_MODE) { 6937 return false; 6938 } 6939 6940 // Can't check 6941 if (!$userpwd) { 6942 return false; 6943 } 6944 6945 // User is known or verified 6946 if ($userpwd->isUserKnownOrVerified(240)) { // 4 hours 6947 return false; 6948 } 6949 6950 // OPs only but the post is a reply 6951 if (TENSORCHAN_MODE == 1 && $resto) { 6952 return false; 6953 } 6954 6955 if ($W < 150 || $H < 150 || $ext == '.pdf') { 6956 return false; 6957 } 6958 6959 return true; 6960 } 6961 6962 function tensorchan_check_nsfw($tensor_png) { 6963 if (!$tensor_png) { 6964 return false; 6965 } 6966 6967 $tensor_res = tensorchan_predict($tensor_png); 6968 6969 if (!$tensor_res) { 6970 return false; 6971 } 6972 6973 if (isset($tensor_res['error'])) { 6974 write_to_event_log('tensor_err', $_SERVER['REMOTE_ADDR'], [ 6975 'board' => BOARD_DIR, 6976 'meta' => htmlspecialchars($tensor_res['error']) 6977 ]); 6978 6979 return false; 6980 } 6981 else { 6982 if (!isset($tensor_res['nsfw'])) { 6983 return false; 6984 } 6985 6986 return (float)$tensor_res['nsfw']; 6987 } 6988 } 6989 6990 function tensorchan_log($board, $post_id, $thread_id, $file_id, $file_ext, $score) { 6991 $post_id = (int)$post_id; 6992 $thread_id = (int)$thread_id; 6993 $score = (float)$score; 6994 6995 $sql =<<<SQL 6996 INSERT INTO tensor_log(board, thread_id, post_id, file_id, file_ext, nsfw) 6997 VALUES('%s', $thread_id, $post_id, '%s', '%s', $score) 6998 SQL; 6999 7000 return !!mysql_global_call($sql, $board, $file_id, $file_ext); 7001 } 7002 7003 function tensorchan_predict($data) { 7004 if (!$data) { 7005 return false; 7006 } 7007 7008 $curl = curl_init(); 7009 7010 $url = "http://danbo.int:8501/predict"; 7011 7012 curl_setopt($curl, CURLOPT_URL, $url); 7013 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 7014 curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 2); 7015 curl_setopt($curl, CURLOPT_TIMEOUT, 4); 7016 7017 curl_setopt($curl, CURLOPT_CUSTOMREQUEST , "POST"); 7018 curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 7019 7020 $headers = array( 7021 'Content-Type: application/octet-stream' 7022 ); 7023 7024 curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 7025 curl_setopt($curl, CURLOPT_USERAGENT, '4chan.org'); 7026 7027 $resp = curl_exec($curl); 7028 7029 if ($resp === false) { 7030 if ($errno = curl_errno($curl)) { 7031 $_err = 'Error (' . $errno . '): ' . curl_strerror($errno); 7032 } 7033 else { 7034 $_err = 'Something went wrong'; 7035 } 7036 7037 return ["error" => $_err]; 7038 } 7039 7040 $resp_status = curl_getinfo($curl, CURLINFO_HTTP_CODE); 7041 7042 if ($resp_status >= 300) { 7043 return ["error" => "HTTP $resp_status: $resp"]; 7044 } 7045 7046 curl_close($curl); 7047 7048 if ($resp[0] == '{') { 7049 $resp = json_decode($resp, true); 7050 } 7051 else { 7052 return ["error" => "Not a JSON response"]; 7053 } 7054 7055 return $resp; 7056 } 7057 7058 function background_color( $im, $is_thread ) 7059 { 7060 if( DEFAULT_BURICHAN == 0 ) { 7061 if( $is_thread ) { 7062 list( $r, $g, $b ) = array(0xFF, 0xFF, 0xEE); 7063 } else { 7064 list( $r, $g, $b ) = array(0xF0, 0xE0, 0xD6); 7065 } 7066 } else { 7067 if( $is_thread ) { 7068 list( $r, $g, $b ) = array(0xEE, 0xF2, 0xFF); 7069 } else { 7070 list( $r, $g, $b ) = array(0xD6, 0xDA, 0xF0); 7071 } 7072 } 7073 7074 return imagecolorallocate( $im, $r, $g, $b ); 7075 } 7076 7077 function optimize_thumb($tmppath) { 7078 system("/usr/local/bin/jpegoptim -q --strip-all '$tmppath' >/dev/null 2>&1"); 7079 } 7080 7081 // Calculates perceptual hash for a thumbnail 7082 // $img is a reference to a GD resource 7083 function get_thumb_dhash(&$img, $width, $height) { 7084 if (!$img) { 7085 return false; 7086 } 7087 7088 $data = imagecreatetruecolor(9, 8); 7089 imagecopyresampled($data, $img, 0, 0, 0, 0, 9, 8, $width, $height); 7090 imagefilter($data, IMG_FILTER_GRAYSCALE); 7091 7092 $hash = 0; 7093 $bit = 1; 7094 7095 for ($y = 0; $y < 8; $y++) { 7096 $previous = imagecolorat($data, 0, $y) & 0xFF; 7097 7098 for ($x = 1; $x < 9; $x++) { 7099 $current = imagecolorat($data, $x, $y) & 0xFF; 7100 7101 if ($previous > $current) { 7102 $hash |= $bit; 7103 } 7104 7105 $bit = $bit << 1; 7106 $previous = $current; 7107 } 7108 } 7109 7110 imagedestroy($data); 7111 7112 return sprintf("%016x", $hash); 7113 } 7114 7115 //thumbnails 7116 function make_thumb( $fname, $tim, $ext, $resto, &$TN_W, &$TN_H, &$tmd5, $webm_sar = null, &$tensor_png = null ) 7117 { 7118 $thumb_dir = THUMB_DIR; //thumbnail directory 7119 $outpath = $thumb_dir . $tim . 's.jpg'; 7120 if( !$resto ) { 7121 $width = MAX_W; //output width 7122 $height = MAX_H; //output height 7123 $jpeg_quality = 50; 7124 } else { 7125 $width = MAXR_W; //output width (imgreply) 7126 $height = MAXR_H; //output height (imgreply) 7127 $jpeg_quality = 40; 7128 } 7129 7130 if( ENABLE_PDF == 1 && $ext == '.pdf' ) { 7131 // create jpeg for the thumbnailer 7132 $pdfjpeg = IMG_DIR . $tim . '.pdf.tmp'; 7133 @exec( "/usr/local/bin/gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=jpeg -sOutputFile=$pdfjpeg $fname" ); 7134 if( !file_exists( $pdfjpeg ) ) unlink( $fname ); 7135 $fname = $pdfjpeg; 7136 } 7137 else if (ENABLE_WEBM && ($ext == '.webm' || $ext == '.mp4')) { 7138 $webm_thumb = thumb_webm($fname, $ext); 7139 7140 if (!$webm_thumb) { 7141 unlink($fname); 7142 return false; 7143 } 7144 7145 $fname = $webm_thumb; 7146 } 7147 7148 $size = @GetImageSize($fname); 7149 7150 if ($size === false) { 7151 return; 7152 } 7153 7154 // File size needs to be checked again because of cleanup_uploaded_file 7155 if (defined('MAX_DIMENSION') && $ext == '.gif') { 7156 if ($size[0] > MAX_DIMENSION || $size[1] > MAX_DIMENSION) { 7157 error(S_TOOLARGERES); 7158 } 7159 } 7160 7161 $memory_limit_increased = false; 7162 $maybe_transparent = true; 7163 // Don't increase memory limit on CLI so that the user can do it with a CLI parameter instead 7164 if( $size[0] * $size[1] > 3000000 && isset($_SERVER['REMOTE_ADDR']) ) { 7165 $memory_limit_increased = true; 7166 ini_set( 'memory_limit', memory_get_usage() + $size[0] * $size[1] * 15 ); // for huge images 7167 } 7168 switch( $size[2] ) { 7169 case 1 : 7170 $im_in = ImageCreateFromGIF( $fname ); 7171 if (!$im_in) { 7172 return; 7173 } 7174 break; 7175 case 2 : 7176 $im_in = ImageCreateFromJPEG( $fname ); 7177 if( !$im_in ) { 7178 return; 7179 } 7180 $maybe_transparent = false; 7181 break; 7182 case 3 : 7183 $im_in = ImageCreateFromPNG( $fname ); 7184 if( !$im_in ) { 7185 return; 7186 } 7187 break; 7188 default : 7189 return; 7190 } 7191 7192 $source_w = $size[0]; 7193 $source_h = $size[1]; 7194 7195 if ($webm_sar) { 7196 if ($webm_sar > 1) { 7197 $size[1] = round($size[1] / $webm_sar); 7198 7199 if ($size[1] == 0) { 7200 $size[1] = 1; 7201 } 7202 } 7203 else { 7204 $size[0] = round($size[0] * $webm_sar); 7205 7206 if ($size[0] == 0) { 7207 $size[0] = 1; 7208 } 7209 } 7210 } 7211 7212 // Resizing 7213 if( $size[0] > $width || $size[1] > $height ) { 7214 $key_w = $width / $size[0]; 7215 $key_h = $height / $size[1]; 7216 ( $key_w < $key_h ) ? $keys = $key_w : $keys = $key_h; 7217 $out_w = floor( $size[0] * $keys ); 7218 $out_h = floor( $size[1] * $keys ); 7219 } else { 7220 $out_w = $size[0]; 7221 $out_h = $size[1]; 7222 } 7223 7224 // the thumbnail is created 7225 $im_out = ImageCreateTrueColor( $out_w, $out_h ); 7226 if( !$im_out ) return; 7227 if( $maybe_transparent ) { 7228 $background = background_color( $im_out, $resto == 0 ); 7229 ImageFill( $im_out, 0, 0, $background ); 7230 } 7231 // copy resized original 7232 ImageCopyResampled( $im_out, $im_in, 0, 0, 0, 0, $out_w, $out_h, $source_w, $source_h ); 7233 $tmppath = tempnam( ini_get( "upload_tmp_dir" ), "thumb" ); 7234 // thumbnail saved 7235 ImageJPEG( $im_out, $tmppath, $jpeg_quality ); 7236 7237 // Generate a perceptual hash from the original image 7238 $tmd5 = Phash::hash($im_in, $source_w, $source_h); 7239 7240 if ($tmd5 === false) { 7241 $tmd5 = ''; 7242 } 7243 7244 // Create the PNG file for inference 7245 if ($tensor_png !== null) { 7246 $tensor_png_dim = TENSORCHAN_DIM; 7247 $tensor_png_out = ImageCreateTrueColor($tensor_png_dim, $tensor_png_dim); 7248 ImageCopyResampled($tensor_png_out, $im_in, 7249 0, 0, 0, 0, 7250 $tensor_png_dim, $tensor_png_dim, $source_w, $source_h 7251 ); 7252 $stream = fopen('php://memory','r+'); 7253 imagepng($tensor_png_out, $stream); 7254 rewind($stream); 7255 $tensor_png = stream_get_contents($stream); 7256 fclose($stream); 7257 ImageDestroy($tensor_png_out); 7258 } 7259 7260 // Cleanup 7261 ImageDestroy( $im_in ); 7262 ImageDestroy( $im_out ); 7263 7264 optimize_thumb($tmppath); 7265 7266 //$tmd5 = md5_file( $tmppath ); 7267 rename_across_device( $tmppath, $outpath ); 7268 7269 // if PDF was thumbnailed delete the orig jpeg 7270 if (isset($pdfjpeg)) { 7271 unlink($pdfjpeg); 7272 } 7273 // delete original webm frame 7274 else if (isset($webm_thumb)) { 7275 unlink($webm_thumb); 7276 } 7277 7278 if( $memory_limit_increased ) 7279 ini_restore( 'memory_limit' ); 7280 7281 $TN_W = $out_w; 7282 $TN_H = $out_h; 7283 7284 return $outpath; 7285 } 7286 7287 /* text plastic surgery */ 7288 // you can call with skip_bidi=1 if cleaning a paragraph element (like $com) 7289 function sanitize_text( $str, $skip_bidi = 0, $allow_html = false ) 7290 { 7291 global $admin, $html; 7292 // stupid unicode-hack removal 7293 if( BOARD_DIR != 'jp' && BOARD_DIR != 'a' && BOARD_DIR != 'b' && !SJIS_TAGS ) { 7294 $str = preg_replace( '#[\x{00A0}\x{3000}]#u', ' ', $str ); 7295 7296 } 7297 7298 if( !CODE_TAGS && !SJIS_TAGS) { 7299 $str = preg_replace( "/([ \t\f]|\xE2\x80\x8B|\xE2\x80\xA9)+/", " ", $str ); //collapse multiple spaces like HTML does 7300 } else { 7301 // fix tabs for html compression 7302 $str = str_replace( "\t", " ", $str ); 7303 } 7304 7305 $str = trim( $str ); //blankspace removal 7306 if( get_magic_quotes_gpc() ) { //magic quotes is deleted (?) 7307 $str = stripslashes( $str ); 7308 } 7309 7310 if ($allow_html && $html == 1 && (has_level('manager') || has_flag('html') || has_flag('developer'))) { 7311 $str = purify_html($str); 7312 } else { 7313 $str = htmlspecialchars( $str, ENT_QUOTES ); 7314 } 7315 7316 if( $skip_bidi == 0 ) { 7317 // fix malformed bidirectional overrides - insert as many PDFs as RLOs 7318 //RLO 7319 $str .= str_repeat( "\xE2\x80\xAC", substr_count( $str, "\xE2\x80\xAE" /* U+202E */ ) ); 7320 $str .= str_repeat( "‬", substr_count( $str, "‮" ) ); 7321 $str .= str_repeat( "‬", substr_count( $str, "‮" ) ); 7322 //RLE 7323 $str .= str_repeat( "\xE2\x80\xAC", substr_count( $str, "\xE2\x80\xAB" /* U+202B */ ) ); 7324 $str .= str_repeat( "‬", substr_count( $str, "‫" ) ); 7325 $str .= str_repeat( "‬", substr_count( $str, "‫" ) ); 7326 } 7327 7328 return $str; 7329 } 7330 7331 // TODO: rewrite this to use less SQL queries 7332 function report() { 7333 global $captcha_bypass; 7334 7335 $host = $_SERVER['REMOTE_ADDR']; 7336 7337 if (isset($_COOKIE['4chan_pass'])) { 7338 $userpwd = new UserPwd($host, MAIN_DOMAIN, $_COOKIE['4chan_pass']); 7339 } 7340 else { 7341 $userpwd = new UserPwd($host, MAIN_DOMAIN); 7342 } 7343 7344 if (BOARD_DIR === 'test') { 7345 require_once('forms/report-test.php'); 7346 require_once('modes/report-test.php'); 7347 } 7348 else { 7349 require_once('forms/report.php'); 7350 require_once('modes/report.php'); 7351 } 7352 7353 if (!CAN_REPORT_POSTS) { 7354 fancydie(S_CANNOTREPORTPOSTS); 7355 } 7356 7357 $no = (int)$_GET['no']; 7358 7359 if ($no <= 0) { 7360 fancydie(S_POST_DEAD); 7361 } 7362 7363 $post = report_check_post(BOARD_DIR, $no); 7364 7365 if (!isset($_COOKIE['4chan_auser']) && !isset($_COOKIE['pass_enabled'])) { 7366 $no_captcha = report_can_bypass_captcha($host, $userpwd, $post); 7367 } 7368 else { 7369 $no_captcha = false; 7370 } 7371 7372 if ($_SERVER['REQUEST_METHOD'] == 'GET') { 7373 header( 'Cache-Control: private, no-cache, must-revalidate' ); 7374 header( 'Expires: -1' ); 7375 7376 // Doesn't check bans here 7377 report_check_ip( BOARD_DIR, $no, false); 7378 7379 form_report(BOARD_DIR, $no, $no_captcha); 7380 } 7381 else { 7382 if (valid_captcha_bypass() !== true && $no_captcha !== true) { 7383 if (CAPTCHA_TWISTER) { 7384 $_m = create_memcached_instance(); 7385 7386 if (isset($_POST['t-challenge']) && $_POST['t-challenge'] === 'noop') { 7387 if (use_twister_captcha_credit($_m, $host, $userpwd) === false) { 7388 error(S_CAPTCHATIMEOUT); 7389 } 7390 } 7391 else if (is_twister_captcha_valid($_m, $host, $userpwd, BOARD_DIR, 1) === false) { 7392 error(S_BADCAPTCHA); 7393 } 7394 } 7395 else { 7396 start_recaptcha_verify(); 7397 7398 if (!$captcha_bypass) { 7399 end_recaptcha_verify(); 7400 } 7401 } 7402 } 7403 7404 // Also checks for bans 7405 report_check_ip(BOARD_DIR, $no, true); 7406 7407 if (!isset($_POST['cat']) && !isset($_POST['cat_id'])) { 7408 fancydie('Invalid category selected.'); 7409 } 7410 7411 if ($_POST['cat']) { 7412 $cat_id = (int)$_POST['cat']; 7413 } 7414 else if ($_POST['cat_id']) { 7415 $cat_id = (int)$_POST['cat_id']; 7416 } 7417 else { 7418 $cat_id = null; 7419 } 7420 7421 if (!$cat_id) { 7422 fancydie('Invalid category selected.'); 7423 } 7424 /* 7425 if ($no_captcha) { 7426 write_to_event_log('skip_rep_captcha', $host, [ 7427 'board' => BOARD_DIR, 7428 'thread_id' => $post['resto'] ? $post['resto'] : $post['no'], 7429 'post_id' => $post['no'] 7430 ]); 7431 } 7432 */ 7433 report_submit(BOARD_DIR, $no, $cat_id); // script dies here 7434 } 7435 7436 die( '</body></html>' ); 7437 } 7438 7439 /** 7440 * Archive deletion function 7441 * only works on archived posts, for authed users 7442 */ 7443 function arcdel($no, $redirect = false, $redirect_res = null) { 7444 global $onlyimgdel; 7445 7446 $delno = array(); 7447 $time = $_SERVER['REQUEST_TIME']; 7448 reset( $_POST ); 7449 7450 while ($item = each($_POST)) { 7451 if ($item[1] == 'delete') { 7452 $delno[] = $item[0]; 7453 } 7454 } 7455 7456 $numdeletions = count($delno); 7457 7458 if (!$numdeletions) { 7459 return; 7460 } 7461 7462 $rebuild_archive_json = false; 7463 7464 $rebuild = array(); 7465 7466 for ($i = 0; $i < $numdeletions; $i++) { 7467 $resto = delete_post($delno[$i], '', $onlyimgdel, 0, 1, $numdeletions == 1, false, true); 7468 if ($resto) { 7469 $rebuild[$resto] = true; 7470 } 7471 else if (!$onlyimgdel) { 7472 $rebuild_archive_json = true; 7473 } 7474 } 7475 7476 if (!has_level('janitor')) { 7477 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]); 7478 } 7479 7480 if ($redirect) { 7481 if ($redirect_res) { 7482 resredir($redirect_res, 1, true); 7483 } 7484 else { 7485 updating_index(); 7486 } 7487 7488 fastcgi_finish_request(); 7489 } 7490 7491 foreach ($rebuild as $thread_id => $true) { 7492 rebuild_archived_thread($thread_id); 7493 } 7494 7495 if ($rebuild_archive_json && ENABLE_JSON_THREADS) { 7496 generate_board_archived_json(); 7497 } 7498 } 7499 7500 function user_delete( $no, $pwd, $redirect = false, $redirect_res = null ) 7501 { 7502 global $pwdc, $onlyimgdel, $captcha_bypass; 7503 7504 if (UPLOAD_BOARD && $onlyimgdel) { 7505 error("It doesn't make any sense to do a file-only delete on a file board!"); 7506 } 7507 7508 $delno = array(); 7509 $time = $_SERVER['REQUEST_TIME']; 7510 $delflag = false; 7511 reset( $_POST ); 7512 7513 while( $item = each( $_POST ) ) { 7514 if( $item[1] == 'delete' ) { 7515 array_push( $delno, $item[0] ); 7516 $delflag = true; 7517 } 7518 } 7519 7520 $user_is_known = false; 7521 7522 if ($pwdc) { 7523 $userpwd = new UserPwd($_SERVER['REMOTE_ADDR'], MAIN_DOMAIN, $pwdc); 7524 7525 if ($userpwd) { 7526 $pwd = $userpwd->getPwd(); 7527 $user_is_known = $userpwd->maskLifetime() >= 900; 7528 } 7529 else { 7530 $pwd = null; 7531 } 7532 } 7533 else { 7534 $pwd = null; 7535 } 7536 7537 $numdeletions = count( $delno ); 7538 if( !$numdeletions ) return; 7539 $flag = false; 7540 7541 if( !has_level( 'janitor' ) ) { 7542 $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'] ) ); 7543 list( $h ) = mysql_fetch_row( $n ); 7544 7545 if( $h ) { 7546 //check_fail_floodcheck($no); 7547 error(S_FLOOD_DEL); 7548 } 7549 7550 $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'] ) ); 7551 list( $h ) = mysql_fetch_row( $n ); 7552 7553 if( $h ) { 7554 //check_fail_floodcheck($no); 7555 error(S_FLOOD_DEL); 7556 } 7557 } 7558 7559 $rebuild = array(); // keys are pages that need to be rebuilt (0 is index, of course) 7560 7561 $lazy_rebuild = false; 7562 7563 if (isset($_POST['tool']) && $_POST['tool']) { 7564 $tool = $_POST['tool']; 7565 } 7566 else { 7567 $tool = null; 7568 } 7569 7570 // Manual, single post deletion. Only rebuilds one page if deleting a reply. 7571 if ($numdeletions == 1) { 7572 $resto = delete_post( $delno[0], $pwd, $onlyimgdel, 0, 1, $numdeletions == 1, false, false, $tool, $user_is_known ); 7573 if ($resto) { 7574 $rebuild[$resto] = 1; 7575 calculate_indexes_to_rebuild($resto); 7576 $lazy_rebuild = true; 7577 } 7578 } 7579 // Other (multi, automatic, etc...) 7580 else { 7581 for( $i = 0; $i < $numdeletions; $i++ ) { 7582 $resto = delete_post( $delno[$i], $pwd, $onlyimgdel, 0, 1, $numdeletions == 1, false, false, $tool, $user_is_known ); 7583 if( $resto ) { 7584 $rebuild[$resto] = 1; 7585 } 7586 } 7587 } 7588 7589 if (!has_level('janitor')) { 7590 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]); 7591 } 7592 7593 if ($redirect) { 7594 if ($redirect_res) { 7595 resredir($redirect_res, 1, true); 7596 } 7597 else { 7598 updating_index(); 7599 } 7600 7601 fastcgi_finish_request(); 7602 } 7603 7604 rebuild_deletions($rebuild, $lazy_rebuild); 7605 } 7606 7607 function updatelog_remote( $no, $noidx ) 7608 { 7609 if( !has_level() ) die( '' ); // anti dos 7610 $no = intval( $no ); 7611 $noidx = !!$noidx; 7612 // FIXME, running this when $noidx is true breaks cross-thread quotelinks. 7613 updatelog( $no, $noidx ); 7614 } 7615 7616 function _print( $s, $echo = 1 ) 7617 { 7618 if( $echo ) { 7619 echo $s; 7620 7621 return; 7622 } 7623 7624 ob_flush(); 7625 flush(); 7626 7627 echo $s; 7628 echo str_repeat( ' ', 256 ) . "\n"; 7629 7630 ob_flush(); 7631 flush(); 7632 } 7633 7634 function fancystyle() 7635 { 7636 $style = <<<HTML 7637 <style type="text/css"> 7638 body { 7639 font-family: Helvetica, Arial, sans-serif; 7640 font-size: 12pt; 7641 } 7642 7643 h1 { 7644 margin: 0; 7645 padding: 0; 7646 } 7647 </style> 7648 HTML; 7649 7650 7651 return $style; 7652 } 7653 7654 function rebuild_catalog( $shutup = false ) 7655 { 7656 if( !has_level() ) die(); 7657 if( !$shutup ) { 7658 echo fancystyle(); 7659 echo '<h1>Rebuilding catalog...</h1>'; 7660 } 7661 7662 $start = microtime( true ); 7663 generate_catalog(); 7664 $time = round( microtime( true ) - $start, 6 ); 7665 7666 if( !$shutup ) { 7667 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">'; 7668 die(); 7669 } 7670 } 7671 7672 function rebuild_boards_json() 7673 { 7674 if( !has_level() ) die(); 7675 echo fancystyle(); 7676 echo '<h1>Rebuilding boards.json...</h1>'; 7677 7678 $start = microtime( true ); 7679 $query = mysql_global_call( "SELECT dir as board,name as title FROM boardlist ORDER BY board ASC" ); 7680 7681 $boards = array(); 7682 7683 $host = 'https://sys.int'; 7684 7685 $post = array( 7686 'mode' => 'cataloginfo' 7687 ); 7688 7689 while( $row = mysql_fetch_assoc( $query ) ) { 7690 if( $row['board'] == 'vp' ) $row['title'] = 'Pokémon'; 7691 //cataloginfo 7692 $url = "$host/{$row['board']}/imgboard.php"; 7693 7694 $ch = rpc_start_request($url, $post, null, true); 7695 7696 $response = rpc_finish_request($ch, $error, $httperror); 7697 7698 if (!$response) { 7699 die( 'Could not generate info for /' . $row['board'] . '/; ' . $error ); 7700 } 7701 7702 $json = json_decode($response, true); 7703 7704 foreach( $json as $key => $val ) { 7705 if( $key != 'board' && ctype_digit( $val ) ) $val = (int)$val; 7706 $row[$key] = $val; 7707 } 7708 7709 $boards['boards'][] = $row; 7710 } 7711 7712 // Dump resulting json on /test/ 7713 if (BOARD_DIR === 'test') { 7714 echo '<br>'; 7715 echo json_encode($boards, JSON_HEX_AMP | JSON_PRETTY_PRINT); 7716 echo '<br>'; 7717 } 7718 else { 7719 $_json = json_encode($boards, JSON_HEX_AMP); 7720 print_page(BOARDS_ROOT . 'boards.json', $_json); 7721 } 7722 7723 $time = round( microtime( true ) - $start, 6 ); 7724 echo 'Done!<br><br>Rebuilding took ' . $time . ' seconds.<br><br>No redirect here boss. Off you go.'; 7725 die(); 7726 } 7727 7728 function rebuild( $all = 0 ) 7729 { 7730 global $rebuildall, $fwritetimer; 7731 if( !has_level() ) die( '' ); // anti dos 7732 7733 if (has_flag('developer')) { 7734 error_reporting(E_ALL); 7735 } 7736 7737 header( "Pragma: no-cache" ); 7738 _print(fancystyle()); 7739 $l = $all ? 'all' : 'missing'; 7740 7741 _print( "Rebuilding $l replies and pages... <a href=\"//boards." . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/\">Go back</a><br><br>\n" ); 7742 log_cache(); 7743 trim_db(); 7744 trim_archive(); 7745 mysql_board_lock( true ); 7746 $starttime = microtime( true ); 7747 $query = "SELECT no, resto FROM `" . SQLLOG . "` WHERE resto = 0 AND archived = 0 ORDER BY root DESC"; 7748 $treeline = mysql_board_call($query); 7749 if (!$treeline) { 7750 echo S_SQLFAIL; 7751 } 7752 mysql_board_unlock(); 7753 _print( "Writing...<br>\n" ); 7754 if( $all ) { 7755 while( list( $no, $resto ) = mysql_fetch_row( $treeline ) ) { 7756 if( !$resto ) { 7757 _print( "Writing No.$no... " ); 7758 updatelog( $no, 1 ); 7759 $ext = TEST_BOARD ? "rp cache: ".realpath_cache_size() : ""; 7760 _print( "<b>DONE!</b><br> $ext\n" ); 7761 } 7762 } 7763 _print( "Writing index pages... " ); 7764 updatelog(); 7765 _print( "<b>DONE!</b><br>" ); 7766 7767 if (ENABLE_CATALOG) { 7768 _print( "Writing catalog..." ); 7769 generate_catalog( true ); 7770 _print( "<b>DONE!</b><br>" ); 7771 } 7772 7773 if (ENABLE_ARCHIVE) { 7774 _print( "Writing archive..." ); 7775 rebuild_archive_list(); 7776 _print( "<b>DONE!</b><br>" ); 7777 } 7778 } 7779 7780 $totaltime = microtime( true ) - $starttime; 7781 $proctimer = $totaltime - $fwritetimer; 7782 7783 $peakmem = memory_get_peak_usage(true) / (1024*1024.0); 7784 $usedmem = memory_get_usage(true) / (1024*1024.0); 7785 7786 $redir = '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . '/'; 7787 7788 echo <<<END 7789 <br>Total running time (lock excluded): $totaltime seconds. 7790 <br>Composed of: 7791 <br>Time spent writing files: $fwritetimer seconds. 7792 <br>Time spent processing: $proctimer seconds. 7793 <br>Memory: Peak: $peakmem MB Final: $usedmem MB 7794 <br>Pages created. 7795 <br><br> 7796 END; 7797 /* 7798 if (!TEST_BOARD) { 7799 echo <<<END 7800 Redirecting back to board. 7801 <meta http-equiv="refresh" content="10;URL=$redir"> 7802 END; 7803 } 7804 */ 7805 } 7806 7807 function rebuild_after_deletion( $no ) 7808 { 7809 if( !has_level() ) die(); 7810 7811 mysql_board_lock( true ); 7812 7813 if( !$treeline = mysql_board_call( "SELECT no FROM `" . SQLLOG . "` WHERE no = %d", $no ) ) { 7814 mysql_board_unlock(); 7815 die( S_POSTGONE ); 7816 } 7817 7818 log_cache( 0, $no ); 7819 mysql_board_unlock(); 7820 7821 updatelog( $no, 1 ); 7822 7823 die( $no . ' Rebuilt OK!' ); 7824 } 7825 7826 function updating_index() 7827 { 7828 $proto = ( stripos( $_SERVER["HTTP_REFERER"], "https" ) !== false ) ? "https:" : "http:"; 7829 echo "<!doctype html><head><meta http-equiv=\"refresh\" content=\"2;URL=$proto" 7830 . '//boards.' . L::d(BOARD_DIR) . '/' . BOARD_DIR . "/\"><title>" 7831 . S_UPDATING_INDEX . "</title></head><body><table style=\"font-family:times,serif;font-size:36pt;text-align:center;width:100%;height:300px;\"><td><strong>" 7832 . S_UPDATING_INDEX . "</strong></td></table>"; 7833 } 7834 7835 function require_request_method( $method ) 7836 { 7837 if (!isset($_SERVER['REMOTE_ADDR'])) return; 7838 $umethod = $_SERVER["REQUEST_METHOD"]; 7839 //$req = htmlspecialchars( $_REQUEST["mode"] ); 7840 if( $umethod == "OPTIONS" || ( $umethod == "HEAD" && $method == "GET" ) ) return; 7841 if( $umethod != $method ) { 7842 error( S_REJECTTEXTBAN ); 7843 } 7844 } 7845 7846 function get_catalog_info() { 7847 $arr = array( 7848 'ws_board' => (int)(CATEGORY == 'ws'), 7849 'per_page' => (int)DEF_PAGES, 7850 'pages' => (int)PAGE_MAX, 7851 'max_filesize' => ((int)MAX_KB) * 1024, 7852 'max_webm_filesize' => ((int)MAX_WEBM_FILESIZE) * 1024, 7853 'max_comment_chars' => (int)MAX_COM_CHARS, 7854 'max_webm_duration' => (int)MAX_WEBM_DURATION, 7855 'bump_limit' => (int)MAX_RES, 7856 'image_limit' => (int)MAX_IMGRES, 7857 'cooldowns' => array( 7858 'threads' => (int)RENZOKU3, 7859 'replies' => (int)RENZOKU, 7860 'images' => (int)RENZOKU2 7861 ) 7862 ); 7863 7864 if (defined('META_DESCRIPTION')) { 7865 $arr['meta_description'] = META_DESCRIPTION; 7866 } 7867 7868 if (SPOILERS) { 7869 $arr['spoilers'] = 1; 7870 if (SPOILER_NUM) { 7871 $arr['custom_spoilers'] = (int)SPOILER_NUM; 7872 } 7873 } 7874 7875 if (DISP_ID) { 7876 $arr['user_ids'] = 1; 7877 } 7878 7879 if (ENABLE_ARCHIVE) { 7880 $arr['is_archived'] = 1; 7881 } 7882 7883 if (CODE_TAGS) { 7884 $arr['code_tags'] = 1; 7885 } 7886 7887 if (SJIS_TAGS) { 7888 $arr['sjis_tags'] = 1; 7889 } 7890 7891 if (JSMATH) { 7892 $arr['math_tags'] = 1; 7893 } 7894 7895 if (SHOW_COUNTRY_FLAGS) { 7896 $arr['country_flags'] = 1; 7897 } 7898 7899 if (ENABLE_BOARD_FLAGS) { 7900 $arr['board_flags'] = get_board_flags_selector(); 7901 } 7902 7903 if (ENABLE_WEBM_AUDIO) { 7904 $arr['webm_audio'] = 1; 7905 } 7906 7907 if (MIN_W > 1) { 7908 $arr['min_image_width'] = (int)MIN_W; 7909 } 7910 7911 if (MIN_H > 1) { 7912 $arr['min_image_height'] = (int)MIN_H; 7913 } 7914 7915 if (TEXT_ONLY) { 7916 $arr['text_only'] = 1; 7917 $arr['require_subject'] = 1; 7918 } 7919 7920 if (FORCED_ANON) { 7921 $arr['forced_anon'] = 1; 7922 } 7923 7924 if (REQUIRE_SUBJECT) { 7925 $arr['require_subject'] = 1; 7926 } 7927 7928 if (ENABLE_PAINTERJS) { 7929 $arr['oekaki'] = 1; 7930 } 7931 7932 die(json_encode($arr)); 7933 } 7934 7935 /** 7936 * Generates context to append to thread links. 7937 */ 7938 function generate_href_context($sub, $com) { 7939 if (JANITOR_BOARD) { 7940 return ''; 7941 } 7942 7943 $context = ''; 7944 7945 if (strpos($sub, 'SPOILER<>') === 0) { 7946 $sub = substr($sub, 9); 7947 } 7948 7949 if ($sub !== '') { 7950 $context = cleanup_context_string($sub); 7951 } 7952 7953 if ($context === '' && $com !== '') { 7954 $context = $com; 7955 7956 if (strpos($context, '<br>') !== false) { 7957 $context = str_replace('<br>', "\n", $context); 7958 $has_br = true; 7959 } 7960 else { 7961 $has_br = false; 7962 } 7963 7964 $context = preg_replace('/(^|\s)https?:\/\/[^\s]{4,}/', '', $context); 7965 7966 if (strpos($context, '<span class="abbr">') !== false) { 7967 $context = preg_replace('/<span class="abbr">.*<\/table>/', '', $context); // ??? 7968 } 7969 if (strpos($context, '<strong') !== false) { 7970 $context = preg_replace('/<strong [^>]+>.*<\/strong>/', '', $context); // ??? 7971 } 7972 7973 if ($has_br) { 7974 $context = ltrim($context); 7975 $context = explode("\n", $context)[0]; 7976 } 7977 7978 $context = preg_replace('/<[^>]+>/', ' ', $context); 7979 $context = cleanup_context_string($context); 7980 } 7981 7982 7983 return $context; 7984 } 7985 7986 /** 7987 * Generates page title from subjects and comments 7988 * params must be already html-escaped. 7989 */ 7990 function generate_page_title($thread_id, $sub, $com) { 7991 if (JANITOR_BOARD) { 7992 return strip_tags(TITLE); 7993 } 7994 7995 if (UPLOAD_BOARD) { 7996 $sub = preg_replace('/^(\d+)\|/', '', $sub); 7997 } 7998 7999 $context = ''; 8000 8001 if (strpos($sub, 'SPOILER<>') === 0) { 8002 $sub = substr($sub, 9); 8003 } 8004 8005 if ($sub !== '') { 8006 $context = $sub; 8007 } 8008 8009 if ($context === '' && $com !== '') { 8010 if (SJIS_TAGS && strpos($com, '<span class="sjis"') !== false) { 8011 $com = preg_replace('/<span class="sjis".+?<\/span>/', '[SJIS]', $com); 8012 } 8013 $context = str_replace('<br>', ' ', $com); 8014 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8015 $context = mb_substr(strip_tags($context), 0, 50); 8016 $context = htmlspecialchars($context, ENT_QUOTES); 8017 } 8018 8019 if ($context === '') { 8020 $context = 'No.' . $thread_id; 8021 } 8022 8023 if (BOARD_DIR === 's4s') { 8024 return '[' . BOARD_DIR . '] - ' . $context; 8025 } 8026 else { 8027 return '/' . BOARD_DIR . '/ - ' . $context; 8028 } 8029 } 8030 8031 /** 8032 * Generates metatags from subjects and comments 8033 * params must be already html-escaped. 8034 */ 8035 function generate_page_metatags($sub, $com) { 8036 if (JANITOR_BOARD) { 8037 return null; 8038 } 8039 8040 $context = ''; 8041 8042 if (strpos($sub, 'SPOILER<>') === 0) { 8043 $sub = substr($sub, 9); 8044 } 8045 8046 if ($sub !== '') { 8047 $context = $sub; 8048 } 8049 8050 $ell = ''; 8051 8052 if ($context === '' && $com !== '') { 8053 $context = preg_replace('/(<br>|\s)+/', ' ', $com); 8054 $context = htmlspecialchars_decode(strip_tags($context), ENT_QUOTES); 8055 8056 if (mb_strlen($context) > 100) { 8057 $ell = '...'; 8058 $context = mb_substr($context, 0, 100); 8059 } 8060 } 8061 else { 8062 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8063 } 8064 8065 if (empty($context)) { 8066 return null; 8067 } 8068 else { 8069 $words = preg_split('/[[:punct:]\s]+/', $context); 8070 $keywords = ''; 8071 foreach ($words as $word) { 8072 if (strlen($word) > 3) { 8073 $keywords .= ',' . $word; 8074 } 8075 } 8076 } 8077 8078 $context = htmlspecialchars($context, ENT_QUOTES); 8079 $keywords = htmlspecialchars($keywords, ENT_QUOTES); 8080 8081 return array($context.$ell, $keywords); 8082 } 8083 8084 function cleanup_context_string($context) { 8085 $context = htmlspecialchars_decode($context, ENT_QUOTES); 8086 8087 $context = strtolower(preg_replace('/[^a-zA-Z0-9\s]+/', '', $context)); 8088 8089 $length = 0; 8090 8091 $words = explode(' ', $context); 8092 8093 $context = array(); 8094 8095 foreach ($words as $word) { 8096 if ($word === '') { 8097 continue; 8098 } 8099 8100 $length += strlen($word) + 1; 8101 8102 if ($length > 50) { 8103 break; 8104 } 8105 8106 $context[] = $word; 8107 } 8108 8109 $context = implode('-', $context); 8110 8111 return htmlspecialchars($context, ENT_QUOTES); 8112 } 8113 8114 /** 8115 * Embedded data detection 8116 * Dies if the PNG or JPG file contains embedded data. 8117 * Returns true if the GIF file was modified, otherwise returns false. 8118 */ 8119 function cleanup_uploaded_file($file, $type) { 8120 $full_size = filesize($file); 8121 8122 // 50KB 8123 $max_delta = 51200; 8124 8125 switch ($type) { 8126 case '.png': 8127 $clean_size = get_clean_png_size($file); 8128 break; 8129 case '.jpg': 8130 if ($full_size < 204800) { 8131 return false; 8132 } 8133 $clean_size = get_clean_jpg_size($file); 8134 break; 8135 case '.gif': 8136 if ($full_size < 204800) { 8137 return false; 8138 } 8139 $clean_size = get_clean_gif_size($file); 8140 break; 8141 case '.swf': 8142 return true; // Why? 8143 default: 8144 return false; 8145 } 8146 8147 if ($clean_size === false) { 8148 return false; 8149 } 8150 8151 // PNGs can still fail the check even if no size delta is found 8152 if ($clean_size === -1) { 8153 if ($type === '.gif') { 8154 $file = escapeshellcmd($file); 8155 $res = system("/usr/local/bin/gifsicle --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1"); 8156 if ($res !== false) { 8157 return true; 8158 } 8159 else { 8160 return false; 8161 } 8162 } 8163 else { 8164 error(S_IMGCONTAINSFILE, $file); 8165 } 8166 } 8167 8168 $delta_size = $full_size - $clean_size; 8169 8170 if ($delta_size > $max_delta) { 8171 if ($type === '.gif') { 8172 $file = escapeshellcmd($file); 8173 $res = system("/usr/local/bin/gifsicle --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1"); 8174 if ($res !== false) { 8175 return true; 8176 } 8177 } 8178 else { 8179 error(S_IMGCONTAINSFILE, $file); 8180 } 8181 } 8182 8183 return false; 8184 } 8185 8186 // Returns the size of critical data or -1 if the file contains extensions. 8187 function get_clean_gif_size($file) { 8188 $file = escapeshellcmd($file); 8189 8190 $binary = '/usr/local/bin/gifsicle'; 8191 8192 $res = shell_exec("$binary --sinfo \"$file\" 2>&1"); 8193 8194 if ($res !== null) { 8195 $size = 0; 8196 8197 if (preg_match('/ extensions [0-9]+/', $res)) { 8198 return -1; 8199 } 8200 8201 if (preg_match_all('/compressed size ([0-9]+)/', $res, $m)) { 8202 foreach ($m[1] as $frame_size) { 8203 $size += (int)$frame_size; 8204 } 8205 8206 return $size; 8207 } 8208 } 8209 8210 return false; 8211 } 8212 8213 // Returns the number of bytes in critical chunks or -1 if too many IDAT chunks are found 8214 // Returns false on error 8215 function get_clean_png_size($file) { 8216 $file = escapeshellcmd($file); 8217 8218 $binary = '/usr/local/bin/pngcrush'; 8219 8220 $res = shell_exec("$binary -m 1 -n -v \"$file\" 2>&1 1>/dev/null"); 8221 8222 if ($res !== null) { 8223 if (preg_match('/Reading (?:iTXt|tEXt|zTXt) chunk,/', $res, $m)) { 8224 if (preg_match('/ [a-z]oo: /i', $res)) { 8225 return -1; 8226 } 8227 else if (preg_match('/ (?:Software|Creation Time): ([=a-zA-Z0-9]{10,})\n/', $res, $ct)) { 8228 $_b64 = base64_decode($ct[0]); 8229 if ($_b64 && preg_match('/[0-9]/', $_b64)) { 8230 write_to_event_log('png_link', $_SERVER['REMOTE_ADDR'], [ 8231 'board' => BOARD_DIR, 8232 'meta' => htmlspecialchars($res) 8233 ]); 8234 return -1; 8235 } 8236 } 8237 } 8238 8239 if (preg_match('/in critical chunks\s+=\s+([0-9]+)/', $res, $m)) { 8240 return (int)$m[1]; 8241 } 8242 } 8243 8244 return false; 8245 } 8246 8247 function get_clean_jpg_size($file) { 8248 $eof = false; 8249 8250 $img = fopen($file, 'rb'); 8251 8252 $data = fread($img, 2); 8253 8254 if ($data !== "\xff\xd8") { 8255 fclose($img); 8256 return false; 8257 } 8258 8259 while (!feof($img)) { 8260 $data = fread($img, 1); 8261 8262 if ($data !== "\xff") { 8263 continue; 8264 } 8265 8266 while (!feof($img)) { 8267 $data = fread($img, 1); 8268 8269 if ($data !== "\xff") { 8270 break; 8271 } 8272 } 8273 8274 if (feof($img)) { 8275 break; 8276 } 8277 8278 $byte = unpack('C', $data)[1]; 8279 8280 if ($byte === 217) { 8281 $eof = ftell($img); 8282 break; 8283 } 8284 8285 if ($byte === 0 || $byte === 1 || ($byte >= 208 && $byte <= 216)) { 8286 continue; 8287 } 8288 8289 $data = fread($img, 2); 8290 8291 $length = unpack('n', $data)[1]; 8292 8293 if ($length < 1) { 8294 break; 8295 } 8296 8297 fseek($img, $length - 2, SEEK_CUR); 8298 } 8299 8300 fclose($img); 8301 8302 return $eof; 8303 } 8304 8305 /** 8306 * Generates a "Pass User Since YEAR" string for pass users 8307 */ 8308 function get_since_4chan($pass_id) { 8309 $query = "SELECT UNIX_TIMESTAMP(purchase_date) FROM pass_users WHERE user_hash = '%s' ORDER BY purchase_date ASC LIMIT 1"; 8310 8311 $res = mysql_global_call($query, $pass_id); 8312 8313 if (!$res) { 8314 return 0; 8315 } 8316 8317 $row = mysql_fetch_row($res); 8318 8319 $ts = (int)$row[0]; 8320 8321 if (!$ts) { 8322 return 0; 8323 } 8324 8325 $ts = date('Y', $ts); 8326 8327 if (!$ts) { 8328 return 0; 8329 } 8330 8331 return (int)$ts; 8332 } 8333 /* 8334 // Halloween 2017 8335 function get_halloween_score($pass_id) { 8336 $query = "SELECT score FROM halloween_tricks WHERE user_hash = '%s' LIMIT 1"; 8337 8338 $res = mysql_global_call($query, $pass_id); 8339 8340 if (!$res) { 8341 return 0; 8342 } 8343 8344 $row = mysql_fetch_row($res); 8345 8346 if (!$row) { 8347 return 0; 8348 } 8349 8350 return (int)$row[0]; 8351 } 8352 8353 // Halloween 2017 8354 /* 8355 function get_halloween_dummy_pass($name) { 8356 if (!$name) { 8357 return ''; 8358 } 8359 8360 $hashed_bits = hash_hmac('sha1', $name, 'CIaPCUkJq9n3fskmKC06tquCV/2edWqbgBeY9pk7RlQ', true); 8361 8362 $hashed_name = base64_encode($hashed_bits); 8363 8364 if (!$hashed_name) { 8365 return ''; 8366 } 8367 8368 return '_' . substr($hashed_name, 0, 9); 8369 } 8370 8371 // Halloween 2017 8372 function process_halloween_score($com, $thread_id, $this_pass_id, $this_pwd, $pass_is_bannable) { 8373 if ($com === '') { 8374 return; 8375 } 8376 8377 $thread_id = (int)$thread_id; 8378 8379 if (!$thread_id) { 8380 return; 8381 } 8382 8383 if (preg_match_all('/>>([0-9]{4,})/', $com, $m) === 1) { 8384 $post_id = (int)$m[1][0]; 8385 8386 if (!$post_id || $post_id == $thread_id) { 8387 return; 8388 } 8389 8390 $query = 'SELECT host, pwd, 4pass_id FROM `' . SQLLOG . '` WHERE no = ' . $post_id . ' AND resto = ' . $thread_id; 8391 8392 $res = mysql_board_call($query); 8393 8394 if (!$res) { 8395 return; 8396 } 8397 8398 $row = mysql_fetch_assoc($res); 8399 8400 if (!$row) { 8401 return; 8402 } 8403 8404 // Check if same person 8405 if (!$row['4pass_id'] || $row['4pass_id'] == $this_pass_id || $row['pwd'] == $this_pwd || $row['host'] == $_SERVER['REMOTE_ADDR']) { 8406 return; 8407 } 8408 8409 $long_ip = ip2long($_SERVER['REMOTE_ADDR']); 8410 8411 if (!$long_ip) { 8412 return; 8413 } 8414 8415 // Check if already gave points 8416 $query = "SELECT 1 FROM `halloween_votes` WHERE long_ip = $long_ip AND board = '" . BOARD_DIR . "' AND post_id = $post_id"; 8417 8418 $res = mysql_global_call($query); 8419 8420 if (!$res) { 8421 return; 8422 } 8423 8424 if (mysql_num_rows($res) > 0) { 8425 return; 8426 } 8427 8428 // Check if the user is known 8429 if (!spam_filter_is_user_known($long_ip, BOARD_DIR, $pass_is_bannable ? $this_pwd : null)) { 8430 return; 8431 } 8432 8433 // Good to go 8434 $query = <<<SQL 8435 INSERT INTO halloween_tricks (user_hash, score) VALUES ('%s', 1) 8436 ON DUPLICATE KEY UPDATE score = score + 1 8437 SQL; 8438 8439 mysql_global_call($query, $row['4pass_id']); 8440 8441 $query = "INSERT INTO halloween_votes (long_ip, board, post_id) VALUES ($long_ip, '" . BOARD_DIR . "', $post_id)"; 8442 8443 mysql_global_call($query); 8444 } 8445 } 8446 8447 // Halloween 2017 8448 function decrease_halloween_score($post_id, $ratio = 0.75) { 8449 $post_id = (int)$post_id; 8450 8451 if (!$post_id) { 8452 return; 8453 } 8454 8455 $query = 'SELECT 4pass_id FROM `' . SQLLOG . '` WHERE no = ' . $post_id; 8456 8457 $res = mysql_board_call($query); 8458 8459 if (!$res) { 8460 return; 8461 } 8462 8463 $pass_id = mysql_fetch_row($res)[0]; 8464 8465 if (!$pass_id) { 8466 return; 8467 } 8468 8469 $query = "UPDATE halloween_tricks SET score = FLOOR(score * %.2f) WHERE user_hash = '%s' LIMIT 1"; 8470 8471 mysql_global_call($query, $ratio, $pass_id); 8472 } 8473 8474 // Halloween 2017 8475 function get_halloween_css_cls($trick_count) { 8476 if ($trick_count >= 1000) { 8477 return " n-jol-6"; 8478 } 8479 if ($trick_count >= 500) { 8480 return " n-jol-5"; 8481 } 8482 if ($trick_count >= 200) { 8483 return " n-jol-4"; 8484 } 8485 if ($trick_count >= 100) { 8486 return " n-jol-3"; 8487 } 8488 if ($trick_count >= 50) { 8489 return " n-jol-2"; 8490 } 8491 if ($trick_count >= 25) { 8492 return " n-jol-1"; 8493 } 8494 return ''; 8495 } 8496 */ 8497 8498 /** 8499 * Checks if the user has a recent ban request. 8500 * dies with an error if user needs to be blocked. 8501 */ 8502 function check_for_ban_request($ip, $pwd = null) { 8503 $time_lim = BLOCK_ON_BR_LEN; 8504 8505 $clauses = []; 8506 8507 $clauses[] = "host = '" . mysql_real_escape_string($ip) . "'"; 8508 8509 if ($pwd) { 8510 $clauses[] = "pwd = '" . mysql_real_escape_string($pwd) . "'"; 8511 } 8512 8513 $board_sql = mysql_real_escape_string(BOARD_DIR); 8514 8515 $clauses = implode(' OR ', $clauses); 8516 8517 $query = <<<SQL 8518 SELECT tpl_name, board, TIMESTAMPDIFF(MINUTE, NOW() - $time_lim, ts) diff 8519 FROM `ban_requests` 8520 WHERE ($clauses) 8521 AND (board = '$board_sql' OR global = 1) 8522 AND warn_req = 0 8523 AND (ts > DATE_SUB(NOW(), $time_lim) OR ban_template IN (1, 2, 123, 126)) 8524 LIMIT 1 8525 SQL; 8526 8527 $res = mysql_global_call($query); 8528 8529 if (!$res || mysql_num_rows($res) !== 1) { 8530 return false; 8531 } 8532 8533 $row = mysql_fetch_assoc($res); 8534 8535 $time = (int)$row['diff']; 8536 8537 $tpl_name = rtrim(preg_replace('/\[[^\]]+\]/', '', $row['tpl_name'])); 8538 8539 // Non-expiring blocks for some global templates 8540 if ($time < 0) { 8541 error(sprintf(S_BRBLOCKED_2, $tpl_name)); 8542 } 8543 8544 $str = $time <= 1 ? '1 minute' : "$time minutes"; 8545 8546 error(sprintf(S_BRBLOCKED, $tpl_name, $str)); 8547 } 8548 8549 /** 8550 * Checks if the IP is banned, also checks for ban evasion 8551 * return 0 for not banned, 1 for banned, 2 for warned 8552 * If the ban has expired, delete the banned thumbnail and de-activate the ban 8553 */ 8554 function check_for_ban($ip, $fields = array(), $thread_id = 0, $user_verified = false) { 8555 global $captcha_bypass; 8556 8557 // Skip IP bans when the user has a valid 4chan Pass 8558 $skip_ip_bans = isset($fields['4pass_id']) && $captcha_bypass; 8559 8560 if (!$skip_ip_bans) { 8561 $fields['host'] = $ip; 8562 } 8563 8564 $expired = []; 8565 8566 $is_banned = 0; 8567 8568 foreach ($fields as $key => $value) { 8569 $query =<<<SQL 8570 SELECT no, global, board, post_num, template_id, 4pass_id, admin, reason, 8571 UNIX_TIMESTAMP(now) as starts_on, UNIX_TIMESTAMP(length) as ends_on 8572 FROM banned_users 8573 WHERE active = 1 AND $key = '%s' 8574 SQL; 8575 8576 $result = mysql_global_call($query, $value); 8577 8578 // Not banned 8579 if (mysql_num_rows($result) < 1) { 8580 continue; 8581 } 8582 8583 while ($ban = mysql_fetch_assoc($result)) { 8584 $end = (int)$ban['ends_on']; 8585 8586 // Warning 8587 if ($end && ($end - (int)$ban['starts_on'] < 1)) { 8588 $is_banned = 2; 8589 break 2; 8590 } 8591 8592 // Ban has expired 8593 if ($end && $end <= $_SERVER['REQUEST_TIME']) { 8594 $expired[] = $ban; 8595 continue; 8596 } 8597 8598 // Skip GR14 bans for pass users 8599 if ($key == '4pass_id') { 8600 if ($ban['template_id'] == 124) { // Global 14 - Proxy, VPN, or Tor Node 8601 continue; 8602 } 8603 } 8604 8605 // Skip proxy autobans for verified users 8606 if ($user_verified && $key == 'host' && $ban['admin'] === 'Auto-ban') { 8607 if (strpos($ban['reason'], 'Proxy') !== false) { 8608 continue; 8609 } 8610 } 8611 8612 if ($ban['global'] || $ban['board'] == BOARD_DIR) { 8613 $is_banned = 1; 8614 break 2; 8615 } 8616 } 8617 } 8618 8619 // Cleanup expired bans 8620 if (!empty($expired)) { 8621 $salt = file_get_contents_cached(SALTFILE); 8622 8623 $expired_ids = []; 8624 8625 foreach ($expired as $ban) { 8626 $expired_ids[] = (int)$ban['no']; 8627 8628 $hash = sha1($ban['board'] . $ban['post_num'] . $salt); 8629 8630 $file_path = BANTHUMB_ROOT . $ban['board'] . '/' . $hash . 's.jpg'; 8631 8632 if (file_exists($file_path)) { 8633 unlink($file_path); 8634 } 8635 } 8636 8637 $lim = count($expired_ids); 8638 8639 $expired_ids = implode(',', $expired_ids); 8640 $query = "UPDATE banned_users SET active = 0, unbannedon = NOW(), unbannedby = 'expiration' WHERE no IN($expired_ids) LIMIT $lim"; 8641 $result = mysql_global_do($query); 8642 } 8643 8644 return $is_banned; 8645 } 8646 8647 /** 8648 * Strips tags from webms and repairs broken streams. 8649 * This is needed to prevent people from bypassing duration limits. 8650 * $file must be safe to use as shell argument 8651 */ 8652 function remux_webm($file, $format, $strip_metadata = true) { 8653 $binary = '/usr/local/bin/ffmpeg-mp4'; 8654 8655 $out_file = $file . '_tmpff'; 8656 8657 if ($strip_metadata) { 8658 $map_meta = '-map_metadata -1 '; 8659 } 8660 else { 8661 $map_meta = ''; 8662 } 8663 8664 if ($format !== 'webm' && $format !== 'mp4') { 8665 return false; 8666 } 8667 8668 // $file and $format must be safe for shell_exec 8669 shell_exec("$binary -f $format -i \"$file\" $map_meta-bitexact -c copy -f $format -y \"$out_file\""); 8670 8671 if (!file_exists($out_file)) { 8672 return false; 8673 } 8674 8675 $ret = rename($out_file, $file); 8676 8677 clearstatcache(true, $file); 8678 8679 return $ret; 8680 } 8681 8682 /** 8683 * Remuxes and validates webm file 8684 * Returns video info on success array(width, height, sar, extension) 8685 * Dies if the file is invalid or not acceptable 8686 * $file must be safe to use as shell argument 8687 */ 8688 function validate_webm($file, $ext) { 8689 $binary = '/usr/local/bin/ffprobe-mp4'; 8690 8691 if ($ext == '.webm') { 8692 $format = 'webm'; 8693 } 8694 else if ($ext == '.mp4') { 8695 $format = 'mp4'; 8696 } 8697 else { 8698 error(S_NOREC, $file); 8699 } 8700 8701 // Remux webm to strip extra data and repair broken streams 8702 if (!remux_webm($file, $format)) { 8703 error(S_NOREC, $file); 8704 } 8705 8706 // $file and $format must be safe for proc_open 8707 $cmd = "$binary -f $format -i \"$file\" -hide_banner -show_streams -show_format -of json"; 8708 8709 $desc = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ]; 8710 8711 $pipes = []; 8712 8713 $proc = proc_open($cmd, $desc, $pipes); 8714 8715 if ($proc === false || !is_resource($proc)) { 8716 error(S_FAILEDUPLOAD, $file); 8717 } 8718 8719 $stdout = stream_get_contents($pipes[1]); 8720 fclose($pipes[1]); 8721 8722 $stderr = stream_get_contents($pipes[2]); 8723 fclose($pipes[2]); 8724 8725 $status = proc_close($proc); 8726 8727 if ($status !== 0) { 8728 error(S_FAILEDUPLOAD, $file); 8729 } 8730 8731 // Check stderr 8732 if (stripos($stderr, 'invalid') !== false) { 8733 error(S_NOREC, $file); 8734 } 8735 8736 // Check stdout 8737 $res = json_decode($stdout, true); 8738 8739 if (json_last_error() !== JSON_ERROR_NONE) { 8740 error(S_FAILEDUPLOAD, $file); 8741 } 8742 8743 //print_r($res); 8744 8745 if ($res['format']['format_name'] === 'matroska,webm') { 8746 $format = 'webm'; 8747 } 8748 else if ($res['format']['format_name'] === 'mov,mp4,m4a,3gp,3g2,mj2') { 8749 $format = 'mp4'; 8750 } 8751 else { 8752 error(S_NOREC, $file); 8753 } 8754 8755 // container duration, can be forged but we remux the file to fix this 8756 $duration = (float)$res['format']['duration']; 8757 8758 if ($duration <= 0 || $duration > MAX_WEBM_DURATION) { 8759 error(S_VIDEOTOOLONG, $file); // Duration too long 8760 } 8761 8762 $has_audio = false; 8763 $video_dims = false; 8764 8765 foreach ($res['streams'] as $stream) { 8766 $type = $stream['codec_type']; 8767 8768 if ($type === 'audio') { 8769 if (!ENABLE_WEBM_AUDIO) { 8770 error(S_AUDIODISABLED, $file); // Audio streams are not allowed 8771 } 8772 8773 // Vorbis or Opus for webm audio 8774 if ($format === 'webm') { 8775 if ($stream['codec_name'] !== 'vorbis' && $stream['codec_name'] !== 'opus') { 8776 error(S_BADAUDIO, $file); // Bad audio stream 8777 } 8778 } 8779 // AAC for mp4 audio 8780 else if ($format === 'mp4') { 8781 if ($stream['codec_name'] !== 'aac') { 8782 error(S_BADAUDIO, $file); // Bad audio stream 8783 } 8784 } 8785 else { 8786 error(S_BADAUDIO, $file); // Bad audio stream 8787 } 8788 8789 $has_audio = true; 8790 } 8791 else if ($type === 'video') { 8792 if ($video_dims) { 8793 error(S_BADSTREAM, $file); // Too many video streams 8794 } 8795 8796 // VP8 or VP9 for webm video 8797 if ($format === 'webm') { 8798 if ($stream['codec_name'] !== 'vp8' && $stream['codec_name'] !== 'vp9') { 8799 error(S_BADVIDEO, $file); // Bad video stream 8800 } 8801 } 8802 // H264 for mp4 video 8803 else if ($format === 'mp4') { 8804 if ($stream['codec_name'] !== 'h264') { 8805 error(S_BADVIDEO, $file); // Bad video stream 8806 } 8807 8808 // Reject 10 bit streams 8809 if ($stream['bits_per_raw_sample'] > 8) { 8810 error(S_NOREC, $file); 8811 } 8812 8813 // Only accept yuv420p streams 8814 if ($stream['pix_fmt'] !== 'yuv420p') { 8815 error(S_NOREC, $file); 8816 } 8817 } 8818 else { 8819 error(S_BADVIDEO, $file); // Bad video stream 8820 } 8821 8822 $width = (int)$stream['width']; 8823 $height = (int)$stream['height']; 8824 8825 if (!$width || !$height || $width > MAX_WEBM_DIMENSION || $height > MAX_WEBM_DIMENSION) { 8826 error(S_TOOLARGERES, $file); // Dimensions too big 8827 } 8828 8829 $sar = null; 8830 8831 if (isset($stream['sample_aspect_ratio'])) { 8832 $tmp_sar = explode(':', $stream['sample_aspect_ratio']); 8833 8834 $tmp_sar[0] = (int)$tmp_sar[0]; 8835 $tmp_sar[1] = (int)$tmp_sar[1]; 8836 8837 if ($tmp_sar[1] && $tmp_sar[0] !== $tmp_sar[1]) { 8838 $tmp_sar = $tmp_sar[0] / $tmp_sar[1]; 8839 8840 if ($tmp_sar < 2 && $tmp_sar > 0.5) { 8841 $sar = $tmp_sar; 8842 } 8843 } 8844 } 8845 8846 $video_dims = array($width, $height, $sar); 8847 } 8848 else { 8849 error(S_BADSTREAM, $file); // Bad stream 8850 } 8851 } 8852 8853 if (!$video_dims) { 8854 error(S_NOVIDEOSTREAM, $file); // No video streams 8855 } 8856 8857 return $video_dims; 8858 } 8859 8860 /** 8861 * Generates thumbnails for webm files 8862 * Returns the thumbnail filename on success. 8863 * Returns false if the thumbnail couldn't be generated. 8864 */ 8865 function thumb_webm($file, $ext) { 8866 $binary = '/usr/local/bin/ffmpeg-mp4'; 8867 8868 $out_file = $file . '.tmp.jpg'; 8869 8870 if ($ext === '.webm') { 8871 $format = 'webm'; 8872 } 8873 else if ($ext === '.mp4') { 8874 $format = 'mp4'; 8875 } 8876 else { 8877 return false; 8878 } 8879 8880 // $file and $format must be safe for shell_exec 8881 $res = shell_exec("$binary -f $format -i \"$file\" -vframes 1 -an -y \"$out_file\" 2>&1"); 8882 8883 if (file_exists($out_file)) { 8884 return $out_file; 8885 } 8886 8887 quick_log_to( "/www/perhost/bad-upload.log", "webm failure on $file:\n$res"); 8888 8889 return false; 8890 } 8891 8892 /** 8893 * Contest banners 468x60 8894 */ 8895 function get_contest_banner() { 8896 $query = "SELECT file_id, file_ext, board FROM contest_banners WHERE is_live = 1 ORDER BY RAND() LIMIT 1"; 8897 8898 $res = mysql_global_call($query); 8899 8900 if (!$res) { 8901 return ''; 8902 } 8903 8904 $banner = mysql_fetch_assoc($res); 8905 8906 if (!$banner) { 8907 return ''; 8908 } 8909 8910 $img_url = STATIC_SERVER . "image/contest_banners/{$banner['file_id']}.{$banner['file_ext']}"; 8911 $link_url = '//boards.' . L::d($banner['board']) . '/' . $banner['board'] . '/'; 8912 8913 return '<div><a href="' . $link_url . '"><img alt="" src="' . $img_url . '"></a></div>'; 8914 } 8915 8916 /** 8917 * Get latest post number from /j/ 8918 * Returns a json { "no": 123 } 8919 */ 8920 8921 function get_last_post_no() { 8922 $no = 0; 8923 $query = "SELECT no FROM `j` ORDER BY no DESC LIMIT 1"; 8924 $res = mysql_board_call($query); 8925 if ($res) { 8926 if ($row = mysql_fetch_row($res)) { 8927 $no = (int)$row[0]; 8928 } 8929 } 8930 echo "{\"no\":$no}"; 8931 } 8932 8933 /** 8934 * Deletes partial jsons for all live threads 8935 */ 8936 function purge_json_tails() { 8937 $query = 'SELECT no FROM `' . SQLLOG . '` WHERE resto = 0 AND archived = 0'; 8938 8939 $res = mysql_board_call($query); 8940 8941 if (!$res) { 8942 return false; 8943 } 8944 8945 while ($row = mysql_fetch_row($res)) { 8946 $thread_id = (int)$row[0]; 8947 update_json_tail_deletion($thread_id, true); 8948 } 8949 8950 return true; 8951 } 8952 8953 /** 8954 * Deletes, if necessary, the partial json tail file after post deletion. 8955 */ 8956 function update_json_tail_deletion($thread_id, $force = false) { 8957 if (!JSON_TAIL_SIZE && !$force) { 8958 return false; 8959 } 8960 8961 $tail_size = $force ? 0 : get_json_tail_size($thread_id); 8962 8963 if (!$tail_size) { 8964 $fname = RES_DIR . $thread_id . '-tail.json'; 8965 8966 if (USE_GZIP) { 8967 $fname = "$fname.gz"; 8968 } 8969 8970 if (file_exists($fname)) { 8971 return unlink($fname); 8972 } 8973 } 8974 8975 return false; 8976 } 8977 8978 /** 8979 * Returns the number of posts in the partial -tail json. 8980 * 0 if no tail json is available 8981 */ 8982 function get_json_tail_size($thread_id) { 8983 global $log; 8984 8985 $tail_size = (int)JSON_TAIL_SIZE; 8986 8987 if (!$tail_size || !isset($log[$thread_id])) { 8988 return 0; 8989 } 8990 8991 $th = $log[$thread_id]; 8992 8993 if ($th['sticky'] && $th['undead']) { 8994 $tail_size = $tail_size * 2; 8995 } 8996 8997 $post_count = count($th['children']); 8998 8999 if ($post_count >= $tail_size * 2) { 9000 return $tail_size; 9001 } 9002 else { 9003 return 0; 9004 } 9005 } 9006 9007 /** 9008 * Test function for mobile image resizing 9009 */ 9010 function resize_mobile_image($path, $w, $h, $fsize, $tim, $ext) { 9011 if ($ext !== '.jpg' && $ext !== '.png') { 9012 return false; 9013 } 9014 9015 $MAX_W = 1024; 9016 $MAX_H = 1024; 9017 $MAX_PXL = 524288; 9018 $MAX_PNG_BYTES = 524288; 9019 9020 if ($ext === '.png' && $fsize <= $MAX_PNG_BYTES) { 9021 return; 9022 } 9023 9024 if (($w > $MAX_W || $h > $MAX_H) && $w * $h > $MAX_PXL) { 9025 $jpeg_quality = 80; 9026 9027 $memory_limit_increased = false; 9028 9029 if ($w * $h > 3000000) { 9030 $memory_limit_increased = true; 9031 ini_set('memory_limit', memory_get_usage() + $w * $h * 15); 9032 } 9033 9034 if ($ext === '.jpg') { 9035 $img_in = ImageCreateFromJPEG($path); 9036 } 9037 else { 9038 $img_in = ImageCreateFromPNG($path); 9039 } 9040 9041 if (!$img_in) { 9042 error(S_FAILEDUPLOAD . ' (rmi)', $path); 9043 } 9044 9045 $ratio = $w / $h; 9046 9047 if ($ratio > 1) { 9048 $out_w = $MAX_W; 9049 $out_h = round($MAX_W / $ratio); 9050 } 9051 else { 9052 $out_w = round($MAX_H * $ratio); 9053 $out_h = $MAX_H; 9054 } 9055 9056 $img_out = ImageCreateTrueColor($out_w, $out_h); 9057 9058 ImageCopyResampled($img_out, $img_in, 0, 0, 0, 0, $out_w, $out_h, $w, $h); 9059 ImageDestroy($img_in); 9060 9061 $out_path = IMG_DIR . $tim . 'm.jpg'; 9062 ImageJPEG($img_out, $out_path, $jpeg_quality); 9063 ImageDestroy($img_out); 9064 9065 if ($memory_limit_increased) { 9066 ini_restore('memory_limit'); 9067 } 9068 9069 return $out_path; 9070 } 9071 } 9072 9073 /** 9074 * Returns the number of unique IPs for a given thread id 9075 * $thread_id needs to be cached in $log. 9076 */ 9077 function get_unique_ip_count($thread_id) { 9078 global $log; 9079 9080 if (!isset($log[$thread_id]) || $log[$thread_id]['archived']) { 9081 return false; 9082 } 9083 9084 $posts = $log[$thread_id]['children']; 9085 9086 if (empty($posts)) { 9087 return 1; 9088 } 9089 9090 $ip_map = array(); 9091 $ip_count = 1; 9092 $ip_map[$log[$thread_id]['host']] = true; 9093 9094 foreach ($posts as $pid => $val) { 9095 if (!isset($log[$pid])) { 9096 continue; 9097 } 9098 $post = $log[$pid]; 9099 if (!isset($ip_map[$post['host']])) { 9100 ++$ip_count; 9101 $ip_map[$post['host']] = true; 9102 } 9103 } 9104 9105 return $ip_count; 9106 } 9107 9108 function generate_del_pwd() { 9109 return '_' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 32); 9110 } 9111 9112 function get_hashed_mod_name($name) { 9113 if (!$name) { 9114 die('Internal Server Error (ghmn0)'); 9115 } 9116 9117 $admin_salt = file_get_contents('/www/keys/2014_admin.salt'); 9118 9119 if (!$admin_salt) { 9120 die('Internal Server Error (ghmn1)'); 9121 } 9122 9123 $hashed_bits = hash_hmac('sha256', $name, $admin_salt, true); 9124 9125 $hashed_name = base64_encode($hashed_bits); 9126 9127 if (!$hashed_name) { 9128 die('Internal Server Error (ghmn2)'); 9129 } 9130 9131 return $hashed_name; 9132 } 9133 9134 function create_memcached_instance() { 9135 $m = new Memcached(); 9136 //$m->setOption(Memcached::OPT_TCP_NODELAY, true); 9137 $m->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 1); 9138 $m->setOption(Memcached::OPT_SEND_TIMEOUT, 500000); // 500ms 9139 $m->setOption(Memcached::OPT_RECV_TIMEOUT, 500000); // 500ms 9140 $m->addServer(MEMCACHED_HOST, MEMCACHED_PORT); 9141 return $m; 9142 } 9143 9144 function forcearchive() { 9145 if (!has_level()) { 9146 error("Can't let you do that."); 9147 } 9148 9149 if (!ENABLE_ARCHIVE) { 9150 error('Archives are disabled on this board.'); 9151 } 9152 9153 if (!isset($_POST['id'])) { 9154 error('Bad Request.'); 9155 } 9156 9157 $tid = (int)$_POST['id']; 9158 9159 $query = 'SELECT resto, sticky, archived, no, name, sub, com, filename, ext FROM `%s` WHERE no = %d'; 9160 $res = mysql_board_call($query, BOARD_DIR, $tid); 9161 9162 if (!$res) { 9163 error('Database error.'); 9164 } 9165 9166 $thread = mysql_fetch_assoc($res); 9167 9168 if (!$thread || $thread['resto']) { 9169 error('Thread not found.'); 9170 } 9171 9172 if ($thread['archived']) { 9173 error('This thread is already archived.'); 9174 } 9175 9176 if ($thread['sticky']) { 9177 error(S_MAYNOTDELSTICKY); 9178 } 9179 9180 archive_thread($tid); 9181 9182 // Log the action 9183 $action_log_post = array( 9184 'no' => $thread['no'], 9185 'name' => $thread['name'], 9186 'sub' => $thread['sub'], 9187 'com' => $thread['com'], 9188 'filename' => $thread['filename'], 9189 'ext' => $thread['ext'] 9190 ); 9191 9192 log_mod_action(3, $action_log_post); 9193 9194 if (ENABLE_JSON_THREADS) { 9195 generate_board_archived_json(); 9196 } 9197 9198 updating_index(); 9199 } 9200 9201 // Called remotely by other tools 9202 function rebuild_threads_by_id() { 9203 header('Content-Type: text/plain'); 9204 9205 if (!isset($_POST['ids']) || !is_array($_POST['ids']) || empty($_POST['ids'])) { 9206 echo '0'; 9207 return; 9208 } 9209 9210 $live_ids = array(); 9211 9212 // Rebuild archived threads first 9213 foreach ($_POST['ids'] as $id) { 9214 $id = (int)$id; 9215 9216 if (!$id) { 9217 continue; 9218 } 9219 9220 $query = "SELECT archived FROM `" . SQLLOG . "` WHERE no = $id LIMIT 1"; 9221 $res = mysql_board_call($query); 9222 9223 if (!$res) { 9224 echo '0'; 9225 return; 9226 } 9227 9228 if (mysql_fetch_row($res)[0] === '1') { 9229 rebuild_archived_thread($id); 9230 } 9231 else { 9232 $live_ids[] = $id; 9233 } 9234 } 9235 9236 // Rebuild live threads 9237 if (!empty($live_ids)) { 9238 9239 foreach ($live_ids as $id) { 9240 updatelog($id, 1); 9241 } 9242 9243 if (STATIC_REBUILD) { 9244 return; 9245 } 9246 9247 updatelog(0, 0); // rebuild indexes 9248 } 9249 9250 echo '1'; 9251 } 9252 9253 function rebuild_archive_list($print = false) { 9254 $board = BOARD_DIR; 9255 9256 $html = ''; 9257 9258 $maxlen = 100; 9259 9260 $max_age_in_days = 3; 9261 9262 $hour_clause = $max_age_in_days * 24; 9263 9264 $thread_limit = 3000; 9265 9266 $query = <<<SQL 9267 SELECT no, sub, com 9268 FROM `$board` 9269 WHERE archived = 1 AND resto = 0 AND root >= DATE_SUB(NOW(), INTERVAL $hour_clause HOUR) 9270 ORDER BY root DESC 9271 LIMIT $thread_limit 9272 SQL; 9273 9274 $res = mysql_board_call($query); 9275 9276 $thread_count = mysql_num_rows($res); 9277 9278 head($html, 0, 0, 0, 0, true); 9279 9280 $html .= '<div class="navLinks mobile"> 9281 <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> 9282 </div>'; 9283 9284 $html .= '<hr class="desktop"> 9285 <div class="navLinks desktop"> 9286 [<a href="/' . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#bottom">' . S_BOTTOM . '</a>] 9287 </div><hr>'; 9288 9289 $html .= '<h4 class="center">Displaying ' . number_format($thread_count) . ' expired thread' 9290 . (!$thread_count || $thread_count > 1 ? 's' : '') . ' from the past ' . $max_age_in_days . ' day' 9291 . ($max_age_in_days > 1 ? 's' : '') . '</h4> 9292 <table id="arc-list" class="flashListing"><thead><tr> 9293 <td class="postblock">No.</td> 9294 <td class="postblock">Excerpt</td> 9295 <td class="postblock"></td> 9296 </tr></thead><tbody>'; 9297 9298 while ($row = mysql_fetch_assoc($res)) { 9299 if (strpos($row['sub'], 'SPOILER<>') === 0) { 9300 $row['sub'] = substr($row['sub'], 9); 9301 } 9302 9303 if (!empty($row['sub'])) { 9304 if ($row['com'] !== '') { 9305 $teaser = '<b>' . $row['sub'] . ':</b> ' . $row['com']; 9306 } 9307 else { 9308 $teaser = $row['sub']; 9309 } 9310 } 9311 else { 9312 $teaser = $row['com']; 9313 } 9314 9315 $teaser = preg_replace('/(?:<br>)+/', ' ', str_replace('"', "'", $teaser)); 9316 9317 $href_context = generate_href_context($row['sub'], $row['com']); 9318 9319 if ($href_context !== '') { 9320 $href_context = "/$href_context"; 9321 } 9322 9323 $html .= '<tr> 9324 <td>' . $row['no'] . '</td> 9325 <td class="teaser-col">' 9326 . truncate_comment($teaser, $maxlen) . 9327 '</td> 9328 <td>[<a class="quotelink" href="/' . $board . '/thread/' 9329 . $row['no'] . $href_context . '">View</a>] 9330 </td> 9331 </tr>'; 9332 } 9333 9334 $html .= '</tbody></table><hr>'; 9335 9336 $html .= '<div class="navLinks navLinksBot desktop">[<a href="/' 9337 . BOARD_DIR . '/" accesskey="a">' . S_RETURN . '</a>] [<a href="/' 9338 . BOARD_DIR . '/catalog">' . S_CATALOG . '</a>] [<a href="#top">' 9339 . S_TOP . '</a>] </div><hr class="desktop">'; 9340 9341 $html .= '<div class="navLinks mobile"><span class="mobileib button"><a href="/' 9342 . BOARD_DIR . '/" accesskey="a">' 9343 . S_RETURN . '</a></span> <span class="mobileib button"><a href="/' 9344 . BOARD_DIR . '/catalog">' 9345 . S_CATALOG . '</a></span> <span class="mobileib button"><a href="#top">' 9346 . S_TOP . '</a></span></div><hr class="mobile">'; 9347 9348 if (AD_BOTTOM_ENABLE == 1) { 9349 $bottomad = ''; 9350 9351 if (defined('AD_BOTTOM_TEXT') && AD_BOTTOM_TEXT) { 9352 $bottomad .= '<div class="bottomad center ad-cnt">' 9353 . ad_text_for(AD_BOTTOM_TEXT) . '</div>' 9354 . (defined('AD_BOTTOM_PLEA') ? AD_BOTTOM_PLEA : ''); 9355 } 9356 9357 if ($bottomad) { 9358 $html .= "$bottomad<hr>"; 9359 } 9360 } 9361 9362 $html .= '<div class="bottomCtrl desktop">'; 9363 9364 if (!defined('CSS_FORCE')) { 9365 $html .= '<span class="stylechanger">Style: 9366 <select id="styleSelector"> 9367 <option value="Yotsuba New">Yotsuba</option> 9368 <option value="Yotsuba B New">Yotsuba B</option> 9369 <option value="Futaba New">Futaba</option> 9370 <option value="Burichan New">Burichan</option> 9371 <option value="Tomorrow">Tomorrow</option> 9372 <option value="Photon">Photon</option>'; 9373 9374 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 9375 $html .= '<option value="_special">Special</option>'; 9376 } 9377 9378 $html .= '</select> 9379 </span>'; 9380 } 9381 9382 $html .= '</div>'; 9383 9384 foot($html, false, true); 9385 9386 if ($print) { 9387 echo $html; 9388 } 9389 else { 9390 print_page(INDEX_DIR . 'archive'. PHP_EXT, $html); 9391 } 9392 } 9393 9394 function rebuild_syncframe_page($print = false) { 9395 $tpl_file = YOTSUBA_DIR . 'views/syncframe.html'; 9396 9397 if (!file_exists($tpl_file)) { 9398 die('Template file not found'); 9399 } 9400 9401 $html = file_get_contents($tpl_file); 9402 9403 if ($print) { 9404 die($html); 9405 } 9406 else { 9407 print_page(BOARDS_ROOT . 'syncframe' . PHP_EXT, $html); 9408 } 9409 } 9410 9411 function rebuild_search_page($print = false) { 9412 $html = ''; 9413 9414 // board select box 9415 9416 $board_select_html = '<select class="g-search-ctrl" id="js-sf-bf"><option value="">All Boards</option>'; 9417 9418 $query = 'SELECT dir, name FROM boardlist ORDER BY dir ASC'; 9419 9420 $res = mysql_global_call($query); 9421 9422 if (!$res) { 9423 error('Database Error (rsp0)'); 9424 } 9425 9426 while ($row = mysql_fetch_assoc($res)) { 9427 $board_select_html .= '<option value="' . $row['dir'] . '">/' . $row['dir'] . '/ - ' . htmlspecialchars($row['name'], ENT_QUOTES) . '</option>'; 9428 } 9429 9430 $board_select_html .= '</select>'; 9431 9432 // header --- 9433 9434 $cssVersion = $print ? CSS_VERSION_TEST : CSS_VERSION; 9435 $defaultcss = 'yotsubanew'; 9436 $mobilecss = 'yotsubamobile.' . $cssVersion . '.css'; 9437 9438 $styles = array( 9439 'Yotsuba New' => "yotsubanew.$cssVersion.css", 9440 'Yotsuba B New' => "yotsubluenew.$cssVersion.css", 9441 'Futaba New' => "futabanew.$cssVersion.css", 9442 'Burichan New' => "burichannew.$cssVersion.css", 9443 'Photon' => "photon.$cssVersion.css", 9444 'Tomorrow' => "tomorrow.$cssVersion.css" 9445 ); 9446 9447 $dcssl = $defaultcss . '.' . $cssVersion . '.css'; 9448 9449 $css = '<link rel="stylesheet" title="switch" href="' . STATIC_SERVER . 'css/' . $dcssl . '">'; 9450 9451 foreach ($styles as $style => $stylecss) { 9452 $css .= '<link rel="alternate stylesheet" style="text/css" href="' . STATIC_SERVER . 'css/' . $stylecss . '" title="' . $style . '">'; 9453 } 9454 9455 $css .= '<link rel="stylesheet" href="' . STATIC_SERVER . 'css/' . $mobilecss . '">'; 9456 9457 $scriptjs = '<script type="text/javascript">var style_group = "nws_style";</script>'; 9458 9459 $testjs = $print ? 'test/core-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'core.min.' . JS_VERSION_CORE . '.js'; 9460 $testextra = $print ? 'test/extension-8psvqAqszI.' . JS_VERSION_TEST . '.js' : 'extension.min.' . JS_VERSION_EXT . '.js'; 9461 9462 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testjs . '"></script>'; 9463 $scriptjs .= '<script type="text/javascript" data-cfasync="false" src="' . STATIC_SERVER . 'js/' . $testextra . '"></script>'; 9464 9465 if (defined('FAVICON')) { 9466 $favicon = '<link rel="shortcut icon" href="' . FAVICON . '">'; 9467 } 9468 else { 9469 $favicon = ''; 9470 } 9471 9472 $includenav = file_get_contents_cached(NAV_TXT); 9473 9474 $html .= '<!DOCTYPE html> 9475 <html> 9476 <head> 9477 <meta charset="utf-8"> 9478 <meta name="robots" content="' . META_ROBOTS . '"> 9479 <meta name="description" content="4chan Search"> 9480 <meta name="keywords" content="4chan,search"> 9481 <meta name="viewport" content="width=device-width,initial-scale=1"> 9482 ' . $favicon . ' 9483 ' . $css . ' 9484 <title>Search 4chan</title>' . $scriptjs . 9485 '</head> 9486 <body class="is_search">' . $stylejs . $includenav . 9487 '<div class="boardBanner"> 9488 <div class="boardTitle">4chan Search</div> 9489 </div>'; 9490 9491 // --- 9492 9493 $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>'; 9494 9495 $html .= '<hr> 9496 <form name="delform" id="delform"> 9497 <div class="board">'; 9498 9499 $html .= '</div><hr>'; 9500 9501 $html .= '<div class="bottomCtrl desktop">'; 9502 9503 if (!defined('CSS_FORCE')) { 9504 $html .= '<span class="stylechanger">Style: 9505 <select id="styleSelector"> 9506 <option value="Yotsuba New">Yotsuba</option> 9507 <option value="Yotsuba B New">Yotsuba B</option> 9508 <option value="Futaba New">Futaba</option> 9509 <option value="Burichan New">Burichan</option> 9510 <option value="Tomorrow">Tomorrow</option> 9511 <option value="Photon">Photon</option>'; 9512 9513 if (defined('CSS_EVENT_NAME') && CSS_EVENT_NAME) { 9514 $html .= '<option value="_special">Special</option>'; 9515 } 9516 9517 $html .= '</select> 9518 </span>'; 9519 } 9520 9521 $html .= '</div></form>'; 9522 9523 foot($html); 9524 9525 if ($print) { 9526 echo $html; 9527 } 9528 else { 9529 print_page(BOARDS_ROOT . 'globalsearch' . PHP_EXT, $html); 9530 } 9531 } 9532 9533 /** 9534 * Enforce the maximum number of allowed threads per user, per board. 9535 * error() if limit has been reached 9536 */ 9537 function validate_user_thread_limit($ip, $password = null, $pass_id = null) { 9538 $clauses = array(); 9539 9540 $clauses[] = "host = '" . mysql_real_escape_string($ip) . "'"; 9541 9542 if ($password) { 9543 $clauses[] = "pwd = '" . mysql_real_escape_string($password) . "'"; 9544 } 9545 9546 if ($pass_id) { 9547 $clauses[] = "4pass_id = '" . mysql_real_escape_string($pass_id) . "'"; 9548 } 9549 9550 $ts = $_SERVER['REQUEST_TIME'] - ((int)MAX_USER_THREADS_PERIOD * 3600); 9551 9552 $clauses = implode(' OR ', $clauses); 9553 9554 $query = 'SELECT COUNT(*) FROM `' . SQLLOG 9555 . "` WHERE resto = 0 AND archived = 0 AND time > $ts AND ($clauses)"; 9556 9557 $res = mysql_board_call($query); 9558 9559 if (!$res) { 9560 return true; 9561 } 9562 9563 $count = (int)mysql_fetch_row($res)[0]; 9564 9565 if ($count >= (int)MAX_USER_THREADS) { 9566 $plural = MAX_USER_THREADS > 1 ? 's' : ''; 9567 error(sprintf(S_TOOMANYTHREADS, MAX_USER_THREADS, $plural)); 9568 } 9569 9570 return true; 9571 } 9572 9573 function is_poster_op($host, $hashed_pwd, $resto) { 9574 $query = 'SELECT host, pwd FROM `%s` WHERE no = %d'; 9575 $res = mysql_board_call($query, SQLLOG, $resto); 9576 9577 if (!$res) { 9578 return false; 9579 } 9580 9581 $post = mysql_fetch_assoc($res); 9582 9583 if (!$post) { 9584 return false; 9585 } 9586 9587 return $post['host'] === $host || $post['pwd'] === $hashed_pwd; 9588 } 9589 9590 function spam_filter_check_qa_bot($board, $resto, $ip, $country, $com, $captcha_resp) { 9591 if (preg_match('/Edge|Safari|WebKit|Firefox|Mozilla/', $_SERVER['HTTP_USER_AGENT']) && $captcha_resp['hostname'] === 'boards.4chan.org') { 9592 return true; 9593 } 9594 9595 // Check if IP is known 9596 $long_ip = ip2long($ip); 9597 9598 if (spam_filter_is_user_known($long_ip)) { 9599 return false; 9600 } 9601 9602 if (!preg_match('/Edge|Mobile/', $_SERVER['HTTP_USER_AGENT']) && preg_match('/WebKit/', $_SERVER['HTTP_USER_AGENT']) !== preg_match('/WebKit/', $_SERVER['HTTP_CONTENT_TYPE'])) { 9603 return true; 9604 } 9605 9606 $bot_countries = array( 9607 'AD','AE','AF','AG','AI','AL','AM','AN','AO','AR','AS','AW','AZ', 9608 'BB','BD','BF','BG','BH','BI','BJ','BM','BN','BO','BR','BS','BT','BV','BW','BY','BZ', 9609 'CC','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CX','CY','CZ', 9610 'DJ','DM','DO','DZ','EC','EE','EG','EH','ER','ET','FJ','FM','FO', 9611 'GA','GD','GE','GF','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GY', 9612 'HK','HM','HN','HT','HU','HR','ID','IL','IN','IO','IQ','IR','IS','JM','JO', 9613 'KE','KG','KH','KI','KM','KN','KR','KW','KY','KZ', 9614 'LA','LB','LC','LI','LK','LR','LS','LU','LY', 9615 'MA','MD','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MY','MZ','NA', 9616 'NE','NF','NG','NI','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PM','PN','PR','PS','PT','PW', 9617 'QA','RE','RS','RO','RU','RW','SA','SB','SC','SD','SH','SI','SJ','SK','SL','SM','SN','SO','SR','ST','SV','SY','SZ', 9618 'TC','TD','TF','TG','TJ','TM','TN','TO','TP','TR','TT','TV','TW','TZ','UG','UM','UY','UZ', 9619 'VA','VE','VC','VG','VI','VN','VU','WF','WS','YE','YT','ZA','ZM','ZR','ZW' 9620 ); 9621 9622 // Check country 9623 if (!in_array($country, $bot_countries)) { 9624 return false; 9625 } 9626 9627 if ($resto) { 9628 $query = 'SELECT time FROM %s WHERE resto = %d ORDER BY no DESC LIMIT 1'; 9629 9630 $res = mysql_board_call($query, $board, $resto); 9631 9632 if (!$res) { 9633 return false; 9634 } 9635 9636 $last_time = mysql_fetch_row($res); 9637 9638 if (!$last_time) { 9639 return false; 9640 } 9641 9642 $last_time = (int)$last_time[0]; 9643 9644 if ($_SERVER['REQUEST_TIME'] - $last_time < 14400) { 9645 return false; 9646 } 9647 } 9648 9649 return true; 9650 } 9651 9652 function log_qa_spam_filter($is_hit, $thread_id, $ip, $country, $captcha_resp) { 9653 $_bot_headers = ''; 9654 9655 foreach ($_SERVER as $_h_name => $_h_val) { 9656 if (substr($_h_name, 0, 5) == 'HTTP_') { 9657 $_bot_headers .= "$_h_name: $_h_val\n"; 9658 } 9659 } 9660 9661 $_bot_headers .= "_Captcha: " . $captcha_resp['hostname'] . "\n"; 9662 9663 $_bot_headers .= "_Country: $country\n"; 9664 9665 log_spam_filter_trigger($is_hit ? 'blocked_qa' : 'ok_qa', BOARD_DIR, $thread_id, $ip, 1, $_bot_headers); 9666 } 9667 9668 function log_spam_filter_trigger($action, $board, $thread_id, $ip, $arg_num, $meta = '') { 9669 $query = <<<SQL 9670 INSERT INTO event_log(`type`, `board`, `arg_num`, `thread_id`, `ip`, `meta`) 9671 VALUES('%s', '%s', %d, %d, '%s', '%s') 9672 SQL; 9673 9674 mysql_global_call($query, $action, $board, $arg_num, $thread_id, $ip, $meta); 9675 } 9676 9677 function preview_html() { 9678 if ($_SERVER['REQUEST_METHOD'] != 'POST') { 9679 updating_index(); 9680 return; 9681 } 9682 9683 if (!has_level('mod')) { 9684 updating_index(); 9685 return; 9686 } 9687 9688 if (!has_flag('html') && !has_flag('developer')) { 9689 updating_index(); 9690 return; 9691 } 9692 9693 header('Content-type: application/json'); 9694 9695 if (isset($_POST['com'])) { 9696 $html = $_POST['com']; 9697 } 9698 else { 9699 $data = array('status' => 'error', 'message' => 'Nothing to do.'); 9700 echo json_encode($data); 9701 return; 9702 } 9703 9704 $html = purify_html($html); 9705 9706 $data = array('status' => 'success', 'data' => $html); 9707 9708 echo json_encode($data); 9709 } 9710 9711 function purify_html($html) { 9712 static $purifier = null; 9713 9714 if ($purifier === null) { 9715 require_once 'lib/htmlpurifier/HTMLPurifier.standalone.php'; 9716 9717 $config = HTMLPurifier_Config::createDefault(); 9718 $config->set('Cache.DefinitionImpl', null); 9719 9720 $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); 9721 9722 $config->set('URI.AllowedSchemes', array('http' => true, 'https' => true)); 9723 $config->set('HTML.SafeIframe', true); 9724 $config->set('URI.SafeIframeRegexp', HTML_IFRAME_WHITELIST); 9725 $config->set('HTML.Allowed', HTML_WHITELIST); 9726 $config->set('CSS.AllowTricky', true); 9727 $config->set('CSS.Trusted', true); 9728 $config->set('Attr.AllowedFrameTargets', array('_blank')); 9729 9730 $def = $config->getHTMLDefinition(true); 9731 $def->addAttribute('iframe', 'allowfullscreen', 'Bool'); 9732 9733 $def->addElement('video', 'Block', 'Flow', 'Common', array( 9734 'controls' => 'Bool', 9735 'height' => 'Length', 9736 'width' => 'Length', 9737 'poster' => 'URI', 9738 'autoplay' => 'Bool', 9739 'loop' => 'Bool', 9740 'muted' => 'Bool', 9741 'src' => 'URI' 9742 )); 9743 9744 $css_def = $config->getDefinition('CSS'); 9745 9746 $css_def->info['color'] = new HTMLPurifier_AttrDef_CSS_Composite( 9747 array( 9748 new HTMLPurifier_AttrDef_Enum(array('transparent')), 9749 new HTMLPurifier_AttrDef_CSS_Color() 9750 ) 9751 ); 9752 9753 $purifier = new HTMLPurifier($config); 9754 9755 if (!$purifier) { 9756 error('Internal Server Error'); 9757 } 9758 } 9759 9760 return $purifier->purify($html); 9761 } 9762 9763 function count_thread_replies($board, $thread_id) { 9764 $thread_id = (int)$thread_id; 9765 9766 if ($thread_id <= 0) { 9767 return 0; 9768 } 9769 9770 $sql = "SELECT COUNT(*) as cnt FROM `%s` WHERE resto = $thread_id"; 9771 9772 $res = mysql_board_call($sql, $board); 9773 9774 if (!$res) { 9775 return 0; 9776 } 9777 9778 return (int)mysql_fetch_row($res)[0]; 9779 } 9780 9781 // TODO: remove later 9782 function check_safe_ua_sig($ua, $sig) { 9783 if (!$ua || !$sig) { 9784 return true; 9785 } 9786 9787 $thres = 3; 9788 9789 $ua_sig = "$ua.$sig"; 9790 9791 $sql = "SELECT 1 FROM event_log WHERE type = 'log_safe_ua' AND ua_sig = '%s' LIMIT $thres"; 9792 9793 $res = mysql_global_call($sql, $ua_sig); 9794 9795 if (!$res) { 9796 return true; 9797 } 9798 9799 if (mysql_num_rows($res) < $thres) { 9800 return false; 9801 } 9802 9803 return true; 9804 } 9805 9806 // April 2024 9807 function april_2024_parse_email($email) { 9808 if ($email[0] != '$') { 9809 return 0; 9810 } 9811 9812 $tag = substr(trim($email), 1); 9813 9814 $stocks = april_2024_get_stock_list(); 9815 9816 $idx = array_search($tag, $stocks); 9817 9818 if ($idx === false) { 9819 return 0; 9820 } 9821 9822 $count = april_2024_get_stock_count($tag); 9823 9824 if ($count < 10) { 9825 return 0; 9826 } 9827 9828 return 10000 + $idx; 9829 } 9830 9831 function april_2024_get_stock_list() { 9832 static $stocks = [ 9833 'PEPE', 'WOJK', 'ANIME', 'CHAD', 'CLOWN', 'LOL', 'SICP', 'AUTSM', 'BANE', 9834 'CIA', 'BOOB', 'RDDT', 'DESU', 'JANNY', 'GME', 'CHUCK', 'YTSB', 'GACHI' 9835 ]; 9836 9837 return $stocks; 9838 } 9839 9840 function april_2024_get_stock_from_s4p($since4pass) { 9841 if ($since4pass < 10000) { 9842 return false; 9843 } 9844 9845 $val = $since4pass - 10000; 9846 9847 if ($val < 0) { 9848 return false; 9849 } 9850 9851 $badges = april_2024_get_stock_list(); 9852 9853 if ($val >= 0 && $val < count($badges)) { 9854 return $badges[$val]; 9855 } 9856 else { 9857 return false; 9858 } 9859 } 9860 9861 function april_2024_get_name() { 9862 $net_worth = april_2024_get_net_worth(); 9863 9864 if ($net_worth < 500) { 9865 return 'Destitute Investor'; 9866 } 9867 else if ($net_worth < 1500) { 9868 return 'Helpless Investor'; 9869 } 9870 else if ($net_worth < 5000) { 9871 return 'Poor Investor'; 9872 } 9873 else if ($net_worth < 50000) { 9874 return 'Fledgling Investor'; 9875 } 9876 else if ($net_worth < 500000) { 9877 return 'Aspiring Investor'; 9878 } 9879 else if ($net_worth < 2000000) { 9880 return 'Rich Investor'; 9881 } 9882 else if ($net_worth < 5000000) { 9883 return 'Anonymous Magnate'; 9884 } 9885 else { 9886 return 'Anonymous Mogul'; 9887 } 9888 } 9889 9890 function april_2024_get_post_cls($since4pass) { 9891 $stock = april_2024_get_stock_from_s4p($since4pass); 9892 9893 if ($stock) { 9894 return " p-xa24-$stock"; 9895 } 9896 else { 9897 return ''; 9898 } 9899 } 9900 9901 function april_2024_get_name_badge($since4pass) { 9902 $stock = april_2024_get_stock_from_s4p($since4pass); 9903 9904 if ($stock) { 9905 return " <span data-tip=\"$stock\" class=\"n-xa24 n-xa24-$stock\"></span>"; 9906 } 9907 else { 9908 return ''; 9909 } 9910 } 9911 9912 function april_2024_get_stock_count($stock) { 9913 $userpwd = UserPwd::getSession(); 9914 9915 if (!$userpwd || $userpwd->isNew()) { 9916 return 0; 9917 } 9918 9919 $user_id = $userpwd->getPwd(); 9920 9921 $sql =<<<SQL 9922 SELECT SUM(amount) as amount FROM april_stock_users 9923 WHERE user_id = '%s' AND stock = '%s' 9924 SQL; 9925 9926 $res = mysql_global_call($sql, $user_id, $stock); 9927 9928 if (!$res) { 9929 return 0; 9930 } 9931 9932 $val = (int)mysql_fetch_row($res)[0]; 9933 9934 if ($val < 0) { 9935 $val = 0; 9936 } 9937 9938 return $val; 9939 } 9940 9941 function april_2024_get_net_worth() { 9942 $userpwd = UserPwd::getSession(); 9943 9944 if (!$userpwd || $userpwd->isNew()) { 9945 return 0; 9946 } 9947 9948 $user_id = $userpwd->getPwd(); 9949 9950 $sql =<<<SQL 9951 SELECT stock, SUM(amount) as amount FROM april_stock_users 9952 WHERE user_id = '%s' GROUP BY stock HAVING amount > 0 9953 SQL; 9954 9955 $res = mysql_global_call($sql, $user_id); 9956 9957 if (!$res) { 9958 return 0; 9959 } 9960 9961 $stocks = []; 9962 9963 while ($row = mysql_fetch_row($res)) { 9964 $stocks[$row[0]] = (int)$row[1]; 9965 } 9966 9967 $sql =<<<SQL 9968 SELECT stock, price FROM april_stock_prices 9969 ORDER BY id DESC LIMIT 30 9970 SQL; 9971 9972 $res = mysql_global_call($sql); 9973 9974 if (!$res) { 9975 return 0; 9976 } 9977 9978 $prices = []; 9979 9980 while ($row = mysql_fetch_row($res)) { 9981 if (isset($prices[$row[0]])) { 9982 continue; 9983 } 9984 $prices[$row[0]] = (int)$row[1]; 9985 } 9986 9987 $net_worth = $stocks['_']; 9988 9989 foreach ($stocks as $stock => $count) { 9990 if (isset($prices[$stock])) { 9991 $net_worth += ($prices[$stock] * $count); 9992 } 9993 } 9994 9995 return $net_worth; 9996 } 9997 9998 // --- 9999 10000 function clear_no_captcha_token() { 10001 setcookie('_ct', null, -3600, '/', '.' . L::d(BOARD_DIR)); 10002 } 10003 10004 function generate_no_captcha_token() { 10005 if (BOARD_DIR === 'pol' || BOARD_DIR === 'b' || BOARD_DIR === 'r9k' || BOARD_DIR === 'bant') { 10006 return false; 10007 } 10008 10009 $long_ip = ip2long($_SERVER['REMOTE_ADDR']); 10010 10011 if (!$long_ip) { 10012 return false; 10013 } 10014 10015 if (!spam_filter_is_user_known($long_ip, BOARD_DIR, null, 15)) { 10016 return false; 10017 } 10018 10019 $salt = file_get_contents_cached(SALTFILE); 10020 10021 if (!$salt) { 10022 return false; 10023 } 10024 10025 $time = $_SERVER['REQUEST_TIME']; 10026 10027 $msg = $_SERVER['REMOTE_ADDR'] . '.' . $time; 10028 10029 $msg = hash_hmac('sha1', $msg, $salt); 10030 10031 if (!$msg) { 10032 return false; 10033 } 10034 10035 $msg = substr($msg, 0, 20) . '.' . $time; 10036 10037 setcookie('_ct', $msg, $time + 300, '/', '.' . L::d(BOARD_DIR)); // 5 minutes 10038 } 10039 10040 function verify_no_captcha_token($token) { 10041 list($hash, $ts) = explode('.', $token); 10042 10043 $ts = (int)$ts; 10044 10045 if (!$hash || !$ts) { 10046 return false; 10047 } 10048 10049 if ($ts < $_SERVER['REQUEST_TIME'] - 300) { // 5 minutes 10050 return false; 10051 } 10052 10053 $salt = file_get_contents_cached(SALTFILE); 10054 10055 if (!$salt) { 10056 return false; 10057 } 10058 10059 $msg = $_SERVER['REMOTE_ADDR'] . '.' . $ts; 10060 10061 if (substr(hash_hmac('sha1', $msg, $salt), 0, 20) === $hash) { 10062 return true; 10063 } 10064 10065 return false; 10066 } 10067 10068 function get_random_real_name() { 10069 $first_name_nid = mt_rand(1, 1000); 10070 10071 if (mt_rand(0, 999) < 10) { 10072 $type = 2; 10073 } 10074 else { 10075 $type = 1; 10076 } 10077 10078 $query = "SELECT data FROM april_names WHERE nid = $first_name_nid AND type = $type"; 10079 $res = mysql_global_call($query); 10080 $first_name = mysql_fetch_row($res)[0]; 10081 10082 if (!$first_name) { 10083 $first_name = 'Alberto'; 10084 } 10085 10086 $last_name_nid = mt_rand(1, 1000); 10087 10088 $query = "SELECT data FROM april_names WHERE nid = $last_name_nid AND type = 3"; 10089 $res = mysql_global_call($query); 10090 $last_name = mysql_fetch_row($res)[0]; 10091 10092 if (!$last_name) { 10093 $last_name = 'Barbosa'; 10094 } 10095 10096 return "$first_name $last_name"; 10097 } 10098 10099 10100 function log_mod_action($action_type, $post, $vip_capcode = false) { 10101 $mask_shift = 128; 10102 $action_id = $mask_shift + $action_type; 10103 10104 $query =<<<SQL 10105 INSERT INTO actions_log (oldmask, newmask, postno, board, name, sub, com, filename, admin) 10106 VALUES (0, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s') 10107 SQL; 10108 10109 mysql_global_call($query, 10110 $action_id, 10111 $post['no'], 10112 BOARD_DIR, 10113 $post['name'], 10114 $post['sub'], 10115 $post['com'], 10116 $post['filename'] . $post['ext'], 10117 $vip_capcode === false ? $_COOKIE['4chan_auser'] : '' 10118 ); 10119 10120 return true; 10121 } 10122 10123 function get_request_xff() { 10124 $xff = $_SERVER['HTTP_X_FORWARDED_FOR']; 10125 10126 if (!$xff) { 10127 return false; 10128 } 10129 10130 if ($xff === $_SERVER['REMOTE_ADDR']) { 10131 return false; 10132 } 10133 10134 // For Cloudflare 10135 if (strpos($xff, ',') !== false) { 10136 $xff = explode(',', $xff); 10137 return end($xff); 10138 } 10139 else { 10140 return $xff; 10141 } 10142 } 10143 10144 function validate_otp() { 10145 if (!isset($_POST['otp']) || $_POST['otp'] == '') { 10146 error("Incorrect or expired OTP."); 10147 } 10148 10149 $otp = $_POST['otp']; 10150 10151 $query = "SELECT auth_secret FROM mod_users WHERE username = '%s' LIMIT 1"; 10152 10153 $res = mysql_global_call($query, $_COOKIE['4chan_auser']); 10154 10155 if (!$res) { 10156 error("Database error."); 10157 } 10158 10159 $user = mysql_fetch_assoc($res); 10160 10161 if (!$user || !$user['auth_secret']) { 10162 error("Incorrect or expired OTP."); 10163 } 10164 10165 require_once 'lib/GoogleAuthenticator.php'; 10166 10167 $ga = new PHPGangsta_GoogleAuthenticator(); 10168 10169 $dec_secret = auth_decrypt($user['auth_secret']); 10170 10171 if ($dec_secret === false) { 10172 error('Internal Server Error.'); 10173 } 10174 10175 if (!$ga->verifyCode($dec_secret, $otp, 1)) { 10176 error("Incorrect or expired OTP."); 10177 } 10178 } 10179 10180 function validate_csrf() { 10181 if ($_SERVER['REQUEST_METHOD'] != 'POST') { 10182 error('Bad Request.'); 10183 } 10184 10185 if (!isset($_COOKIE['_tkn']) || !isset($_POST['_tkn']) 10186 || $_COOKIE['_tkn'] == '' || $_POST['_tkn'] == '' 10187 || $_COOKIE['_tkn'] !== $_POST['_tkn']) { 10188 10189 if (!is_local()) { 10190 error('Bad Request.'); 10191 } 10192 } 10193 } 10194 10195 function validate_referer($strict = false) { 10196 if (!$strict && (!isset($_SERVER['HTTP_REFERER']) || $_SERVER['HTTP_REFERER'] == '')) { 10197 return; 10198 } 10199 10200 if (!preg_match('/^https?:\/\/([_a-z0-9]+)\.(4chan|4channel)\.org(\/|$)/', $_SERVER['HTTP_REFERER'])) { 10201 error('Bad Request.'); 10202 } 10203 } 10204 10205 function dev_make_remote_thumbnail() { 10206 if (!is_local()) { 10207 die('403'); 10208 } 10209 10210 $infile = $_FILES['file']['tmp_name']; 10211 $file_ext = $_POST['file_ext']; 10212 $src_width = $_POST['src_width']; 10213 $src_height = $_POST['src_height']; 10214 $th_width = $_POST['th_width']; 10215 $th_height = $_POST['th_height']; 10216 10217 if (!$infile || !$file_ext || !$src_width || !$src_height || !$th_width || !$th_height) { 10218 return; 10219 } 10220 10221 $jpeg_quality = 65; 10222 10223 switch ($file_ext) { 10224 case 'gif': 10225 $img_in = ImageCreateFromGIF($infile); 10226 break; 10227 case 'jpg': 10228 $img_in = ImageCreateFromJPEG($infile); 10229 break; 10230 case 'png': 10231 $img_in = ImageCreateFromPNG($infile); 10232 break; 10233 default : 10234 return; 10235 } 10236 10237 if (!$img_in) { 10238 return; 10239 } 10240 10241 $img_out = ImageCreateTrueColor($th_width, $th_height); 10242 10243 if (!$img_out) { 10244 return; 10245 } 10246 10247 ImageCopyResampled($img_out, $img_in, 0, 0, 0, 0, $th_width, $th_height, $src_width, $src_height); 10248 10249 ImageDestroy($img_in); 10250 10251 ImageJPEG($img_out, NULL, $jpeg_quality); 10252 10253 ImageDestroy($img_out); 10254 } 10255 10256 /*-----------Main-------------*/ 10257 switch( $mode ) { 10258 case 'make_remote_thumbnail': 10259 dev_make_remote_thumbnail(); 10260 die(); 10261 case 'purgejsontails': 10262 if (has_flag('developer')) { 10263 echo purge_json_tails() ? 'OK' : 'ERROR'; 10264 } 10265 die(); 10266 case 'listarchive': 10267 if (has_flag('developer')) { 10268 rebuild_archive_list(true); 10269 } 10270 die(); 10271 case 'search': 10272 if (has_flag('developer')) { 10273 rebuild_search_page(true); 10274 } 10275 die(); 10276 case 'rebuildsearchpage': 10277 if (has_flag('developer') || has_level('manager')) { 10278 rebuild_search_page(); 10279 echo 'done'; 10280 } 10281 die(); 10282 case 'rebuildsyncframepage': 10283 if (has_flag('developer') || has_level('manager')) { 10284 rebuild_syncframe_page(isset($_GET['print'])); 10285 echo 'done'; 10286 } 10287 die(); 10288 case 'rebuildarchivedthread': 10289 if (has_flag('developer') || has_level('manager')) { 10290 rebuild_archived_thread((int)$_GET['id']); 10291 echo 'done'; 10292 } 10293 die(); 10294 10295 case 'regist': 10296 case 'post': 10297 require_request_method( "POST" ); 10298 validate_referer(); 10299 new_post( $name, $email, $sub, $com, '', $pwd, $upfile, $upfile_name, $resto, $age, $filetag ); 10300 break; 10301 case 'report': 10302 report(); 10303 break; 10304 10305 case 'preview_html': 10306 preview_html(); 10307 break; 10308 10309 case 'rebuild': 10310 require_request_method( "GET" ); 10311 rebuild(); 10312 break; 10313 case 'rebuildall': 10314 rebuild( 1 ); 10315 break; 10316 10317 case 'rebuildadmin': 10318 rebuild_deletions( array($no => '1') ); 10319 echo '<span style="display: none;">Rebuilt OK!</span>'; // shut rpc up 10320 break; 10321 10322 case 'rebuildcatalog': 10323 if (ENABLE_CATALOG) { 10324 rebuild_catalog(); 10325 } 10326 break; 10327 10328 case 'rebuildboardsjson': 10329 if (has_flag('developer')) { 10330 rebuild_boards_json(); 10331 } 10332 break; 10333 10334 case 'rebuildthumb': 10335 rebuildallthumb(isset($_GET['archiveonly']) || isset($_ENV['archiveonly'])); 10336 break; 10337 10338 case 'cataloginfo': 10339 get_catalog_info(); 10340 break; 10341 10342 case 'admindel': case 'admindelete': 10343 user_delete( $no, $pwd ); 10344 echo "<meta http-equiv=\"refresh\" content=\"0;URL=admin.php\">"; 10345 break; 10346 case 'updatelog': 10347 updatelog_remote( $no, $noidx ); 10348 break; 10349 case 'nothing': 10350 break; 10351 case 'arcdel': 10352 require_request_method( "POST" ); 10353 validate_referer(); 10354 arcdel($no, true, $res); 10355 break; 10356 case 'usrdel': case 'delete': 10357 require_request_method( "POST" ); 10358 validate_referer(); 10359 user_delete( $no, $pwd, true, $res ); 10360 break; 10361 case 'rebuild_threads_by_id': 10362 require_request_method( "POST" ); 10363 if (is_local()) { 10364 rebuild_threads_by_id(); 10365 } 10366 else { 10367 updating_index(); 10368 } 10369 break; 10370 case 'forcearchive': 10371 require_request_method( "POST" ); 10372 validate_referer(); //validate_csrf(); 10373 forcearchive(); 10374 break; 10375 case 'copythreads': 10376 require_request_method( "GET" ); 10377 do_copy_threads(); 10378 break; 10379 case 'movethread': 10380 validate_csrf(); 10381 do_move_thread(); 10382 break; 10383 case 'latest': 10384 if (has_level('janitor')) { 10385 get_last_post_no(); 10386 } 10387 die(); 10388 case 'rake_post': 10389 //april_rake_commit(); 10390 die('0\nNo'); 10391 break; 10392 default: 10393 require_request_method( "GET" ); 10394 if( JANITOR_BOARD == 1 && !has_level( 'janitor' ) ) { 10395 die( '' ); 10396 } 10397 if( $res ) { 10398 resredir( $res ); 10399 } else { 10400 updating_index(); 10401 } 10402 }