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