The version of vichan running on lainchan.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

869 line
29KB

  1. <?php
  2. /*
  3. * Copyright (c) 2010-2014 Tinyboard Development Group
  4. */
  5. require 'inc/functions.php';
  6. require 'inc/anti-bot.php';
  7. // Fix for magic quotes
  8. if (get_magic_quotes_gpc()) {
  9. function strip_array($var) {
  10. return is_array($var) ? array_map('strip_array', $var) : stripslashes($var);
  11. }
  12. $_GET = strip_array($_GET);
  13. $_POST = strip_array($_POST);
  14. }
  15. if (isset($_POST['delete'])) {
  16. // Delete
  17. if (!isset($_POST['board'], $_POST['password']))
  18. error($config['error']['bot']);
  19. $password = &$_POST['password'];
  20. if ($password == '')
  21. error($config['error']['invalidpassword']);
  22. $delete = array();
  23. foreach ($_POST as $post => $value) {
  24. if (preg_match('/^delete_(\d+)$/', $post, $m)) {
  25. $delete[] = (int)$m[1];
  26. }
  27. }
  28. checkDNSBL();
  29. // Check if board exists
  30. if (!openBoard($_POST['board']))
  31. error($config['error']['noboard']);
  32. // Check if banned
  33. checkBan($board['uri']);
  34. if (empty($delete))
  35. error($config['error']['nodelete']);
  36. foreach ($delete as &$id) {
  37. $query = prepare(sprintf("SELECT `thread`, `time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  38. $query->bindValue(':id', $id, PDO::PARAM_INT);
  39. $query->execute() or error(db_error($query));
  40. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  41. $thread = false;
  42. if ($config['user_moderation'] && $post['thread']) {
  43. $thread_query = prepare(sprintf("SELECT `time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  44. $thread_query->bindValue(':id', $post['thread'], PDO::PARAM_INT);
  45. $thread_query->execute() or error(db_error($query));
  46. $thread = $thread_query->fetch(PDO::FETCH_ASSOC);
  47. }
  48. if ($password != '' && $post['password'] != $password && (!$thread || $thread['password'] != $password))
  49. error($config['error']['invalidpassword']);
  50. if ($post['time'] > time() - $config['delete_time'] && (!$thread || $thread['password'] != $password)) {
  51. error(sprintf($config['error']['delete_too_soon'], until($post['time'] + $config['delete_time'])));
  52. }
  53. if (isset($_POST['file'])) {
  54. // Delete just the file
  55. deleteFile($id);
  56. } else {
  57. // Delete entire post
  58. deletePost($id);
  59. }
  60. _syslog(LOG_INFO, 'Deleted post: ' .
  61. '/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '')
  62. );
  63. }
  64. }
  65. buildIndex();
  66. rebuildThemes('post-delete', $board['uri']);
  67. $is_mod = isset($_POST['mod']) && $_POST['mod'];
  68. $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  69. if (!isset($_POST['json_response'])) {
  70. header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
  71. } else {
  72. header('Content-Type: text/json');
  73. echo json_encode(array('success' => true));
  74. }
  75. } elseif (isset($_POST['report'])) {
  76. if (!isset($_POST['board'], $_POST['password'], $_POST['reason']))
  77. error($config['error']['bot']);
  78. $report = array();
  79. foreach ($_POST as $post => $value) {
  80. if (preg_match('/^delete_(\d+)$/', $post, $m)) {
  81. $report[] = (int)$m[1];
  82. }
  83. }
  84. checkDNSBL();
  85. // Check if board exists
  86. if (!openBoard($_POST['board']))
  87. error($config['error']['noboard']);
  88. // Check if banned
  89. checkBan($board['uri']);
  90. if (empty($report))
  91. error($config['error']['noreport']);
  92. if (count($report) > $config['report_limit'])
  93. error($config['error']['toomanyreports']);
  94. $reason = escape_markup_modifiers($_POST['reason']);
  95. markup($reason);
  96. foreach ($report as &$id) {
  97. $query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  98. $query->bindValue(':id', $id, PDO::PARAM_INT);
  99. $query->execute() or error(db_error($query));
  100. $thread = $query->fetchColumn();
  101. if ($config['syslog'])
  102. _syslog(LOG_INFO, 'Reported post: ' .
  103. '/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $thread ? $thread : $id) . ($thread ? '#' . $id : '') .
  104. ' for "' . $reason . '"'
  105. );
  106. $query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason)");
  107. $query->bindValue(':time', time(), PDO::PARAM_INT);
  108. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
  109. $query->bindValue(':board', $board['uri'], PDO::PARAM_INT);
  110. $query->bindValue(':post', $id, PDO::PARAM_INT);
  111. $query->bindValue(':reason', $reason, PDO::PARAM_STR);
  112. $query->execute() or error(db_error($query));
  113. }
  114. $is_mod = isset($_POST['mod']) && $_POST['mod'];
  115. $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  116. if (!isset($_POST['json_response'])) {
  117. header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
  118. } else {
  119. header('Content-Type: text/json');
  120. echo json_encode(array('success' => true));
  121. }
  122. } elseif (isset($_POST['post'])) {
  123. if (!isset($_POST['body'], $_POST['board']))
  124. error($config['error']['bot']);
  125. $post = array('board' => $_POST['board']);
  126. // Check if board exists
  127. if (!openBoard($post['board']))
  128. error($config['error']['noboard']);
  129. if (!isset($_POST['name']))
  130. $_POST['name'] = $config['anonymous'];
  131. if (!isset($_POST['email']))
  132. $_POST['email'] = '';
  133. if (!isset($_POST['subject']))
  134. $_POST['subject'] = '';
  135. if (!isset($_POST['password']))
  136. $_POST['password'] = '';
  137. if (isset($_POST['thread'])) {
  138. $post['op'] = false;
  139. $post['thread'] = round($_POST['thread']);
  140. } elseif ($config['quick_reply'] && isset($_POST['quick-reply'])) {
  141. $post['op'] = false;
  142. $post['thread'] = round($_POST['quick-reply']);
  143. } else
  144. $post['op'] = true;
  145. if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) ||
  146. (!$post['op'] && $_POST['post'] == $config['button_reply'])))
  147. error($config['error']['bot']);
  148. // Check the referrer
  149. if ($config['referer_match'] !== false &&
  150. (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))))
  151. error($config['error']['referer']);
  152. checkDNSBL();
  153. // Check if banned
  154. checkBan($board['uri']);
  155. // Check for CAPTCHA right after opening the board so the "return" link is in there
  156. if ($config['recaptcha']) {
  157. if (!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field']))
  158. error($config['error']['bot']);
  159. // Check what reCAPTCHA has to say...
  160. $resp = recaptcha_check_answer($config['recaptcha_private'],
  161. $_SERVER['REMOTE_ADDR'],
  162. $_POST['recaptcha_challenge_field'],
  163. $_POST['recaptcha_response_field']);
  164. if (!$resp->is_valid) {
  165. error($config['error']['captcha']);
  166. }
  167. }
  168. if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) {
  169. require 'inc/mod/auth.php';
  170. if (!$mod) {
  171. // Liar. You're not a mod.
  172. error($config['error']['notamod']);
  173. }
  174. $post['sticky'] = $post['op'] && isset($_POST['sticky']);
  175. $post['locked'] = $post['op'] && isset($_POST['lock']);
  176. $post['raw'] = isset($_POST['raw']);
  177. if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri']))
  178. error($config['error']['noaccess']);
  179. if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri']))
  180. error($config['error']['noaccess']);
  181. if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri']))
  182. error($config['error']['noaccess']);
  183. }
  184. if (!$post['mod']) {
  185. $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) && !($config['quick_reply'] && isset($_POST['quick-reply'])) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null)));
  186. if ($post['antispam_hash'] === true)
  187. error($config['error']['spam']);
  188. }
  189. if ($config['robot_enable'] && $config['robot_mute']) {
  190. checkMute();
  191. }
  192. //Check if thread exists
  193. if (!$post['op']) {
  194. $query = prepare(sprintf("SELECT `sticky`,`locked`,`sage` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
  195. $query->bindValue(':id', $post['thread'], PDO::PARAM_INT);
  196. $query->execute() or error(db_error());
  197. if (!$thread = $query->fetch(PDO::FETCH_ASSOC)) {
  198. // Non-existant
  199. error($config['error']['nonexistant']);
  200. }
  201. }
  202. // Check for an embed field
  203. if ($config['enable_embedding'] && isset($_POST['embed']) && !empty($_POST['embed'])) {
  204. // yep; validate it
  205. $value = $_POST['embed'];
  206. foreach ($config['embedding'] as &$embed) {
  207. if (preg_match($embed[0], $value)) {
  208. // Valid link
  209. $post['embed'] = $value;
  210. // This is bad, lol.
  211. $post['no_longer_require_an_image_for_op'] = true;
  212. break;
  213. }
  214. }
  215. if (!isset($post['embed'])) {
  216. error($config['error']['invalid_embed']);
  217. }
  218. }
  219. if (!hasPermission($config['mod']['bypass_field_disable'], $board['uri'])) {
  220. if ($config['field_disable_name'])
  221. $_POST['name'] = $config['anonymous']; // "forced anonymous"
  222. if ($config['field_disable_email'])
  223. $_POST['email'] = '';
  224. if ($config['field_disable_password'])
  225. $_POST['password'] = '';
  226. if ($config['field_disable_subject'] || (!$post['op'] && $config['field_disable_reply_subject']))
  227. $_POST['subject'] = '';
  228. }
  229. if ($config['allow_upload_by_url'] && isset($_POST['file_url']) && !empty($_POST['file_url'])) {
  230. $post['file_url'] = $_POST['file_url'];
  231. if (!preg_match('@^https?://@', $post['file_url']))
  232. error($config['error']['invalidimg']);
  233. if (mb_strpos($post['file_url'], '?') !== false)
  234. $url_without_params = mb_substr($post['file_url'], 0, mb_strpos($post['file_url'], '?'));
  235. else
  236. $url_without_params = $post['file_url'];
  237. $post['extension'] = strtolower(mb_substr($url_without_params, mb_strrpos($url_without_params, '.') + 1));
  238. if (!in_array($post['extension'], $config['allowed_ext']) && !in_array($post['extension'], $config['allowed_ext_files']))
  239. error($config['error']['unknownext']);
  240. $post['file_tmp'] = tempnam($config['tmp'], 'url');
  241. function unlink_tmp_file($file) {
  242. @unlink($file);
  243. fatal_error_handler();
  244. }
  245. register_shutdown_function('unlink_tmp_file', $post['file_tmp']);
  246. $fp = fopen($post['file_tmp'], 'w');
  247. $curl = curl_init();
  248. curl_setopt($curl, CURLOPT_URL, $post['file_url']);
  249. curl_setopt($curl, CURLOPT_FAILONERROR, true);
  250. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
  251. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
  252. curl_setopt($curl, CURLOPT_TIMEOUT, $config['upload_by_url_timeout']);
  253. curl_setopt($curl, CURLOPT_USERAGENT, 'Tinyboard');
  254. curl_setopt($curl, CURLOPT_BINARYTRANSFER, true);
  255. curl_setopt($curl, CURLOPT_FILE, $fp);
  256. curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  257. if (curl_exec($curl) === false)
  258. error($config['error']['nomove']);
  259. curl_close($curl);
  260. fclose($fp);
  261. $_FILES['file'] = array(
  262. 'name' => basename($url_without_params),
  263. 'tmp_name' => $post['file_tmp'],
  264. 'error' => 0,
  265. 'size' => filesize($post['file_tmp'])
  266. );
  267. }
  268. // Check for a file
  269. if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) {
  270. if (!isset($_FILES['file']['tmp_name']) || $_FILES['file']['tmp_name'] == '' && $config['force_image_op'])
  271. error($config['error']['noimage']);
  272. }
  273. $post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
  274. $post['subject'] = $_POST['subject'];
  275. $post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
  276. $post['body'] = $_POST['body'];
  277. $post['password'] = $_POST['password'];
  278. $post['has_file'] = !isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || (isset($_FILES['file']) && $_FILES['file']['tmp_name'] != ''));
  279. if ($post['has_file'])
  280. $post['filename'] = urldecode(get_magic_quotes_gpc() ? stripslashes($_FILES['file']['name']) : $_FILES['file']['name']);
  281. if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) {
  282. $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']);
  283. if ($stripped_whitespace == '') {
  284. error($config['error']['tooshort_body']);
  285. }
  286. }
  287. if (!$post['op']) {
  288. // Check if thread is locked
  289. // but allow mods to post
  290. if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri']))
  291. error($config['error']['locked']);
  292. $numposts = numPosts($post['thread']);
  293. if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies'])
  294. error($config['error']['reply_hard_limit']);
  295. if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images'])
  296. error($config['error']['image_hard_limit']);
  297. }
  298. if ($post['has_file']) {
  299. $size = $_FILES['file']['size'];
  300. if ($size > $config['max_filesize'])
  301. error(sprintf3($config['error']['filesize'], array(
  302. 'sz' => number_format($size),
  303. 'filesz' => number_format($size),
  304. 'maxsz' => number_format($config['max_filesize'])
  305. )));
  306. }
  307. $post['capcode'] = false;
  308. if ($mod && preg_match('/^((.+) )?## (.+)$/', $post['name'], $matches)) {
  309. $name = $matches[2] != '' ? $matches[2] : $config['anonymous'];
  310. $cap = $matches[3];
  311. if (isset($config['mod']['capcode'][$mod['type']])) {
  312. if ( $config['mod']['capcode'][$mod['type']] === true ||
  313. (is_array($config['mod']['capcode'][$mod['type']]) &&
  314. in_array($cap, $config['mod']['capcode'][$mod['type']])
  315. )) {
  316. $post['capcode'] = utf8tohtml($cap);
  317. $post['name'] = $name;
  318. }
  319. }
  320. }
  321. $trip = generate_tripcode($post['name']);
  322. $post['name'] = $trip[0];
  323. $post['trip'] = isset($trip[1]) ? $trip[1] : '';
  324. $noko = false;
  325. if (strtolower($post['email']) == 'noko') {
  326. $noko = true;
  327. $post['email'] = '';
  328. } elseif (strtolower($post['email']) == 'nonoko'){
  329. $noko = false;
  330. $post['email'] = '';
  331. } else $noko = $config['always_noko'];
  332. if ($post['has_file']) {
  333. $post['extension'] = strtolower(mb_substr($post['filename'], mb_strrpos($post['filename'], '.') + 1));
  334. if (isset($config['filename_func']))
  335. $post['file_id'] = $config['filename_func']($post);
  336. else
  337. $post['file_id'] = time() . substr(microtime(), 2, 3);
  338. $post['file'] = $board['dir'] . $config['dir']['img'] . $post['file_id'] . '.' . $post['extension'];
  339. $post['thumb'] = $board['dir'] . $config['dir']['thumb'] . $post['file_id'] . '.' . ($config['thumb_ext'] ? $config['thumb_ext'] : $post['extension']);
  340. }
  341. if ($config['strip_combining_chars']) {
  342. $post['name'] = strip_combining_chars($post['name']);
  343. $post['email'] = strip_combining_chars($post['email']);
  344. $post['subject'] = strip_combining_chars($post['subject']);
  345. $post['body'] = strip_combining_chars($post['body']);
  346. }
  347. // Check string lengths
  348. if (mb_strlen($post['name']) > 35)
  349. error(sprintf($config['error']['toolong'], 'name'));
  350. if (mb_strlen($post['email']) > 40)
  351. error(sprintf($config['error']['toolong'], 'email'));
  352. if (mb_strlen($post['subject']) > 100)
  353. error(sprintf($config['error']['toolong'], 'subject'));
  354. if (!$mod && mb_strlen($post['body']) > $config['max_body'])
  355. error($config['error']['toolong_body']);
  356. if (mb_strlen($post['password']) > 20)
  357. error(sprintf($config['error']['toolong'], 'password'));
  358. wordfilters($post['body']);
  359. $post['body'] = escape_markup_modifiers($post['body']);
  360. if ($mod && isset($post['raw']) && $post['raw']) {
  361. $post['body'] .= "\n<tinyboard raw html>1</tinyboard>";
  362. }
  363. if ($config['country_flags']) {
  364. require 'inc/lib/geoip/geoip.inc';
  365. $gi=geoip\geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD);
  366. function ipv4to6($ip) {
  367. if (strpos($ip, ':') !== false) {
  368. if (strpos($ip, '.') > 0)
  369. $ip = substr($ip, strrpos($ip, ':')+1);
  370. else return $ip; //native ipv6
  371. }
  372. $iparr = array_pad(explode('.', $ip), 4, 0);
  373. $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16);
  374. $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16);
  375. return '::ffff:'.$part7.':'.$part8;
  376. }
  377. if ($country_code = geoip\geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) {
  378. if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2')))
  379. $post['body'] .= "\n<tinyboard flag>".strtolower($country_code)."</tinyboard>".
  380. "\n<tinyboard flag alt>".geoip\geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))."</tinyboard>";
  381. }
  382. }
  383. if ($config['user_flag'] && isset($_POST['user_flag']))
  384. if (!empty($_POST['user_flag']) ){
  385. $user_flag = $_POST['user_flag'];
  386. if (!isset($config['user_flags'][$user_flag]))
  387. error('Invalid flag selection!');
  388. $flag_alt = isset($user_flag_alt) ? $user_flag_alt : $config['user_flags'][$user_flag];
  389. $post['body'] .= "\n<tinyboard flag>" . strtolower($user_flag) . "</tinyboard>" .
  390. "\n<tinyboard flag alt>" . $flag_alt . "</tinyboard>";
  391. }
  392. if (mysql_version() >= 50503) {
  393. $post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset
  394. } else {
  395. // MySQL's `utf8` charset only supports up to 3-byte symbols
  396. // Remove anything >= 0x010000
  397. $chars = preg_split('//u', $post['body'], -1, PREG_SPLIT_NO_EMPTY);
  398. $post['body_nomarkup'] = '';
  399. foreach ($chars as $char) {
  400. $o = 0;
  401. $ord = ordutf8($char, $o);
  402. if ($ord >= 0x010000)
  403. continue;
  404. $post['body_nomarkup'] .= $char;
  405. }
  406. }
  407. $post['tracked_cites'] = markup($post['body'], true);
  408. if ($post['has_file']) {
  409. if (!in_array($post['extension'], $config['allowed_ext']) && !in_array($post['extension'], $config['allowed_ext_files']))
  410. error($config['error']['unknownext']);
  411. $is_an_image = !in_array($post['extension'], $config['allowed_ext_files']);
  412. // Truncate filename if it is too long
  413. $post['filename'] = mb_substr($post['filename'], 0, $config['max_filename_len']);
  414. $upload = $_FILES['file']['tmp_name'];
  415. if (!is_readable($upload))
  416. error($config['error']['nomove']);
  417. $post['filehash'] = md5_file($upload);
  418. $post['filesize'] = filesize($upload);
  419. }
  420. if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) {
  421. require_once 'inc/filters.php';
  422. do_filters($post);
  423. }
  424. if ($post['has_file']) {
  425. if ($is_an_image && $config['ie_mime_type_detection'] !== false) {
  426. // Check IE MIME type detection XSS exploit
  427. $buffer = file_get_contents($upload, null, null, null, 255);
  428. if (preg_match($config['ie_mime_type_detection'], $buffer)) {
  429. undoImage($post);
  430. error($config['error']['mime_exploit']);
  431. }
  432. require_once 'inc/image.php';
  433. // find dimensions of an image using GD
  434. if (!$size = @getimagesize($upload)) {
  435. error($config['error']['invalidimg']);
  436. }
  437. if ($size[0] > $config['max_width'] || $size[1] > $config['max_height']) {
  438. error($config['error']['maxsize']);
  439. }
  440. if ($config['convert_auto_orient'] && ($post['extension'] == 'jpg' || $post['extension'] == 'jpeg')) {
  441. // The following code corrects the image orientation.
  442. // Currently only works with the 'convert' option selected but it could easily be expanded to work with the rest if you can be bothered.
  443. if (!($config['redraw_image'] || (($config['strip_exif'] && !$config['use_exiftool']) && ($post['extension'] == 'jpg' || $post['extension'] == 'jpeg')))) {
  444. if (in_array($config['thumb_method'], array('convert', 'convert+gifsicle', 'gm', 'gm+gifsicle'))) {
  445. $exif = @exif_read_data($upload);
  446. $gm = in_array($config['thumb_method'], array('gm', 'gm+gifsicle'));
  447. if (isset($exif['Orientation']) && $exif['Orientation'] != 1) {
  448. if ($config['convert_manual_orient']) {
  449. $error = shell_exec_error(($gm ? 'gm ' : '') . 'convert ' .
  450. escapeshellarg($upload) . ' ' .
  451. ImageConvert::jpeg_exif_orientation(false, $exif) . ' ' .
  452. ($config['strip_exif'] ? '+profile "*"' :
  453. ($config['use_exiftool'] ? '' : '+profile "*"')
  454. ) . ' ' .
  455. escapeshellarg($upload));
  456. if ($config['use_exiftool'] && !$config['strip_exif']) {
  457. if ($exiftool_error = shell_exec_error(
  458. 'exiftool -overwrite_original -q -q -orientation=1 -n ' .
  459. escapeshellarg($upload)))
  460. error('exiftool failed!', null, $exiftool_error);
  461. } else {
  462. // TODO: Find another way to remove the Orientation tag from the EXIF profile
  463. // without needing `exiftool`.
  464. }
  465. } else {
  466. $error = shell_exec_error(($gm ? 'gm ' : '') . 'convert ' .
  467. escapeshellarg($upload) . ' -auto-orient ' . escapeshellarg($upload));
  468. }
  469. if ($error)
  470. error('Could not auto-orient image!', null, $error);
  471. $size = @getimagesize($upload);
  472. if ($config['strip_exif'])
  473. $post['exif_stripped'] = true;
  474. }
  475. }
  476. }
  477. }
  478. // create image object
  479. $image = new Image($upload, $post['extension'], $size);
  480. if ($image->size->width > $config['max_width'] || $image->size->height > $config['max_height']) {
  481. $image->delete();
  482. error($config['error']['maxsize']);
  483. }
  484. $post['width'] = $image->size->width;
  485. $post['height'] = $image->size->height;
  486. if ($config['spoiler_images'] && isset($_POST['spoiler'])) {
  487. $post['thumb'] = 'spoiler';
  488. $size = @getimagesize($config['spoiler_image']);
  489. $post['thumbwidth'] = $size[0];
  490. $post['thumbheight'] = $size[1];
  491. } elseif ($config['minimum_copy_resize'] &&
  492. $image->size->width <= $config['thumb_width'] &&
  493. $image->size->height <= $config['thumb_height'] &&
  494. $post['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $post['extension'])) {
  495. // Copy, because there's nothing to resize
  496. copy($upload, $post['thumb']);
  497. $post['thumbwidth'] = $image->size->width;
  498. $post['thumbheight'] = $image->size->height;
  499. } else {
  500. $thumb = $image->resize(
  501. $config['thumb_ext'] ? $config['thumb_ext'] : $post['extension'],
  502. $post['op'] ? $config['thumb_op_width'] : $config['thumb_width'],
  503. $post['op'] ? $config['thumb_op_height'] : $config['thumb_height']
  504. );
  505. $thumb->to($post['thumb']);
  506. $post['thumbwidth'] = $thumb->width;
  507. $post['thumbheight'] = $thumb->height;
  508. $thumb->_destroy();
  509. }
  510. if ($config['redraw_image'] || (!@$post['exif_stripped'] && $config['strip_exif'] && ($post['extension'] == 'jpg' || $post['extension'] == 'jpeg'))) {
  511. if (!$config['redraw_image'] && $config['use_exiftool']) {
  512. if($error = shell_exec_error('exiftool -overwrite_original -ignoreMinorErrors -q -q -all= ' .
  513. escapeshellarg($upload)))
  514. error(_('Could not strip EXIF metadata!'), null, $error);
  515. } else {
  516. $image->to($post['file']);
  517. $dont_copy_file = true;
  518. }
  519. }
  520. $image->destroy();
  521. } else {
  522. // not an image
  523. //copy($config['file_thumb'], $post['thumb']);
  524. $post['thumb'] = 'file';
  525. $size = @getimagesize(sprintf($config['file_thumb'],
  526. isset($config['file_icons'][$post['extension']]) ?
  527. $config['file_icons'][$post['extension']] : $config['file_icons']['default']));
  528. $post['thumbwidth'] = $size[0];
  529. $post['thumbheight'] = $size[1];
  530. }
  531. if (!isset($dont_copy_file) || !$dont_copy_file) {
  532. if (isset($post['file_tmp'])) {
  533. if (!@rename($upload, $post['file']))
  534. error($config['error']['nomove']);
  535. chmod($post['file'], 0644);
  536. } elseif (!@move_uploaded_file($upload, $post['file']))
  537. error($config['error']['nomove']);
  538. }
  539. }
  540. if ($post['has_file']) {
  541. if ($config['image_reject_repost']) {
  542. if ($p = getPostByHash($post['filehash'])) {
  543. undoImage($post);
  544. error(sprintf($config['error']['fileexists'],
  545. ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) .
  546. ($board['dir'] . $config['dir']['res'] .
  547. ($p['thread'] ?
  548. $p['thread'] . '.html#' . $p['id']
  549. :
  550. $p['id'] . '.html'
  551. ))
  552. ));
  553. }
  554. } else if (!$post['op'] && $config['image_reject_repost_in_thread']) {
  555. if ($p = getPostByHashInThread($post['filehash'], $post['thread'])) {
  556. undoImage($post);
  557. error(sprintf($config['error']['fileexistsinthread'],
  558. ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) .
  559. ($board['dir'] . $config['dir']['res'] .
  560. ($p['thread'] ?
  561. $p['thread'] . '.html#' . $p['id']
  562. :
  563. $p['id'] . '.html'
  564. ))
  565. ));
  566. }
  567. }
  568. }
  569. if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) {
  570. undoImage($post);
  571. if ($config['robot_mute']) {
  572. error(sprintf($config['error']['muted'], mute()));
  573. } else {
  574. error($config['error']['unoriginal']);
  575. }
  576. }
  577. // Remove board directories before inserting them into the database.
  578. if ($post['has_file']) {
  579. $post['file_path'] = $post['file'];
  580. $post['thumb_path'] = $post['thumb'];
  581. $post['file'] = mb_substr($post['file'], mb_strlen($board['dir'] . $config['dir']['img']));
  582. if ($is_an_image && $post['thumb'] != 'spoiler')
  583. $post['thumb'] = mb_substr($post['thumb'], mb_strlen($board['dir'] . $config['dir']['thumb']));
  584. }
  585. $post = (object)$post;
  586. if ($error = event('post', $post)) {
  587. undoImage((array)$post);
  588. error($error);
  589. }
  590. $post = (array)$post;
  591. $post['id'] = $id = post($post);
  592. insertFloodPost($post);
  593. if (isset($post['antispam_hash'])) {
  594. incrementSpamHash($post['antispam_hash']);
  595. }
  596. if (isset($post['tracked_cites']) && !empty($post['tracked_cites'])) {
  597. $insert_rows = array();
  598. foreach ($post['tracked_cites'] as $cite) {
  599. $insert_rows[] = '(' .
  600. $pdo->quote($board['uri']) . ', ' . (int)$id . ', ' .
  601. $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')';
  602. }
  603. query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error());
  604. }
  605. if (!$post['op'] && strtolower($post['email']) != 'sage' && !$thread['sage'] && ($config['reply_limit'] == 0 || $numposts['replies']+1 < $config['reply_limit'])) {
  606. bumpThread($post['thread']);
  607. }
  608. buildThread($post['op'] ? $id : $post['thread']);
  609. if ($config['try_smarter'] && $post['op'])
  610. $build_pages = range(1, $config['max_pages']);
  611. if ($post['op'])
  612. clean();
  613. event('post-after', $post);
  614. buildIndex();
  615. if (isset($_SERVER['HTTP_REFERER'])) {
  616. // Tell Javascript that we posted successfully
  617. if (isset($_COOKIE[$config['cookies']['js']]))
  618. $js = json_decode($_COOKIE[$config['cookies']['js']]);
  619. else
  620. $js = (object) array();
  621. // Tell it to delete the cached post for referer
  622. $js->{$_SERVER['HTTP_REFERER']} = true;
  623. // Encode and set cookie
  624. setcookie($config['cookies']['js'], json_encode($js), 0, $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, false, false);
  625. }
  626. $root = $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  627. if ($noko) {
  628. $redirect = $root . $board['dir'] . $config['dir']['res'] .
  629. sprintf($config['file_page'], $post['op'] ? $id:$post['thread']) . (!$post['op'] ? '#' . $id : '');
  630. if (!$post['op'] && isset($_SERVER['HTTP_REFERER'])) {
  631. $regex = array(
  632. 'board' => str_replace('%s', '(\w{1,8})', preg_quote($config['board_path'], '/')),
  633. 'page' => str_replace('%d', '(\d+)', preg_quote($config['file_page'], '/')),
  634. 'page50' => str_replace('%d', '(\d+)', preg_quote($config['file_page50'], '/')),
  635. 'res' => preg_quote($config['dir']['res'], '/'),
  636. );
  637. if (preg_match('/\/' . $regex['board'] . $regex['res'] . $regex['page50'] . '([?&].*)?$/', $_SERVER['HTTP_REFERER'])) {
  638. $redirect = $root . $board['dir'] . $config['dir']['res'] .
  639. sprintf($config['file_page50'], $post['op'] ? $id:$post['thread']) . (!$post['op'] ? '#' . $id : '');
  640. }
  641. }
  642. } else {
  643. $redirect = $root . $board['dir'] . $config['file_index'];
  644. }
  645. if ($config['syslog'])
  646. _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] .
  647. sprintf($config['file_page'], $post['op'] ? $id : $post['thread']) . (!$post['op'] ? '#' . $id : ''));
  648. if (!$post['mod']) header('X-Associated-Content: "' . $redirect . '"');
  649. if ($post['op'])
  650. rebuildThemes('post-thread', $board['uri']);
  651. else
  652. rebuildThemes('post', $board['uri']);
  653. if (!isset($_POST['json_response'])) {
  654. header('Location: ' . $redirect, true, $config['redirect_http']);
  655. } else {
  656. header('Content-Type: text/json; charset=utf-8');
  657. echo json_encode(array(
  658. 'redirect' => $redirect,
  659. 'noko' => $noko,
  660. 'id' => $id
  661. ));
  662. }
  663. } elseif (isset($_POST['appeal'])) {
  664. if (!isset($_POST['ban_id']))
  665. error($config['error']['bot']);
  666. $ban_id = (int)$_POST['ban_id'];
  667. $bans = Bans::find($_SERVER['REMOTE_ADDR']);
  668. foreach ($bans as $_ban) {
  669. if ($_ban['id'] == $ban_id) {
  670. $ban = $_ban;
  671. break;
  672. }
  673. }
  674. if (!isset($ban)) {
  675. error(_("That ban doesn't exist or is not for you."));
  676. }
  677. if ($ban['expires'] && $ban['expires'] - $ban['created'] <= $config['ban_appeals_min_length']) {
  678. error(_("You cannot appeal a ban of this length."));
  679. }
  680. $query = query("SELECT `denied` FROM ``ban_appeals`` WHERE `ban_id` = $ban_id") or error(db_error());
  681. $ban_appeals = $query->fetchAll(PDO::FETCH_COLUMN);
  682. if (count($ban_appeals) >= $config['ban_appeals_max']) {
  683. error(_("You cannot appeal this ban again."));
  684. }
  685. foreach ($ban_appeals as $is_denied) {
  686. if (!$is_denied)
  687. error(_("There is already a pending appeal for this ban."));
  688. }
  689. $query = prepare("INSERT INTO ``ban_appeals`` VALUES (NULL, :ban_id, :time, :message, 0)");
  690. $query->bindValue(':ban_id', $ban_id, PDO::PARAM_INT);
  691. $query->bindValue(':time', time(), PDO::PARAM_INT);
  692. $query->bindValue(':message', $_POST['appeal']);
  693. $query->execute() or error(db_error($query));
  694. displayBan($ban);
  695. } else {
  696. if (!file_exists($config['has_installed'])) {
  697. header('Location: install.php', true, $config['redirect_http']);
  698. } else {
  699. // They opened post.php in their browser manually.
  700. error($config['error']['nopost']);
  701. }
  702. }