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.

post.php 43KB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
13 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
13 years ago
10 years ago
12 years ago
13 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
8 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
9 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291
  1. <?php
  2. /*
  3. * Copyright (c) 2010-2014 Tinyboard Development Group
  4. */
  5. require_once 'inc/functions.php';
  6. require_once 'inc/anti-bot.php';
  7. require_once 'inc/bans.php';
  8. // Fix for magic quotes
  9. if (get_magic_quotes_gpc()) {
  10. function strip_array($var) {
  11. return is_array($var) ? array_map('strip_array', $var) : stripslashes($var);
  12. }
  13. $_GET = strip_array($_GET);
  14. $_POST = strip_array($_POST);
  15. }
  16. if ((!isset($_POST['mod']) || !$_POST['mod'])
  17. && ($config['board_locked']===true
  18. || (is_array($config['board_locked']) && in_array(strtolower($_POST['board']), $config['board_locked'])))){
  19. error("Board is locked");
  20. }
  21. $dropped_post = false;
  22. // Is it a post coming from NNTP? Let's extract it and pretend it's a normal post.
  23. if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) {
  24. if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) {
  25. error("NNTPChan: Forbidden. $_SERVER[REMOTE_ADDR] is not a trusted peer");
  26. }
  27. $_POST = array();
  28. $_POST['json_response'] = true;
  29. $headers = json_encode($_GET);
  30. if (!isset ($_GET['Message-Id'])) {
  31. if (!isset ($_GET['Message-ID'])) {
  32. error("NNTPChan: No message ID");
  33. }
  34. else $msgid = $_GET['Message-ID'];
  35. }
  36. else $msgid = $_GET['Message-Id'];
  37. $groups = preg_split("/,\s*/", $_GET['Newsgroups']);
  38. if (count($groups) != 1) {
  39. error("NNTPChan: Messages can go to only one newsgroup");
  40. }
  41. $group = $groups[0];
  42. if (!isset($config['nntpchan']['dispatch'][$group])) {
  43. error("NNTPChan: We don't synchronize $group");
  44. }
  45. $xboard = $config['nntpchan']['dispatch'][$group];
  46. $ref = null;
  47. if (isset ($_GET['References'])) {
  48. $refs = preg_split("/,\s*/", $_GET['References']);
  49. if (count($refs) > 1) {
  50. error("NNTPChan: We don't support multiple references");
  51. }
  52. $ref = $refs[0];
  53. $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id` = :ref");
  54. $query->bindValue(':ref', $ref);
  55. $query->execute() or error(db_error($query));
  56. $ary = $query->fetchAll(PDO::FETCH_ASSOC);
  57. if (count($ary) == 0) {
  58. error("NNTPChan: We don't have $ref that $msgid references");
  59. }
  60. $p_id = $ary[0]['id'];
  61. $p_board = $ary[0]['board'];
  62. if ($p_board != $xboard) {
  63. error("NNTPChan: Cross board references not allowed. Tried to reference $p_board on $xboard");
  64. }
  65. $_POST['thread'] = $p_id;
  66. }
  67. $date = isset($_GET['Date']) ? strtotime($_GET['Date']) : time();
  68. list($ct) = explode('; ', $_GET['Content-Type']);
  69. $query = prepare("SELECT COUNT(*) AS `c` FROM ``nntp_references`` WHERE `message_id` = :msgid");
  70. $query->bindValue(":msgid", $msgid);
  71. $query->execute() or error(db_error($query));
  72. $a = $query->fetch(PDO::FETCH_ASSOC);
  73. if ($a['c'] > 0) {
  74. error("NNTPChan: We already have this post. Post discarded.");
  75. }
  76. if ($ct == 'text/plain') {
  77. $content = file_get_contents("php://input");
  78. }
  79. elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') {
  80. _syslog(LOG_INFO, "MM: Files: ".print_r($GLOBALS, true)); // Debug
  81. $content = '';
  82. $newfiles = array();
  83. foreach ($_FILES['attachment']['error'] as $id => $error) {
  84. if ($_FILES['attachment']['type'][$id] == 'text/plain') {
  85. $content .= file_get_contents($_FILES['attachment']['tmp_name'][$id]);
  86. }
  87. elseif ($_FILES['attachment']['type'][$id] == 'message/rfc822') { // Signed message, ignore for now
  88. }
  89. else { // A real attachment :^)
  90. $file = array();
  91. $file['name'] = $_FILES['attachment']['name'][$id];
  92. $file['type'] = $_FILES['attachment']['type'][$id];
  93. $file['size'] = $_FILES['attachment']['size'][$id];
  94. $file['tmp_name'] = $_FILES['attachment']['tmp_name'][$id];
  95. $file['error'] = $_FILES['attachment']['error'][$id];
  96. $newfiles["file$id"] = $file;
  97. }
  98. }
  99. $_FILES = $newfiles;
  100. }
  101. else {
  102. error("NNTPChan: Wrong mime type: $ct");
  103. }
  104. $_POST['subject'] = isset($_GET['Subject']) ? ($_GET['Subject'] == 'None' ? '' : $_GET['Subject']) : '';
  105. $_POST['board'] = $xboard;
  106. if (isset ($_GET['From'])) {
  107. list($name, $mail) = explode(" <", $_GET['From'], 2);
  108. $mail = preg_replace('/>\s+$/', '', $mail);
  109. $_POST['name'] = $name;
  110. //$_POST['email'] = $mail;
  111. $_POST['email'] = '';
  112. }
  113. if (isset ($_GET['X_Sage'])) {
  114. $_POST['email'] = 'sage';
  115. }
  116. $content = preg_replace_callback('/>>([0-9a-fA-F]{6,})/', function($id) use ($xboard) {
  117. $id = $id[1];
  118. $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id_digest` LIKE :rule");
  119. $idx = $id . "%";
  120. $query->bindValue(':rule', $idx);
  121. $query->execute() or error(db_error($query));
  122. $ary = $query->fetchAll(PDO::FETCH_ASSOC);
  123. if (count($ary) == 0) {
  124. return ">>>>$id";
  125. }
  126. else {
  127. $ret = array();
  128. foreach ($ary as $v) {
  129. if ($v['board'] != $xboard) {
  130. $ret[] = ">>>/".$v['board']."/".$v['id'];
  131. }
  132. else {
  133. $ret[] = ">>".$v['id'];
  134. }
  135. }
  136. return implode($ret, ", ");
  137. }
  138. }, $content);
  139. $_POST['body'] = $content;
  140. $dropped_post = array(
  141. 'date' => $date,
  142. 'board' => $xboard,
  143. 'msgid' => $msgid,
  144. 'headers' => $headers,
  145. 'from_nntp' => true,
  146. );
  147. }
  148. elseif (isset($_GET['Newsgroups'])) {
  149. error("NNTPChan: NNTPChan support is disabled");
  150. }
  151. if (isset($_POST['delete'])) {
  152. // Delete
  153. if (!isset($_POST['board'], $_POST['password']))
  154. error($config['error']['bot']);
  155. $password = &$_POST['password'];
  156. if ($password == '')
  157. error($config['error']['invalidpassword']);
  158. $delete = array();
  159. foreach ($_POST as $post => $value) {
  160. if (preg_match('/^delete_(\d+)$/', $post, $m)) {
  161. $delete[] = (int)$m[1];
  162. }
  163. }
  164. checkDNSBL();
  165. // Check if board exists
  166. if (!openBoard($_POST['board']))
  167. error($config['error']['noboard']);
  168. // Check if banned
  169. checkBan($board['uri']);
  170. // Check if deletion enabled
  171. if (!$config['allow_delete'])
  172. error(_('Post deletion is not allowed!'));
  173. if (empty($delete))
  174. error($config['error']['nodelete']);
  175. foreach ($delete as &$id) {
  176. $query = prepare(sprintf("SELECT `thread`, `time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  177. $query->bindValue(':id', $id, PDO::PARAM_INT);
  178. $query->execute() or error(db_error($query));
  179. if ($post = $query->fetch(PDO::FETCH_ASSOC)) {
  180. $thread = false;
  181. if ($config['user_moderation'] && $post['thread']) {
  182. $thread_query = prepare(sprintf("SELECT `time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  183. $thread_query->bindValue(':id', $post['thread'], PDO::PARAM_INT);
  184. $thread_query->execute() or error(db_error($query));
  185. $thread = $thread_query->fetch(PDO::FETCH_ASSOC);
  186. }
  187. if ($password != '' && $post['password'] != $password && (!$thread || $thread['password'] != $password))
  188. error($config['error']['invalidpassword']);
  189. if ($post['time'] > time() - $config['delete_time'] && (!$thread || $thread['password'] != $password)) {
  190. error(sprintf($config['error']['delete_too_soon'], until($post['time'] + $config['delete_time'])));
  191. }
  192. if (isset($_POST['file'])) {
  193. // Delete just the file
  194. deleteFile($id);
  195. modLog("User deleted file from his own post #$id");
  196. } else {
  197. // Delete entire post
  198. deletePost($id);
  199. modLog("User deleted his own post #$id");
  200. }
  201. _syslog(LOG_INFO, 'Deleted post: ' .
  202. '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '')
  203. );
  204. }
  205. }
  206. buildIndex();
  207. $is_mod = isset($_POST['mod']) && $_POST['mod'];
  208. $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  209. if (!isset($_POST['json_response'])) {
  210. header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
  211. } else {
  212. header('Content-Type: text/json');
  213. echo json_encode(array('success' => true));
  214. }
  215. // We are already done, let's continue our heavy-lifting work in the background (if we run off FastCGI)
  216. if (function_exists('fastcgi_finish_request'))
  217. @fastcgi_finish_request();
  218. rebuildThemes('post-delete', $board['uri']);
  219. } elseif (isset($_POST['report'])) {
  220. if (!isset($_POST['board'], $_POST['reason']))
  221. error($config['error']['bot']);
  222. $report = array();
  223. foreach ($_POST as $post => $value) {
  224. if (preg_match('/^delete_(\d+)$/', $post, $m)) {
  225. $report[] = (int)$m[1];
  226. }
  227. }
  228. checkDNSBL();
  229. // Check if board exists
  230. if (!openBoard($_POST['board']))
  231. error($config['error']['noboard']);
  232. // Check if banned
  233. checkBan($board['uri']);
  234. if (empty($report))
  235. error($config['error']['noreport']);
  236. if (count($report) > $config['report_limit'])
  237. error($config['error']['toomanyreports']);
  238. if ($config['report_captcha'] && !isset($_POST['captcha_text'], $_POST['captcha_cookie'])) {
  239. error($config['error']['bot']);
  240. }
  241. if ($config['report_captcha']) {
  242. $resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([
  243. 'mode' => 'check',
  244. 'text' => $_POST['captcha_text'],
  245. 'extra' => $config['captcha']['extra'],
  246. 'cookie' => $_POST['captcha_cookie']
  247. ]));
  248. if ($resp !== '1') {
  249. error($config['error']['captcha']);
  250. }
  251. }
  252. $reason = escape_markup_modifiers($_POST['reason']);
  253. markup($reason);
  254. foreach ($report as &$id) {
  255. $query = prepare(sprintf("SELECT `id`,`thread` , `body_nomarkup` FROM ``posts_%s`` WHERE `id` = :id", $board['uri']));
  256. $query->bindValue(':id', $id, PDO::PARAM_INT);
  257. $query->execute() or error(db_error($query));
  258. $thread = $query->fetch(PDO::FETCH_ASSOC);
  259. if ($config['syslog'])
  260. _syslog(LOG_INFO, 'Reported post: ' .
  261. '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($thread['thread'] ? '#' . $id : '') .
  262. ' for "' . $reason . '"'
  263. );
  264. $query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason)");
  265. $query->bindValue(':time', time(), PDO::PARAM_INT);
  266. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
  267. $query->bindValue(':board', $board['uri'], PDO::PARAM_INT);
  268. $query->bindValue(':post', $id, PDO::PARAM_INT);
  269. $query->bindValue(':reason', $reason, PDO::PARAM_STR);
  270. $query->execute() or error(db_error($query));
  271. if ($config['slack'])
  272. {
  273. function slack($message, $room = "reports", $icon = ":no_entry_sign:")
  274. {
  275. $room = ($room) ? $room : "reports";
  276. $data = "payload=" . json_encode(array(
  277. "channel" => "#{$room}",
  278. "text" => urlencode($message),
  279. "icon_emoji" => $icon
  280. ));
  281. // You can get your webhook endpoint from your Slack settings
  282. // For some reason using the configuration key doesn't work
  283. //$ch = curl_init($config['slack_incoming_webhook_endpoint']);
  284. $ch = curl_init("https://hooks.slack.com/services/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");
  285. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  286. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
  287. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  288. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  289. $result = curl_exec($ch);
  290. curl_close($ch);
  291. return $result;
  292. }
  293. $postcontent = mb_substr($thread['body_nomarkup'], 0, 120) . '... _*(POST TRIMMED)*_';
  294. $slackmessage = '<' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n ' . $reason . '\n ' . $postcontent . '\n';
  295. $slackresult = slack($slackmessage, $config['slack_channel']);
  296. }
  297. }
  298. $is_mod = isset($_POST['mod']) && $_POST['mod'];
  299. $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  300. if (!isset($_POST['json_response'])) {
  301. $index = $root . $board['dir'] . $config['file_index'];
  302. echo Element('page.html', array('config' => $config, 'body' => '<div style="text-align:center"><a href="javascript:window.close()">[ ' . _('Close window') ." ]</a> <a href='$index'>[ " . _('Return') . ' ]</a></div>', 'title' => _('Report submitted!')));
  303. } else {
  304. header('Content-Type: text/json');
  305. echo json_encode(array('success' => true));
  306. }
  307. } elseif (isset($_POST['post']) || $dropped_post) {
  308. if (!isset($_POST['body'], $_POST['board']) && !$dropped_post)
  309. error($config['error']['bot']);
  310. $post = array('board' => $_POST['board'], 'files' => array());
  311. // Check if board exists
  312. if (!openBoard($post['board']))
  313. error($config['error']['noboard']);
  314. if (!isset($_POST['name']))
  315. $_POST['name'] = $config['anonymous'];
  316. if (!isset($_POST['email']))
  317. $_POST['email'] = '';
  318. if (!isset($_POST['subject']))
  319. $_POST['subject'] = '';
  320. if (!isset($_POST['password']))
  321. $_POST['password'] = '';
  322. if (isset($_POST['thread'])) {
  323. $post['op'] = false;
  324. $post['thread'] = round($_POST['thread']);
  325. } else
  326. $post['op'] = true;
  327. if (!$dropped_post) {
  328. // Check for CAPTCHA right after opening the board so the "return" link is in there
  329. if ($config['recaptcha']) {
  330. if (!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field']))
  331. error($config['error']['bot']);
  332. // Check what reCAPTCHA has to say...
  333. $resp = recaptcha_check_answer($config['recaptcha_private'],
  334. $_SERVER['REMOTE_ADDR'],
  335. $_POST['recaptcha_challenge_field'],
  336. $_POST['recaptcha_response_field']);
  337. if (!$resp->is_valid) {
  338. error($config['error']['captcha']);
  339. }
  340. }
  341. if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) ||
  342. (!$post['op'] && $_POST['post'] == $config['button_reply'])))
  343. error($config['error']['bot']);
  344. // Check the referrer
  345. if ($config['referer_match'] !== false &&
  346. (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))))
  347. error($config['error']['referer']);
  348. checkDNSBL();
  349. // Check if banned
  350. checkBan($board['uri']);
  351. if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) {
  352. check_login(false);
  353. if (!$mod) {
  354. // Liar. You're not a mod.
  355. error($config['error']['notamod']);
  356. }
  357. $post['sticky'] = $post['op'] && isset($_POST['sticky']);
  358. $post['locked'] = $post['op'] && isset($_POST['lock']);
  359. $post['raw'] = isset($_POST['raw']);
  360. if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri']))
  361. error($config['error']['noaccess']);
  362. if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri']))
  363. error($config['error']['noaccess']);
  364. if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri']))
  365. error($config['error']['noaccess']);
  366. }
  367. if (!$post['mod']) {
  368. $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null)));
  369. if ($post['antispam_hash'] === true)
  370. error($config['error']['spam']);
  371. }
  372. if ($config['robot_enable'] && $config['robot_mute']) {
  373. checkMute();
  374. }
  375. }
  376. else {
  377. $mod = $post['mod'] = false;
  378. }
  379. //Check if thread exists
  380. if (!$post['op']) {
  381. $query = prepare(sprintf("SELECT `sticky`,`locked`,`cycle`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
  382. $query->bindValue(':id', $post['thread'], PDO::PARAM_INT);
  383. $query->execute() or error(db_error());
  384. if (!$thread = $query->fetch(PDO::FETCH_ASSOC)) {
  385. // Non-existant
  386. error($config['error']['nonexistant']);
  387. }
  388. }
  389. else {
  390. $thread = false;
  391. }
  392. // Check for an embed field
  393. if ($config['enable_embedding'] && isset($_POST['embed']) && !empty($_POST['embed'])) {
  394. // yep; validate it
  395. $value = $_POST['embed'];
  396. foreach ($config['embedding'] as &$embed) {
  397. if (preg_match($embed[0], $value)) {
  398. // Valid link
  399. $post['embed'] = $value;
  400. // This is bad, lol.
  401. $post['no_longer_require_an_image_for_op'] = true;
  402. break;
  403. }
  404. }
  405. if (!isset($post['embed'])) {
  406. error($config['error']['invalid_embed']);
  407. }
  408. }
  409. if (!hasPermission($config['mod']['bypass_field_disable'], $board['uri'])) {
  410. if ($config['field_disable_name'])
  411. $_POST['name'] = $config['anonymous']; // "forced anonymous"
  412. if ($config['field_disable_email'])
  413. $_POST['email'] = '';
  414. if ($config['field_disable_password'])
  415. $_POST['password'] = '';
  416. if ($config['field_disable_subject'] || (!$post['op'] && $config['field_disable_reply_subject']))
  417. $_POST['subject'] = '';
  418. }
  419. if ($config['allow_upload_by_url'] && isset($_POST['file_url']) && !empty($_POST['file_url'])) {
  420. $post['file_url'] = $_POST['file_url'];
  421. if (!preg_match('@^https?://@', $post['file_url']))
  422. error($config['error']['invalidimg']);
  423. if (mb_strpos($post['file_url'], '?') !== false)
  424. $url_without_params = mb_substr($post['file_url'], 0, mb_strpos($post['file_url'], '?'));
  425. else
  426. $url_without_params = $post['file_url'];
  427. $post['extension'] = strtolower(mb_substr($url_without_params, mb_strrpos($url_without_params, '.') + 1));
  428. if ($post['op'] && $config['allowed_ext_op']) {
  429. if (!in_array($post['extension'], $config['allowed_ext_op']))
  430. error($config['error']['unknownext']);
  431. }
  432. else if (!in_array($post['extension'], $config['allowed_ext']) && !in_array($post['extension'], $config['allowed_ext_files']))
  433. error($config['error']['unknownext']);
  434. $post['file_tmp'] = tempnam($config['tmp'], 'url');
  435. function unlink_tmp_file($file) {
  436. @unlink($file);
  437. fatal_error_handler();
  438. }
  439. register_shutdown_function('unlink_tmp_file', $post['file_tmp']);
  440. $fp = fopen($post['file_tmp'], 'w');
  441. $curl = curl_init();
  442. curl_setopt($curl, CURLOPT_URL, $post['file_url']);
  443. curl_setopt($curl, CURLOPT_FAILONERROR, true);
  444. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
  445. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
  446. curl_setopt($curl, CURLOPT_TIMEOUT, $config['upload_by_url_timeout']);
  447. curl_setopt($curl, CURLOPT_USERAGENT, 'Tinyboard');
  448. curl_setopt($curl, CURLOPT_BINARYTRANSFER, true);
  449. curl_setopt($curl, CURLOPT_FILE, $fp);
  450. curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  451. if (curl_exec($curl) === false)
  452. error($config['error']['nomove'] . '<br/>Curl says: ' . curl_error($curl));
  453. curl_close($curl);
  454. fclose($fp);
  455. $_FILES['file'] = array(
  456. 'name' => basename($url_without_params),
  457. 'tmp_name' => $post['file_tmp'],
  458. 'file_tmp' => true,
  459. 'error' => 0,
  460. 'size' => filesize($post['file_tmp'])
  461. );
  462. }
  463. $post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
  464. $post['subject'] = $_POST['subject'];
  465. $post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
  466. $post['body'] = $_POST['body'];
  467. $post['password'] = $_POST['password'];
  468. $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0));
  469. if (!$dropped_post) {
  470. if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) {
  471. $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']);
  472. if ($stripped_whitespace == '') {
  473. error($config['error']['tooshort_body']);
  474. }
  475. }
  476. if (!$post['op']) {
  477. // Check if thread is locked
  478. // but allow mods to post
  479. if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri']))
  480. error($config['error']['locked']);
  481. $numposts = numPosts($post['thread']);
  482. if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies'])
  483. error($config['error']['reply_hard_limit']);
  484. if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images'])
  485. error($config['error']['image_hard_limit']);
  486. }
  487. }
  488. else {
  489. if (!$post['op']) {
  490. $numposts = numPosts($post['thread']);
  491. }
  492. }
  493. if ($post['has_file']) {
  494. // Determine size sanity
  495. $size = 0;
  496. if ($config['multiimage_method'] == 'split') {
  497. foreach ($_FILES as $key => $file) {
  498. $size += $file['size'];
  499. }
  500. } elseif ($config['multiimage_method'] == 'each') {
  501. foreach ($_FILES as $key => $file) {
  502. if ($file['size'] > $size) {
  503. $size = $file['size'];
  504. }
  505. }
  506. } else {
  507. error(_('Unrecognized file size determination method.'));
  508. }
  509. if ($size > $config['max_filesize'])
  510. error(sprintf3($config['error']['filesize'], array(
  511. 'sz' => number_format($size),
  512. 'filesz' => number_format($size),
  513. 'maxsz' => number_format($config['max_filesize'])
  514. )));
  515. $post['filesize'] = $size;
  516. }
  517. $post['capcode'] = false;
  518. if ($mod && preg_match('/^((.+) )?## (.+)$/', $post['name'], $matches)) {
  519. $name = $matches[2] != '' ? $matches[2] : $config['anonymous'];
  520. $cap = $matches[3];
  521. if (isset($config['mod']['capcode'][$mod['type']])) {
  522. if ( $config['mod']['capcode'][$mod['type']] === true ||
  523. (is_array($config['mod']['capcode'][$mod['type']]) &&
  524. in_array($cap, $config['mod']['capcode'][$mod['type']])
  525. )) {
  526. $post['capcode'] = utf8tohtml($cap);
  527. $post['name'] = $name;
  528. }
  529. }
  530. }
  531. $trip = generate_tripcode($post['name']);
  532. $post['name'] = $trip[0];
  533. $post['trip'] = isset($trip[1]) ? $trip[1] : ''; // XX: Dropped posts and tripcodes
  534. $noko = false;
  535. if (strtolower($post['email']) == 'noko') {
  536. $noko = true;
  537. $post['email'] = '';
  538. } elseif (strtolower($post['email']) == 'nonoko'){
  539. $noko = false;
  540. $post['email'] = '';
  541. } else $noko = $config['always_noko'];
  542. if ($post['has_file']) {
  543. $i = 0;
  544. foreach ($_FILES as $key => $file) {
  545. if ($file['size'] && $file['tmp_name']) {
  546. $file['filename'] = urldecode($file['name']);
  547. $file['extension'] = strtolower(mb_substr($file['filename'], mb_strrpos($file['filename'], '.') + 1));
  548. if (isset($config['filename_func']))
  549. $file['file_id'] = $config['filename_func']($file);
  550. else
  551. $file['file_id'] = time() . substr(microtime(), 2, 3);
  552. if (sizeof($_FILES) > 1)
  553. $file['file_id'] .= "-$i";
  554. $file['file'] = $board['dir'] . $config['dir']['img'] . $file['file_id'] . '.' . $file['extension'];
  555. $file['thumb'] = $board['dir'] . $config['dir']['thumb'] . $file['file_id'] . '.' . ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']);
  556. $post['files'][] = $file;
  557. $i++;
  558. }
  559. }
  560. }
  561. if (empty($post['files'])) $post['has_file'] = false;
  562. if (!$dropped_post) {
  563. // Check for a file
  564. if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) {
  565. if (!$post['has_file'] && $config['force_image_op'])
  566. error($config['error']['noimage']);
  567. }
  568. // Check for too many files
  569. if (sizeof($post['files']) > $config['max_images'])
  570. error($config['error']['toomanyimages']);
  571. }
  572. if ($config['strip_combining_chars']) {
  573. $post['name'] = strip_combining_chars($post['name']);
  574. $post['email'] = strip_combining_chars($post['email']);
  575. $post['subject'] = strip_combining_chars($post['subject']);
  576. $post['body'] = strip_combining_chars($post['body']);
  577. }
  578. if (!$dropped_post) {
  579. // Check string lengths
  580. if (mb_strlen($post['name']) > 35)
  581. error(sprintf($config['error']['toolong'], 'name'));
  582. if (mb_strlen($post['email']) > 40)
  583. error(sprintf($config['error']['toolong'], 'email'));
  584. if (mb_strlen($post['subject']) > 100)
  585. error(sprintf($config['error']['toolong'], 'subject'));
  586. if (!$mod && mb_strlen($post['body']) > $config['max_body'])
  587. error($config['error']['toolong_body']);
  588. if (mb_strlen($post['password']) > 20)
  589. error(sprintf($config['error']['toolong'], 'password'));
  590. }
  591. wordfilters($post['body']);
  592. $post['body'] = escape_markup_modifiers($post['body']);
  593. if ($mod && isset($post['raw']) && $post['raw']) {
  594. $post['body'] .= "\n<tinyboard raw html>1</tinyboard>";
  595. }
  596. if (!$dropped_post)
  597. if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) {
  598. require 'inc/lib/geoip/geoip.inc';
  599. $gi=geoip\geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD);
  600. function ipv4to6($ip) {
  601. if (strpos($ip, ':') !== false) {
  602. if (strpos($ip, '.') > 0)
  603. $ip = substr($ip, strrpos($ip, ':')+1);
  604. else return $ip; //native ipv6
  605. }
  606. $iparr = array_pad(explode('.', $ip), 4, 0);
  607. $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16);
  608. $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16);
  609. return '::ffff:'.$part7.':'.$part8;
  610. }
  611. if ($country_code = geoip\geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) {
  612. if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2')))
  613. $post['body'] .= "\n<tinyboard flag>".strtolower($country_code)."</tinyboard>".
  614. "\n<tinyboard flag alt>".geoip\geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))."</tinyboard>";
  615. }
  616. }
  617. if ($config['user_flag'] && isset($_POST['user_flag']))
  618. if (!empty($_POST['user_flag']) ){
  619. $user_flag = $_POST['user_flag'];
  620. if (!isset($config['user_flags'][$user_flag]))
  621. error(_('Invalid flag selection!'));
  622. $flag_alt = isset($user_flag_alt) ? $user_flag_alt : $config['user_flags'][$user_flag];
  623. $post['body'] .= "\n<tinyboard flag>" . strtolower($user_flag) . "</tinyboard>" .
  624. "\n<tinyboard flag alt>" . $flag_alt . "</tinyboard>";
  625. }
  626. if ($config['allowed_tags'] && $post['op'] && isset($_POST['tag']) && isset($config['allowed_tags'][$_POST['tag']])) {
  627. $post['body'] .= "\n<tinyboard tag>" . $_POST['tag'] . "</tinyboard>";
  628. }
  629. if (!$dropped_post)
  630. if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  631. $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']);
  632. $post['body'] .= "\n<tinyboard proxy>".$proxy."</tinyboard>";
  633. }
  634. if (mysql_version() >= 50503) {
  635. $post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset
  636. } else {
  637. // MySQL's `utf8` charset only supports up to 3-byte symbols
  638. // Remove anything >= 0x010000
  639. $chars = preg_split('//u', $post['body'], -1, PREG_SPLIT_NO_EMPTY);
  640. $post['body_nomarkup'] = '';
  641. foreach ($chars as $char) {
  642. $o = 0;
  643. $ord = ordutf8($char, $o);
  644. if ($ord >= 0x010000)
  645. continue;
  646. $post['body_nomarkup'] .= $char;
  647. }
  648. }
  649. $post['tracked_cites'] = markup($post['body'], true);
  650. if ($post['has_file']) {
  651. $md5cmd = false;
  652. if ($config['bsd_md5']) $md5cmd = '/sbin/md5 -r';
  653. if ($config['gnu_md5']) $md5cmd = 'md5sum';
  654. $allhashes = '';
  655. foreach ($post['files'] as $key => &$file) {
  656. if ($post['op'] && $config['allowed_ext_op']) {
  657. if (!in_array($file['extension'], $config['allowed_ext_op']))
  658. error($config['error']['unknownext']);
  659. }
  660. elseif (!in_array($file['extension'], $config['allowed_ext']) && !in_array($file['extension'], $config['allowed_ext_files']))
  661. error($config['error']['unknownext']);
  662. $file['is_an_image'] = !in_array($file['extension'], $config['allowed_ext_files']);
  663. // Truncate filename if it is too long
  664. $file['filename'] = mb_substr($file['filename'], 0, $config['max_filename_len']);
  665. $upload = $file['tmp_name'];
  666. if (!is_readable($upload))
  667. error($config['error']['nomove']);
  668. if ($md5cmd) {
  669. $output = shell_exec_error($md5cmd . " " . escapeshellarg($upload));
  670. $output = explode(' ', $output);
  671. $hash = $output[0];
  672. }
  673. else {
  674. $hash = md5_file($upload);
  675. }
  676. $file['hash'] = $hash;
  677. $allhashes .= $hash;
  678. }
  679. if (count ($post['files']) == 1) {
  680. $post['filehash'] = $hash;
  681. }
  682. else {
  683. $post['filehash'] = md5($allhashes);
  684. }
  685. }
  686. if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
  687. require_once 'inc/filters.php';
  688. do_filters($post);
  689. }
  690. if ($post['has_file']) {
  691. foreach ($post['files'] as $key => &$file) {
  692. if ($file['is_an_image']) {
  693. if ($config['ie_mime_type_detection'] !== false) {
  694. // Check IE MIME type detection XSS exploit
  695. $buffer = file_get_contents($upload, null, null, null, 255);
  696. if (preg_match($config['ie_mime_type_detection'], $buffer)) {
  697. undoImage($post);
  698. error($config['error']['mime_exploit']);
  699. }
  700. }
  701. require_once 'inc/image.php';
  702. // find dimensions of an image using GD
  703. if (!$size = @getimagesize($file['tmp_name'])) {
  704. error($config['error']['invalidimg']);
  705. }
  706. if (!in_array($size[2], array(IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_BMP))) {
  707. error($config['error']['invalidimg']);
  708. }
  709. if ($size[0] > $config['max_width'] || $size[1] > $config['max_height']) {
  710. error($config['error']['maxsize']);
  711. }
  712. if ($config['convert_auto_orient'] && ($file['extension'] == 'jpg' || $file['extension'] == 'jpeg')) {
  713. // The following code corrects the image orientation.
  714. // Currently only works with the 'convert' option selected but it could easily be expanded to work with the rest if you can be bothered.
  715. if (!($config['redraw_image'] || (($config['strip_exif'] && !$config['use_exiftool']) && ($file['extension'] == 'jpg' || $file['extension'] == 'jpeg')))) {
  716. if (in_array($config['thumb_method'], array('convert', 'convert+gifsicle', 'gm', 'gm+gifsicle'))) {
  717. $exif = @exif_read_data($file['tmp_name']);
  718. $gm = in_array($config['thumb_method'], array('gm', 'gm+gifsicle'));
  719. if (isset($exif['Orientation']) && $exif['Orientation'] != 1) {
  720. if ($config['convert_manual_orient']) {
  721. $error = shell_exec_error(($gm ? 'gm ' : '') . 'convert ' .
  722. escapeshellarg($file['tmp_name']) . ' ' .
  723. ImageConvert::jpeg_exif_orientation(false, $exif) . ' ' .
  724. ($config['strip_exif'] ? '+profile "*"' :
  725. ($config['use_exiftool'] ? '' : '+profile "*"')
  726. ) . ' ' .
  727. escapeshellarg($file['tmp_name']));
  728. if ($config['use_exiftool'] && !$config['strip_exif']) {
  729. if ($exiftool_error = shell_exec_error(
  730. 'exiftool -overwrite_original -q -q -orientation=1 -n ' .
  731. escapeshellarg($file['tmp_name'])))
  732. error(_('exiftool failed!'), null, $exiftool_error);
  733. } else {
  734. // TODO: Find another way to remove the Orientation tag from the EXIF profile
  735. // without needing `exiftool`.
  736. }
  737. } else {
  738. $error = shell_exec_error(($gm ? 'gm ' : '') . 'convert ' .
  739. escapeshellarg($file['tmp_name']) . ' -auto-orient ' . escapeshellarg($upload));
  740. }
  741. if ($error)
  742. error(_('Could not auto-orient image!'), null, $error);
  743. $size = @getimagesize($file['tmp_name']);
  744. if ($config['strip_exif'])
  745. $file['exif_stripped'] = true;
  746. }
  747. }
  748. }
  749. }
  750. // create image object
  751. $image = new Image($file['tmp_name'], $file['extension'], $size);
  752. if ($image->size->width > $config['max_width'] || $image->size->height > $config['max_height']) {
  753. $image->delete();
  754. error($config['error']['maxsize']);
  755. }
  756. $file['width'] = $image->size->width;
  757. $file['height'] = $image->size->height;
  758. if ($config['spoiler_images'] && isset($_POST['spoiler'])) {
  759. $file['thumb'] = 'spoiler';
  760. $size = @getimagesize($config['spoiler_image']);
  761. $file['thumbwidth'] = $size[0];
  762. $file['thumbheight'] = $size[1];
  763. } elseif ($config['minimum_copy_resize'] &&
  764. $image->size->width <= $config['thumb_width'] &&
  765. $image->size->height <= $config['thumb_height'] &&
  766. $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])) {
  767. // Copy, because there's nothing to resize
  768. copy($file['tmp_name'], $file['thumb']);
  769. $file['thumbwidth'] = $image->size->width;
  770. $file['thumbheight'] = $image->size->height;
  771. } else {
  772. $thumb = $image->resize(
  773. $config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'],
  774. $post['op'] ? $config['thumb_op_width'] : $config['thumb_width'],
  775. $post['op'] ? $config['thumb_op_height'] : $config['thumb_height']
  776. );
  777. $thumb->to($file['thumb']);
  778. $file['thumbwidth'] = $thumb->width;
  779. $file['thumbheight'] = $thumb->height;
  780. $thumb->_destroy();
  781. }
  782. if ($config['redraw_image'] || (!@$file['exif_stripped'] && $config['strip_exif'] && ($file['extension'] == 'jpg' || $file['extension'] == 'jpeg'))) {
  783. if (!$config['redraw_image'] && $config['use_exiftool']) {
  784. if($error = shell_exec_error('exiftool -overwrite_original -ignoreMinorErrors -q -q -all= ' .
  785. escapeshellarg($file['tmp_name'])))
  786. error(_('Could not strip EXIF metadata!'), null, $error);
  787. } else {
  788. $image->to($file['file']);
  789. $dont_copy_file = true;
  790. }
  791. }
  792. $image->destroy();
  793. } else {
  794. // not an image
  795. //copy($config['file_thumb'], $post['thumb']);
  796. $file['thumb'] = 'file';
  797. $size = @getimagesize(sprintf($config['file_thumb'],
  798. isset($config['file_icons'][$file['extension']]) ?
  799. $config['file_icons'][$file['extension']] : $config['file_icons']['default']));
  800. $file['thumbwidth'] = $size[0];
  801. $file['thumbheight'] = $size[1];
  802. }
  803. if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { // Let's OCR it!
  804. $fname = $file['tmp_name'];
  805. if ($file['height'] > 500 || $file['width'] > 500) {
  806. $fname = $file['thumb'];
  807. }
  808. if ($fname == 'spoiler') { // We don't have that much CPU time, do we?
  809. }
  810. else {
  811. $tmpname = "tmp/tesseract/".rand(0,10000000);
  812. // Preprocess command is an ImageMagick b/w quantization
  813. $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
  814. 'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']);
  815. $tmpname .= ".txt";
  816. $value = @file_get_contents($tmpname);
  817. @unlink($tmpname);
  818. if ($value && trim($value)) {
  819. // This one has an effect, that the body is appended to a post body. So you can write a correct
  820. // spamfilter.
  821. $post['body_nomarkup'] .= "<tinyboard ocr image $key>".htmlspecialchars($value)."</tinyboard>";
  822. }
  823. }
  824. }
  825. if (!isset($dont_copy_file) || !$dont_copy_file) {
  826. if (isset($file['file_tmp'])) {
  827. if (!@rename($file['tmp_name'], $file['file']))
  828. error($config['error']['nomove']);
  829. chmod($file['file'], 0644);
  830. } elseif (!@move_uploaded_file($file['tmp_name'], $file['file']))
  831. error($config['error']['nomove']);
  832. }
  833. }
  834. if ($config['image_reject_repost']) {
  835. if ($p = getPostByHash($post['filehash'])) {
  836. undoImage($post);
  837. error(sprintf($config['error']['fileexists'],
  838. ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) .
  839. ($board['dir'] . $config['dir']['res'] .
  840. ($p['thread'] ?
  841. $p['thread'] . '.html#' . $p['id']
  842. :
  843. $p['id'] . '.html'
  844. ))
  845. ));
  846. }
  847. } else if (!$post['op'] && $config['image_reject_repost_in_thread']) {
  848. if ($p = getPostByHashInThread($post['filehash'], $post['thread'])) {
  849. undoImage($post);
  850. error(sprintf($config['error']['fileexistsinthread'],
  851. ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) .
  852. ($board['dir'] . $config['dir']['res'] .
  853. ($p['thread'] ?
  854. $p['thread'] . '.html#' . $p['id']
  855. :
  856. $p['id'] . '.html'
  857. ))
  858. ));
  859. }
  860. }
  861. }
  862. // Do filters again if OCRing
  863. if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
  864. do_filters($post);
  865. }
  866. if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) {
  867. undoImage($post);
  868. if ($config['robot_mute']) {
  869. error(sprintf($config['error']['muted'], mute()));
  870. } else {
  871. error($config['error']['unoriginal']);
  872. }
  873. }
  874. // Remove board directories before inserting them into the database.
  875. if ($post['has_file']) {
  876. foreach ($post['files'] as $key => &$file) {
  877. $file['file_path'] = $file['file'];
  878. $file['thumb_path'] = $file['thumb'];
  879. $file['file'] = mb_substr($file['file'], mb_strlen($board['dir'] . $config['dir']['img']));
  880. if ($file['is_an_image'] && $file['thumb'] != 'spoiler')
  881. $file['thumb'] = mb_substr($file['thumb'], mb_strlen($board['dir'] . $config['dir']['thumb']));
  882. }
  883. }
  884. $post = (object)$post;
  885. $post->files = array_map(function($a) { return (object)$a; }, $post->files);
  886. $error = event('post', $post);
  887. $post->files = array_map(function($a) { return (array)$a; }, $post->files);
  888. if ($error) {
  889. undoImage((array)$post);
  890. error($error);
  891. }
  892. $post = (array)$post;
  893. if ($post['files'])
  894. $post['files'] = $post['files'];
  895. $post['num_files'] = sizeof($post['files']);
  896. $post['id'] = $id = post($post);
  897. $post['slug'] = slugify($post);
  898. if ($dropped_post && $dropped_post['from_nntp']) {
  899. $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ".
  900. "(:board , :id , :message_id , :message_id_digest , false, :headers)");
  901. $query->bindValue(':board', $dropped_post['board']);
  902. $query->bindValue(':id', $id);
  903. $query->bindValue(':message_id', $dropped_post['msgid']);
  904. $query->bindValue(':message_id_digest', sha1($dropped_post['msgid']));
  905. $query->bindValue(':headers', $dropped_post['headers']);
  906. $query->execute() or error(db_error($query));
  907. } // ^^^^^ For inbound posts ^^^^^
  908. elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) {
  909. // vvvvv For outbound posts vvvvv
  910. require_once('inc/nntpchan/nntpchan.php');
  911. $msgid = gen_msgid($post['board'], $post['id']);
  912. list($headers, $files) = post2nntp($post, $msgid);
  913. $message = gen_nntp($headers, $files);
  914. $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ".
  915. "(:board , :id , :message_id , :message_id_digest , true , :headers)");
  916. $query->bindValue(':board', $post['board']);
  917. $query->bindValue(':id', $post['id']);
  918. $query->bindValue(':message_id', $msgid);
  919. $query->bindValue(':message_id_digest', sha1($msgid));
  920. $query->bindValue(':headers', json_encode($headers));
  921. $query->execute() or error(db_error($query));
  922. // Let's broadcast it!
  923. nntp_publish($message, $msgid);
  924. }
  925. insertFloodPost($post);
  926. // Handle cyclical threads
  927. if (!$post['op'] && isset($thread['cycle']) && $thread['cycle']) {
  928. // Query is a bit weird due to "This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" (MariaDB Ver 15.1 Distrib 10.0.17-MariaDB, for Linux (x86_64))
  929. $query = prepare(sprintf('DELETE FROM ``posts_%s`` WHERE `thread` = :thread AND `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM ``posts_%s`` WHERE `thread` = :thread ORDER BY `id` DESC LIMIT :limit) i)', $board['uri'], $board['uri']));
  930. $query->bindValue(':thread', $post['thread']);
  931. $query->bindValue(':limit', $config['cycle_limit'], PDO::PARAM_INT);
  932. $query->execute() or error(db_error($query));
  933. }
  934. if (isset($post['antispam_hash'])) {
  935. incrementSpamHash($post['antispam_hash']);
  936. }
  937. if (isset($post['tracked_cites']) && !empty($post['tracked_cites'])) {
  938. $insert_rows = array();
  939. foreach ($post['tracked_cites'] as $cite) {
  940. $insert_rows[] = '(' .
  941. $pdo->quote($board['uri']) . ', ' . (int)$id . ', ' .
  942. $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')';
  943. }
  944. query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error());
  945. }
  946. if (!$post['op'] && strtolower($post['email']) != 'sage' && !$thread['sage'] && ($config['reply_limit'] == 0 || $numposts['replies']+1 < $config['reply_limit'])) {
  947. bumpThread($post['thread']);
  948. }
  949. if (isset($_SERVER['HTTP_REFERER'])) {
  950. // Tell Javascript that we posted successfully
  951. if (isset($_COOKIE[$config['cookies']['js']]))
  952. $js = json_decode($_COOKIE[$config['cookies']['js']]);
  953. else
  954. $js = (object) array();
  955. // Tell it to delete the cached post for referer
  956. $js->{$_SERVER['HTTP_REFERER']} = true;
  957. // Encode and set cookie
  958. setcookie($config['cookies']['js'], json_encode($js), 0, $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, false, false);
  959. }
  960. $root = $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
  961. if ($noko) {
  962. $redirect = $root . $board['dir'] . $config['dir']['res'] .
  963. link_for($post, false, false, $thread) . (!$post['op'] ? '#' . $id : '');
  964. if (!$post['op'] && isset($_SERVER['HTTP_REFERER'])) {
  965. $regex = array(
  966. 'board' => str_replace('%s', '(\w{1,8})', preg_quote($config['board_path'], '/')),
  967. 'page' => str_replace('%d', '(\d+)', preg_quote($config['file_page'], '/')),
  968. 'page50' => '(' . str_replace('%d', '(\d+)', preg_quote($config['file_page50'], '/')) . '|' .
  969. str_replace(array('%d', '%s'), array('(\d+)', '[a-z0-9-]+'), preg_quote($config['file_page50_slug'], '/')) . ')',
  970. 'res' => preg_quote($config['dir']['res'], '/'),
  971. );
  972. if (preg_match('/\/' . $regex['board'] . $regex['res'] . $regex['page50'] . '([?&].*)?$/', $_SERVER['HTTP_REFERER'])) {
  973. $redirect = $root . $board['dir'] . $config['dir']['res'] .
  974. link_for($post, true, false, $thread) . (!$post['op'] ? '#' . $id : '');
  975. }
  976. }
  977. } else {
  978. $redirect = $root . $board['dir'] . $config['file_index'];
  979. }
  980. buildThread($post['op'] ? $id : $post['thread']);
  981. if ($config['syslog'])
  982. _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] .
  983. link_for($post) . (!$post['op'] ? '#' . $id : ''));
  984. if (!$post['mod']) header('X-Associated-Content: "' . $redirect . '"');
  985. if (!isset($_POST['json_response'])) {
  986. header('Location: ' . $redirect, true, $config['redirect_http']);
  987. } else {
  988. header('Content-Type: text/json; charset=utf-8');
  989. echo json_encode(array(
  990. 'redirect' => $redirect,
  991. 'noko' => $noko,
  992. 'id' => $id
  993. ));
  994. }
  995. if ($config['try_smarter'] && $post['op'])
  996. $build_pages = range(1, $config['max_pages']);
  997. if ($post['op'])
  998. clean($id);
  999. event('post-after', $post);
  1000. buildIndex();
  1001. // We are already done, let's continue our heavy-lifting work in the background (if we run off FastCGI)
  1002. if (function_exists('fastcgi_finish_request'))
  1003. @fastcgi_finish_request();
  1004. if ($post['op'])
  1005. rebuildThemes('post-thread', $board['uri']);
  1006. else
  1007. rebuildThemes('post', $board['uri']);
  1008. } elseif (isset($_POST['appeal'])) {
  1009. if (!isset($_POST['ban_id']))
  1010. error($config['error']['bot']);
  1011. $ban_id = (int)$_POST['ban_id'];
  1012. $bans = Bans::find($_SERVER['REMOTE_ADDR']);
  1013. foreach ($bans as $_ban) {
  1014. if ($_ban['id'] == $ban_id) {
  1015. $ban = $_ban;
  1016. break;
  1017. }
  1018. }
  1019. if (!isset($ban)) {
  1020. error(_("That ban doesn't exist or is not for you."));
  1021. }
  1022. if ($ban['expires'] && $ban['expires'] - $ban['created'] <= $config['ban_appeals_min_length']) {
  1023. error(_("You cannot appeal a ban of this length."));
  1024. }
  1025. $query = query("SELECT `denied` FROM ``ban_appeals`` WHERE `ban_id` = $ban_id") or error(db_error());
  1026. $ban_appeals = $query->fetchAll(PDO::FETCH_COLUMN);
  1027. if (count($ban_appeals) >= $config['ban_appeals_max']) {
  1028. error(_("You cannot appeal this ban again."));
  1029. }
  1030. foreach ($ban_appeals as $is_denied) {
  1031. if (!$is_denied)
  1032. error(_("There is already a pending appeal for this ban."));
  1033. }
  1034. $query = prepare("INSERT INTO ``ban_appeals`` VALUES (NULL, :ban_id, :time, :message, 0)");
  1035. $query->bindValue(':ban_id', $ban_id, PDO::PARAM_INT);
  1036. $query->bindValue(':time', time(), PDO::PARAM_INT);
  1037. $query->bindValue(':message', $_POST['appeal']);
  1038. $query->execute() or error(db_error($query));
  1039. displayBan($ban);
  1040. } else {
  1041. if (!file_exists($config['has_installed'])) {
  1042. header('Location: install.php', true, $config['redirect_http']);
  1043. } else {
  1044. // They opened post.php in their browser manually.
  1045. error($config['error']['nopost']);
  1046. }
  1047. }