Paste.php example

PHP Detected boxlabs 10 Views Size: 45.96 KB Posted on: Sep 2, 25 @ 9:18 PM
  1. 1<?php
  2. 2/*
  3. 3 * Paste $v3.2 2025/09/01 https://github.com/boxlabss/PASTE
  4. 4 * demo: https://paste.boxlabs.uk/
  5. 5 *
  6. 6 * https://phpaste.sourceforge.io/
  7. 7 *
  8. 8 * This program is free software; you can redistribute it and/or
  9. 9 * modify it under the terms of the GNU General Public License
  10. 10 * as published by the Free Software Foundation; either version 3
  11. 11 * of the License, or (at your option) any later version.
  12. 12 *
  13. 13 * This program is distributed in the hope that it will be useful,
  14. 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. 16 * GNU General Public License in LICENCE for more details.
  17. 17 */
  18. 18declare(strict_types=1);
  19. 19
  20. 20require_once 'includes/session.php';
  21. 21require_once 'config.php';
  22. 22
  23. 23// Load highlighter engine libs conditionally
  24. 24if (($highlighter ?? 'geshi') === 'geshi') {
  25. 25 require_once 'includes/geshi.php';
  26. 26} else {
  27. 27 require_once __DIR__ . '/includes/hlbootstrap.php';
  28. 28}
  29. 29
  30. 30require_once 'includes/functions.php';
  31. 31
  32. 32// ensure these are visible to all included templates (header/footer/sidebar)
  33. 33global $pdo, $mod_rewrite;
  34. 34
  35. 35// default to avoid notices if config hasn't set it (DB can override later)
  36. 36if (!isset($mod_rewrite)) {
  37. 37 $mod_rewrite = '0';
  38. 38}
  39. 39
  40. 40$path = 'includes/geshi/'; // GeSHi language files
  41. 41$parsedown_path = 'includes/Parsedown/Parsedown.php'; // Markdown
  42. 42$ges_style = ''; // no inline CSS injection
  43. 43$require_password = false; // errors.php shows password box when true
  44. 44
  45. 45// ---------------- Helpers ----------------
  46. 46
  47. 47// --- highlight theme override (?theme= or ?highlight=) ---
  48. 48if (($highlighter ?? 'geshi') === 'highlight') {
  49. 49 $param = $_GET['theme'] ?? $_GET['highlight'] ?? null;
  50. 50 if ($param !== null) {
  51. 51 // normalize: accept "dracula", "dracula.css", or "atelier estuary dark"
  52. 52 $t = strtolower((string)$param);
  53. 53 $t = str_replace(['+', ' ', '_'], '-', $t);
  54. 54 $t = preg_replace('~\.css$~', '', $t);
  55. 55 $t = preg_replace('~[^a-z0-9.-]~', '', $t);
  56. 56
  57. 57 $stylesRel = 'includes/Highlight/styles';
  58. 58 $fs = __DIR__ . '/' . $stylesRel . '/' . $t . '.css';
  59. 59 if (is_file($fs)) {
  60. 60 // header.php will read this to seed the initial <link>
  61. 61 $hl_style = $t . '.css';
  62. 62 }
  63. 63 }
  64. 64}
  65. 65
  66. 66// Map some GeSHi-style names to highlight.js ids
  67. 67function map_to_hl_lang(string $code): string {
  68. 68 static $map = [
  69. 69 'text' => 'plaintext',
  70. 70 'html5' => 'xml',
  71. 71 'html4strict' => 'xml',
  72. 72 'php-brief' => 'php',
  73. 73 'pycon' => 'python',
  74. 74 'postgresql' => 'pgsql',
  75. 75 'dos' => 'dos',
  76. 76 'vb' => 'vbnet',
  77. 77 ];
  78. 78 $code = strtolower($code);
  79. 79 return $map[$code] ?? $code;
  80. 80}
  81. 81
  82. 82// Wrap hljs tokens in a line-numbered <ol> so togglev() works
  83. 83function hl_wrap_with_lines(string $value, string $hlLang, array $highlight_lines): string {
  84. 84 $lines = explode("\n", $value);
  85. 85 $digits = max(2, strlen((string) count($lines))); // how many digits do we need?
  86. 86 $hlset = $highlight_lines ? array_flip($highlight_lines) : [];
  87. 87
  88. 88 $out = [];
  89. 89 $out[] = '<pre class="hljs"><code class="hljs language-' . htmlspecialchars($hlLang, ENT_QUOTES, 'UTF-8') . '">';
  90. 90 $out[] = '<ol class="hljs-ln" style="--ln-digits:' . (int)$digits . '">';
  91. 91 foreach ($lines as $i => $lineHtml) {
  92. 92 $ln = $i + 1;
  93. 93 $cls = isset($hlset[$ln]) ? ' class="hljs-ln-line hljs-hl"' : ' class="hljs-ln-line"';
  94. 94 $out[] = '<li' . $cls . '><span class="hljs-ln-n">' . $ln . '</span><span class="hljs-ln-c">' . $lineHtml . '</span></li>';
  95. 95 }
  96. 96 $out[] = '</ol></code></pre>';
  97. 97 return implode('', $out);
  98. 98}
  99. 99
  100. 100// Add a class to specific <li> lines in GeSHi output (no inline styles)
  101. 101function geshi_add_line_highlight_class(string $html, array $highlight_lines, string $class = 'hljs-hl'): string {
  102. 102 if (!$highlight_lines) return $html;
  103. 103 $targets = array_flip($highlight_lines);
  104. 104 $i = 0;
  105. 105 return preg_replace_callback('/<li\b([^>]*)>/', static function($m) use (&$i, $targets, $class) {
  106. 106 $i++;
  107. 107 $attrs = $m[1];
  108. 108 if (!isset($targets[$i])) return '<li' . $attrs . '>';
  109. 109 if (preg_match('/\bclass="([^"]*)"/i', $attrs, $cm)) {
  110. 110 $new = trim($cm[1] . ' ' . $class);
  111. 111 $attrs = preg_replace('/\bclass="[^"]*"/i', 'class="' . htmlspecialchars($new, ENT_QUOTES, 'UTF-8') . '"', $attrs, 1);
  112. 112 } else {
  113. 113 $attrs .= ' class="' . htmlspecialchars($class, ENT_QUOTES, 'UTF-8') . '"';
  114. 114 }
  115. 115 return '<li' . $attrs . '>';
  116. 116 }, $html) ?? $html;
  117. 117}
  118. 118
  119. 119// ---------- Explain how autodetect made a decision ----------
  120. 120function paste_build_explain(string $source, string $title, string $sample, string $lang): string {
  121. 121 $sample = (string) $sample;
  122. 122 if ($sample !== '') $sample = substr($sample, 0, 2048);
  123. 123 $lang = strtolower($lang);
  124. 124 $ext = strtolower((string) pathinfo((string)$title, PATHINFO_EXTENSION));
  125. 125
  126. 126 switch ($source) {
  127. 127 case 'php-tag':
  128. 128 $phpCount = preg_match_all('/<\?(php|=)/i', $sample, $m1);
  129. 129 return "Found PHP opening tag(s) (" . (int)$phpCount . ") such as '<?php' or '<?='. Locked to PHP.";
  130. 130 case 'filename':
  131. 131 return $ext ? "Paste title ends with .{$ext}. Mapped extension → {$lang}." : "Filename hint used. Mapped to {$lang}.";
  132. 132 case 'shebang':
  133. 133 if (preg_match('/^#![^\r\n]+/m', $sample, $m)) return "Shebang line detected: {$m[0]} → {$lang}.";
  134. 134 return "Shebang detected → {$lang}.";
  135. 135 case 'modeline':
  136. 136 if (preg_match('/-\*-\s*mode:\s*([a-z0-9#+-]+)\s*;?/i', $sample, $m)) return "Emacs modeline ‘mode: {$m[1]}’ → {$lang}.";
  137. 137 if (preg_match('/(?:^|\n)[ \t]*(?:vi|vim):[^\n]*\bfiletype=([a-z0-9#+-]+)/i', $sample, $m)) return "Vim modeline ‘filetype={$m[1]}’ → {$lang}.";
  138. 138 return "Editor modeline matched → {$lang}.";
  139. 139 case 'fence':
  140. 140 if (preg_match('/```([a-z0-9#+._-]{1,32})/i', $sample, $m)) return "Markdown code fence language tag ‘{$m[1]}’ → {$lang}.";
  141. 141 return "Markdown code fence language tag → {$lang}.";
  142. 142 case 'markdown': {
  143. 143 $h = preg_match_all('/^(?:#{1,6}\s|>|-{3,}\s*$|\*\s|\d+\.\s|\[.+?\]\(.+?\))/m', $sample);
  144. 144 return "Markdown structure detected (headings/lists/links: ~{$h} signals). Rendered as Markdown.";
  145. 145 }
  146. 146 case 'heuristic': {
  147. 147 if (in_array($lang, ['yaml','yml'], true)) {
  148. 148 $kv = preg_match_all('/^[ \t\-]*[A-Za-z0-9_.-]+:\s/m', $sample);
  149. 149 $list = preg_match_all('/^[ \t]*-\s/m', $sample);
  150. 150 return "YAML heuristics: key:value pairs (~{$kv}) and list items (~{$list}).";
  151. 151 }
  152. 152 if (in_array($lang, ['sql','pgsql','mysql','tsql'], true)) {
  153. 153 $kw = preg_match_all('/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|FROM|WHERE|JOIN)\b/i', $sample);
  154. 154 return "SQL keywords matched (~{$kw}). Guessed {$lang}.";
  155. 155 }
  156. 156 if ($lang === 'makefile' || $lang === 'make') {
  157. 157 $t = preg_match_all('/^[A-Za-z0-9_.-]+:.*$/m', $sample);
  158. 158 return "Makefile targets detected (~{$t}).";
  159. 159 }
  160. 160 if ($lang === 'dos' || $lang === 'batch') {
  161. 161 $b = preg_match_all('/^\s*(?:@?echo|rem|set|call|goto)\b/i', $sample);
  162. 162 return "Batch/DOS commands matched (~{$b}).";
  163. 163 }
  164. 164 return "Heuristic rules matched characteristic tokens for {$lang}.";
  165. 165 }
  166. 166 case 'hljs':
  167. 167 case 'hljs-auto':
  168. 168 return "highlight.php auto-guess selected ‘{$lang}’ after other hints were inconclusive.";
  169. 169 case 'fallback':
  170. 170 return "Generic fallback selected a safe default (‘{$lang}’).";
  171. 171 case 'explicit':
  172. 172 return "Language was explicitly chosen by the author.";
  173. 173 default:
  174. 174 return ucfirst($source) . " → {$lang}.";
  175. 175 }
  176. 176}
  177. 177
  178. 178// ---------- autodetect: filename/extension hint ----------
  179. 179function paste_pick_from_filename(?string $title, string $engine = 'highlight', $hl = null): ?string {
  180. 180 $title = (string) ($title ?? '');
  181. 181 $base = trim(basename($title));
  182. 182 if ($base === '') return null;
  183. 183
  184. 184 // No extension cases
  185. 185 if (strcasecmp($base, 'Makefile') === 0) return ($engine === 'geshi') ? 'make' : 'makefile';
  186. 186 if (strcasecmp($base, 'Dockerfile') === 0) return 'dockerfile';
  187. 187
  188. 188 $ext = strtolower((string) pathinfo($base, PATHINFO_EXTENSION));
  189. 189 if ($ext === '') return null;
  190. 190
  191. 191 static $ext2hl = [
  192. 192 'php'=>'php','phtml'=>'php','inc'=>'php',
  193. 193 'js'=>'javascript','mjs'=>'javascript','cjs'=>'javascript',
  194. 194 'ts'=>'typescript','tsx'=>'typescript','jsx'=>'javascript',
  195. 195 'py'=>'python','rb'=>'ruby','pl'=>'perl','pm'=>'perl','raku'=>'perl',
  196. 196 'sh'=>'bash','bash'=>'bash','zsh'=>'bash','ksh'=>'bash',
  197. 197 'ps1'=>'powershell',
  198. 198 'c'=>'c','h'=>'c','i'=>'c',
  199. 199 'cpp'=>'cpp','cxx'=>'cpp','cc'=>'cpp','hpp'=>'cpp','hxx'=>'cpp','hh'=>'cpp',
  200. 200 'cs'=>'csharp','java'=>'java','go'=>'go','rs'=>'rust','kt'=>'kotlin','kts'=>'kotlin',
  201. 201 'm'=>'objectivec','mm'=>'objectivec',
  202. 202 'json'=>'json','yml'=>'yaml','yaml'=>'yaml','ini'=>'ini','toml'=>'toml','properties'=>'properties',
  203. 203 'xml'=>'xml','xhtml'=>'xml','xq'=>'xquery','xqy'=>'xquery',
  204. 204 'html'=>'xml','htm'=>'xml','svg'=>'xml',
  205. 205 'md'=>'markdown','markdown'=>'markdown',
  206. 206 'sql'=>'sql','psql'=>'pgsql',
  207. 207 'bat'=>'dos','cmd'=>'dos','nginx'=>'nginx','conf'=>'ini','cfg'=>'ini','txt'=>'plaintext',
  208. 208 'make'=>'makefile','mk'=>'makefile','mak'=>'makefile','gradle'=>'gradle','dockerfile'=>'dockerfile'
  209. 209 ];
  210. 210
  211. 211 $hlId = $ext2hl[$ext] ?? null;
  212. 212 if ($hlId === null) return null;
  213. 213
  214. 214 if ($engine === 'highlight') {
  215. 215 if ($hl && method_exists($hl, 'listLanguages')) {
  216. 216 $set = array_map('strtolower', (array)$hl->listLanguages());
  217. 217 if (in_array($hlId, $set, true)) return $hlId;
  218. 218 if ($hlId === 'pgsql' && in_array('sql', $set, true)) return 'sql';
  219. 219 if ($hlId === 'plaintext' && in_array('text', $set, true)) return 'text';
  220. 220 return null;
  221. 221 }
  222. 222 return $hlId;
  223. 223 }
  224. 224
  225. 225 // GeSHi: normalize if helper exists
  226. 226 if (function_exists('paste_normalize_lang')) {
  227. 227 $g = paste_normalize_lang($hlId, 'geshi', null);
  228. 228 return $g ?: null;
  229. 229 }
  230. 230 return null;
  231. 231}
  232. 232
  233. 233function is_probable_php_tag(string $code): bool {
  234. 234 return (bool) preg_match('/<\?(php|=)/i', $code);
  235. 235}
  236. 236
  237. 237// --- Safe themed error renderers (header -> errors -> footer) ---
  238. 238function themed_error_render(string $msg, int $http_code = 404, bool $show_password_form = false): void {
  239. 239 global $default_theme, $lang, $baseurl, $site_name, $pdo, $mod_rewrite, $require_password, $paste_id;
  240. 240
  241. 241 $site_name = $site_name ?? '';
  242. 242 $p_title = $lang['error'] ?? 'Error';
  243. 243 $enablegoog = 'no';
  244. 244 $enablefb = 'no';
  245. 245
  246. 246 if (!headers_sent()) {
  247. 247 http_response_code($http_code);
  248. 248 header('Content-Type: text/html; charset=utf-8');
  249. 249 }
  250. 250
  251. 251 $require_password = $show_password_form;
  252. 252 $error = $msg;
  253. 253
  254. 254 $theme = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
  255. 255 require_once $theme . '/header.php';
  256. 256 require_once $theme . '/errors.php';
  257. 257 require_once $theme . '/footer.php';
  258. 258 exit;
  259. 259}
  260. 260
  261. 261function render_error_and_exit(string $msg, string $http = '404'): void {
  262. 262 $code = ($http === '403') ? 403 : 404;
  263. 263 themed_error_render($msg, $code, false);
  264. 264}
  265. 265
  266. 266function render_password_required_and_exit(string $msg): void {
  267. 267 themed_error_render($msg, 403, true);
  268. 268}
  269. 269
  270. 270// --- Inputs ---
  271. 271$p_password = '';
  272. 272$paste_id = null;
  273. 273if (isset($_GET['id']) && $_GET['id'] !== '') {
  274. 274 $paste_id = (int) trim((string) $_GET['id']);
  275. 275} elseif (isset($_POST['id']) && $_POST['id'] !== '') {
  276. 276 $paste_id = (int) trim((string) $_POST['id']);
  277. 277}
  278. 278
  279. 279try {
  280. 280 // site_info
  281. 281 $stmt = $pdo->query("SELECT * FROM site_info WHERE id='1'");
  282. 282 $si = $stmt->fetch() ?: [];
  283. 283 $title = trim($si['title'] ?? '');
  284. 284 $des = trim($si['des'] ?? '');
  285. 285 $baseurl = rtrim(trim($si['baseurl'] ?? ''), '/') . '/';
  286. 286 $keyword = trim($si['keyword'] ?? '');
  287. 287 $site_name = trim($si['site_name'] ?? '');
  288. 288 $email = trim($si['email'] ?? '');
  289. 289 $twit = trim($si['twit'] ?? '');
  290. 290 $face = trim($si['face'] ?? '');
  291. 291 $gplus = trim($si['gplus'] ?? '');
  292. 292 $ga = trim($si['ga'] ?? '');
  293. 293 $additional_scripts = trim($si['additional_scripts'] ?? '');
  294. 294
  295. 295 // allow DB to define mod_rewrite
  296. 296 if (isset($si['mod_rewrite']) && $si['mod_rewrite'] !== '') {
  297. 297 $mod_rewrite = (string) $si['mod_rewrite'];
  298. 298 }
  299. 299
  300. 300 // interface
  301. 301 $stmt = $pdo->query("SELECT * FROM interface WHERE id='1'");
  302. 302 $iface = $stmt->fetch() ?: [];
  303. 303 $default_lang = trim($iface['lang'] ?? 'en.php');
  304. 304 $default_theme = trim($iface['theme'] ?? 'default');
  305. 305 require_once("langs/$default_lang");
  306. 306
  307. 307 // ban check
  308. 308 $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
  309. 309 if (is_banned($pdo, $ip)) {
  310. 310 render_error_and_exit($lang['banned'] ?? 'You are banned from this site.', '403');
  311. 311 }
  312. 312
  313. 313 // site permissions
  314. 314 $stmt = $pdo->query("SELECT * FROM site_permissions WHERE id='1'");
  315. 315 $perm = $stmt->fetch() ?: [];
  316. 316 $disableguest = trim($perm['disableguest'] ?? 'off');
  317. 317 $siteprivate = trim($perm['siteprivate'] ?? 'off');
  318. 318 if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $siteprivate === "on") {
  319. 319 $privatesite = "on";
  320. 320 }
  321. 321
  322. 322 // page views (best effort)
  323. 323 $date = date('Y-m-d');
  324. 324 try {
  325. 325 $stmt = $pdo->prepare("SELECT id, tpage, tvisit FROM page_view WHERE date = ?");
  326. 326 $stmt->execute([$date]);
  327. 327 $pv = $stmt->fetch();
  328. 328 if ($pv) {
  329. 329 $page_view_id = (int) $pv['id'];
  330. 330 $tpage = (int) $pv['tpage'] + 1;
  331. 331 $tvisit = (int) $pv['tvisit'];
  332. 332
  333. 333 $stmt = $pdo->prepare("SELECT COUNT(*) FROM visitor_ips WHERE ip = ? AND visit_date = ?");
  334. 334 $stmt->execute([$ip, $date]);
  335. 335 if ((int) $stmt->fetchColumn() === 0) {
  336. 336 $tvisit++;
  337. 337 $stmt = $pdo->prepare("INSERT INTO visitor_ips (ip, visit_date) VALUES (?, ?)");
  338. 338 $stmt->execute([$ip, $date]);
  339. 339 }
  340. 340 $stmt = $pdo->prepare("UPDATE page_view SET tpage = ?, tvisit = ? WHERE id = ?");
  341. 341 $stmt->execute([$tpage, $tvisit, $page_view_id]);
  342. 342 } else {
  343. 343 $stmt = $pdo->prepare("INSERT INTO page_view (date, tpage, tvisit) VALUES (?, ?, ?)");
  344. 344 $stmt->execute([$date, 1, 1]);
  345. 345 $stmt = $pdo->prepare("INSERT INTO visitor_ips (ip, visit_date) VALUES (?, ?)");
  346. 346 $stmt->execute([$ip, $date]);
  347. 347 }
  348. 348 } catch (PDOException $e) {
  349. 349 error_log("Page view tracking error: " . $e->getMessage());
  350. 350 }
  351. 351
  352. 352 // ads
  353. 353 $stmt = $pdo->query("SELECT * FROM ads WHERE id='1'");
  354. 354 $ads = $stmt->fetch() ?: [];
  355. 355 $text_ads = trim($ads['text_ads'] ?? '');
  356. 356 $ads_1 = trim($ads['ads_1'] ?? '');
  357. 357 $ads_2 = trim($ads['ads_2'] ?? '');
  358. 358
  359. 359 // Guard ID
  360. 360 if (!$paste_id) {
  361. 361 render_error_and_exit($lang['notfound'] ?? 'Paste not found.');
  362. 362 }
  363. 363
  364. 364 // load paste
  365. 365 $stmt = $pdo->prepare("SELECT * FROM pastes WHERE id = ?");
  366. 366 $stmt->execute([$paste_id]);
  367. 367 if ($stmt->rowCount() === 0) {
  368. 368 render_error_and_exit($lang['notfound'] ?? 'Paste not found.');
  369. 369 }
  370. 370 $row = $stmt->fetch();
  371. 371
  372. 372 // paste fields
  373. 373 $p_title = (string) ($row['title'] ?? '');
  374. 374 $p_content = (string) ($row['content'] ?? '');
  375. 375 $p_visible = (string) ($row['visible'] ?? '0');
  376. 376 $p_code = (string) ($row['code'] ?? 'text');
  377. 377 $p_expiry = trim((string) ($row['expiry'] ?? 'NULL'));
  378. 378 $p_password = (string) ($row['password'] ?? 'NONE');
  379. 379 $p_member = (string) ($row['member'] ?? '');
  380. 380 $p_date = (string) ($row['date'] ?? '');
  381. 381 $p_encrypt = (string) ($row['encrypt'] ?? '0');
  382. 382 $p_views = getPasteViewCount($pdo, (int) $paste_id);
  383. 383
  384. 384 // ---- comments config for view (AFTER $p_password is known) ----
  385. 385 // Read site-wide flags from config.php (with safe defaults)
  386. 386 $comments_enabled = isset($comments_enabled) ? (bool)$comments_enabled : true;
  387. 387 $comments_require_login = isset($comments_require_login) ? (bool)$comments_require_login : true;
  388. 388 $comments_on_protected = isset($comments_on_protected) ? (bool)$comments_on_protected : false;
  389. 389
  390. 390 // Should we render the comments section for this paste?
  391. 391 $show_comments = $comments_enabled && ($comments_on_protected || $p_password === "NONE");
  392. 392 // Is the current user allowed to post a comment?
  393. 393 $can_comment = $show_comments && ( !$comments_require_login || isset($_SESSION['username']) );
  394. 394
  395. 395 $comment_error = '';
  396. 396 $comment_success = '';
  397. 397
  398. 398 // private?
  399. 399 if ($p_visible === "2") {
  400. 400 if (!isset($_SESSION['username']) || $p_member !== (string) ($_SESSION['username'] ?? '')) {
  401. 401 render_error_and_exit($lang['privatepaste'] ?? 'This is a private paste.', '403');
  402. 402 }
  403. 403 }
  404. 404
  405. 405 // expiry
  406. 406 if ($p_expiry !== "NULL" && $p_expiry !== "SELF") {
  407. 407 $input_time = (int) $p_expiry;
  408. 408 if ($input_time > 0 && $input_time < time()) {
  409. 409 render_error_and_exit($lang['expired'] ?? 'This paste has expired.');
  410. 410 }
  411. 411 }
  412. 412
  413. 413 // decrypt if needed
  414. 414 if ($p_encrypt === "1") {
  415. 415 if (!defined('SECRET')) {
  416. 416 render_error_and_exit(($lang['error'] ?? 'Error') . ': Missing SECRET.', '403');
  417. 417 }
  418. 418 $dec = decrypt($p_content, hex2bin(SECRET));
  419. 419 if ($dec === null || $dec === '') {
  420. 420 render_error_and_exit(($lang['error'] ?? 'Error') . ': Decryption failed.', '403');
  421. 421 }
  422. 422 $p_content = $dec;
  423. 423 }
  424. 424 $op_content = trim(htmlspecialchars_decode($p_content));
  425. 425
  426. 426 // download/raw/embed
  427. 427 if (isset($_GET['download'])) {
  428. 428 if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
  429. 429 doDownload((int) $paste_id, $p_title, $op_content, $p_code);
  430. 430 exit;
  431. 431 }
  432. 432 render_password_required_and_exit(
  433. 433 isset($_GET['password'])
  434. 434 ? ($lang['wrongpassword'] ?? 'Incorrect password.')
  435. 435 : ($lang['pwdprotected'] ?? 'This paste is password-protected.')
  436. 436 );
  437. 437 }
  438. 438
  439. 439 if (isset($_GET['raw'])) {
  440. 440 if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
  441. 441 rawView((int) $paste_id, $p_title, $op_content, $p_code);
  442. 442 exit;
  443. 443 }
  444. 444 render_password_required_and_exit(
  445. 445 isset($_GET['password'])
  446. 446 ? ($lang['wrongpassword'] ?? 'Incorrect password.')
  447. 447 : ($lang['pwdprotected'] ?? 'This paste is password-protected.')
  448. 448 );
  449. 449 }
  450. 450
  451. 451 if (isset($_GET['embed'])) {
  452. 452 if ($p_password === "NONE" || (isset($_GET['password']) && password_verify((string) $_GET['password'], $p_password))) {
  453. 453 // Embed view is standalone; we pass empty $ges_style as we don't inject CSS here.
  454. 454 embedView((int) $paste_id, $p_title, $p_content, $p_code, $title, $baseurl, $ges_style, $lang);
  455. 455 exit;
  456. 456 }
  457. 457 render_password_required_and_exit(
  458. 458 isset($_GET['password'])
  459. 459 ? ($lang['wrongpassword'] ?? 'Incorrect password.')
  460. 460 : ($lang['pwdprotected'] ?? 'This paste is password-protected.')
  461. 461 );
  462. 462 }
  463. 463
  464. 464 // highlight extraction
  465. 465 $highlight = [];
  466. 466 $prefix = '!highlight!';
  467. 467 if ($prefix !== '') {
  468. 468 $lines = explode("\n", $p_content);
  469. 469 $p_content = '';
  470. 470 foreach ($lines as $idx => $line) {
  471. 471 if (strncmp($line, $prefix, strlen($prefix)) === 0) {
  472. 472 $highlight[] = $idx + 1;
  473. 473 $line = substr($line, strlen($prefix));
  474. 474 }
  475. 475 $p_content .= $line . "\n";
  476. 476 }
  477. 477 $p_content = rtrim($p_content);
  478. 478 }
  479. 479
  480. 480 // -------------- transform content --------------
  481. 481 $p_code_explain = ''; // will be filled when we detect
  482. 482
  483. 483 if ($p_code === "markdown") {
  484. 484 // ---------- Markdown (keep using Parsedown, safe) ----------
  485. 485 require_once $parsedown_path;
  486. 486 $Parsedown = new Parsedown();
  487. 487
  488. 488 $md_input = htmlspecialchars_decode($p_content);
  489. 489
  490. 490 // Disable raw HTML and sanitize URLs during Markdown rendering
  491. 491 if (method_exists($Parsedown, 'setSafeMode')) {
  492. 492 $Parsedown->setSafeMode(true);
  493. 493 if (method_exists($Parsedown, 'setMarkupEscaped')) {
  494. 494 $Parsedown->setMarkupEscaped(true);
  495. 495 }
  496. 496 } else {
  497. 497 // Fallback for very old Parsedown: escape raw HTML tags BEFORE parsing
  498. 498 $md_input = preg_replace_callback('/<[^>]*>/', static function($m){
  499. 499 return htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8');
  500. 500 }, $md_input);
  501. 501 }
  502. 502
  503. 503 $rendered = $Parsedown->text($md_input);
  504. 504 $p_content = '<div class="md-body">'.sanitize_allowlist_html($rendered).'</div>';
  505. 505
  506. 506 $p_code_effective = 'markdown';
  507. 507 $p_code_label = $geshiformats['markdown'] ?? 'Markdown';
  508. 508 $p_code_source = 'explicit';
  509. 509 $p_code_explain = 'Language was explicitly chosen by the author.';
  510. 510
  511. 511 } else {
  512. 512 // ---------- Code (choose engine) ----------
  513. 513 $code_input = htmlspecialchars_decode($p_content);
  514. 514
  515. 515 if (($highlighter ?? 'geshi') === 'highlight') {
  516. 516 // ---- Highlight.php ----
  517. 517 $hlId = map_to_hl_lang($p_code); // map geshi -> hljs ids
  518. 518 $use_auto = ($hlId === 'auto' || $hlId === 'autodetect' || $hlId === '' || $hlId === 'text');
  519. 519
  520. 520 try {
  521. 521 $hl = function_exists('make_highlighter') ? make_highlighter() : null;
  522. 522 if (!$hl) { throw new \RuntimeException('Highlighter not available'); }
  523. 523
  524. 524 // 1) Filename strong hint if auto
  525. 525 if ($use_auto) {
  526. 526 $fname = paste_pick_from_filename($p_title ?? '', 'highlight', $hl);
  527. 527 if ($fname) {
  528. 528 $langTry = $fname;
  529. 529 $p_code_source = 'filename';
  530. 530 $p_code_explain = paste_build_explain('filename', $p_title ?? '', $code_input, $langTry);
  531. 531 $res = $hl->highlight($langTry, $code_input);
  532. 532 $inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
  533. 533 $p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
  534. 534 $p_code_effective = $langTry;
  535. 535 $p_code_label = $geshiformats[$langTry] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($langTry) : strtoupper($langTry));
  536. 536 goto HL_DONE;
  537. 537 }
  538. 538 }
  539. 539
  540. 540 // 2) PHP tag hard rule if auto
  541. 541 if ($use_auto && is_probable_php_tag($code_input)) {
  542. 542 $langTry = 'php';
  543. 543 $p_code_source = 'php-tag';
  544. 544 $p_code_explain = paste_build_explain('php-tag', $p_title ?? '', $code_input, $langTry);
  545. 545 $res = $hl->highlight($langTry, $code_input);
  546. 546 $inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
  547. 547 $p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
  548. 548 $p_code_effective = $langTry;
  549. 549 $p_code_label = $geshiformats[$langTry] ?? 'PHP';
  550. 550 goto HL_DONE;
  551. 551 }
  552. 552
  553. 553 if ($use_auto && function_exists('paste_autodetect_language')) {
  554. 554 // Unified autodetect
  555. 555 $det = paste_autodetect_language($code_input, 'highlight', $hl);
  556. 556 $langTry = $det['id'];
  557. 557 $p_code_effective = $langTry;
  558. 558 $p_code_label = $geshiformats[$langTry] ?? $det['label'];
  559. 559 $p_code_source = $det['source'];
  560. 560 $p_code_explain = $det['explain'] ?? '';
  561. 561 if ($p_code_explain === '') {
  562. 562 $p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $langTry);
  563. 563 }
  564. 564
  565. 565 if ($langTry === 'markdown') {
  566. 566 // Render Markdown via Parsedown
  567. 567 require_once $parsedown_path;
  568. 568 $Parsedown = new Parsedown();
  569. 569 if (method_exists($Parsedown, 'setSafeMode')) {
  570. 570 $Parsedown->setSafeMode(true);
  571. 571 if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
  572. 572 }
  573. 573 $rendered = $Parsedown->text($code_input);
  574. 574 $p_content = '<div class="md-body">' . sanitize_allowlist_html($rendered) . '</div>';
  575. 575 } else {
  576. 576 $res = $hl->highlight($langTry, $code_input);
  577. 577 $inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
  578. 578 $p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
  579. 579 }
  580. 580 } else {
  581. 581 // Explicit language requested OR fallback to built-in auto
  582. 582 $langTry = function_exists('paste_normalize_lang') ? paste_normalize_lang($hlId, 'highlight', $hl) : $hlId;
  583. 583 $p_code_source = 'explicit';
  584. 584 try {
  585. 585 $res = $hl->highlight($langTry, $code_input);
  586. 586 } catch (\Throwable $e) {
  587. 587 // Fallbacks: pgsql -> sql, otherwise shared autodetect
  588. 588 if ($langTry === 'pgsql') {
  589. 589 $res = $hl->highlight('sql', $code_input);
  590. 590 } elseif (function_exists('paste_autodetect_language')) {
  591. 591 $det = paste_autodetect_language($code_input, 'highlight', $hl);
  592. 592 $langTry = $det['id'];
  593. 593 $p_code_label = $geshiformats[$langTry] ?? $det['label'];
  594. 594 $p_code_source = $det['source'];
  595. 595 $p_code_explain = $det['explain'] ?? '';
  596. 596 if ($p_code_explain === '') {
  597. 597 $p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $langTry);
  598. 598 }
  599. 599
  600. 600 if ($langTry === 'markdown') {
  601. 601 require_once $parsedown_path;
  602. 602 $Parsedown = new Parsedown();
  603. 603 if (method_exists($Parsedown, 'setSafeMode')) {
  604. 604 $Parsedown->setSafeMode(true);
  605. 605 if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
  606. 606 }
  607. 607 $rendered = $Parsedown->text($code_input);
  608. 608 $p_content = '<div class="md-body">' . sanitize_allowlist_html($rendered) . '</div>';
  609. 609 $p_code_effective = 'markdown';
  610. 610 goto HL_DONE;
  611. 611 } else {
  612. 612 $res = $hl->highlight($langTry, $code_input);
  613. 613 }
  614. 614 } else {
  615. 615 // last resort: hljs auto
  616. 616 $p_code_source = 'hljs';
  617. 617 $res = $hl->highlightAuto($code_input);
  618. 618 $langTry = strtolower((string)($res->language ?? $langTry));
  619. 619 $p_code_explain = paste_build_explain('hljs', $p_title ?? '', $code_input, $langTry ?: 'plaintext');
  620. 620 }
  621. 621 }
  622. 622 $inner = $res->value ?: htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
  623. 623 $p_content = hl_wrap_with_lines($inner, $langTry, $highlight);
  624. 624 $p_code_effective = $langTry;
  625. 625 $p_code_label = $geshiformats[$langTry] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($langTry) : strtoupper($langTry));
  626. 626 }
  627. 627 HL_DONE: ;
  628. 628 } catch (\Throwable $t) {
  629. 629 // Last resort: plain escaped
  630. 630 $esc = htmlspecialchars($code_input, ENT_QUOTES, 'UTF-8');
  631. 631 $p_content = hl_wrap_with_lines($esc, 'plaintext', $highlight);
  632. 632 $p_code_effective = 'plaintext';
  633. 633 $p_code_label = $geshiformats['plaintext'] ?? 'Plain Text';
  634. 634 $p_code_source = $p_code_source ?? 'fallback';
  635. 635 $p_code_explain = $p_code_explain ?: paste_build_explain('fallback', $p_title ?? '', $code_input, 'plaintext');
  636. 636 }
  637. 637
  638. 638 } else {
  639. 639 // ---- GeSHi ----
  640. 640 $use_auto = ($p_code === 'auto' || $p_code === 'autodetect' || $p_code === '' || $p_code === 'text');
  641. 641 $lang_for_geshi = $p_code;
  642. 642
  643. 643 // 1) Filename hint if auto
  644. 644 if ($use_auto) {
  645. 645 $fname = paste_pick_from_filename($p_title ?? '', 'geshi', null);
  646. 646 if ($fname) {
  647. 647 $lang_for_geshi = $fname;
  648. 648 $p_code_effective = $lang_for_geshi;
  649. 649 $p_code_label = $geshiformats[$lang_for_geshi] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($lang_for_geshi) : strtoupper($lang_for_geshi));
  650. 650 $p_code_source = 'filename';
  651. 651 $p_code_explain = paste_build_explain('filename', $p_title ?? '', $code_input, $lang_for_geshi);
  652. 652 }
  653. 653 }
  654. 654 // 2) PHP tag hard rule if auto and not already set by filename
  655. 655 if ($use_auto && empty($p_code_source) && is_probable_php_tag($code_input)) {
  656. 656 $lang_for_geshi = function_exists('paste_normalize_lang') ? paste_normalize_lang('php', 'geshi', null) : 'php';
  657. 657 $p_code_effective = $lang_for_geshi;
  658. 658 $p_code_label = $geshiformats[$lang_for_geshi] ?? 'PHP';
  659. 659 $p_code_source = 'php-tag';
  660. 660 $p_code_explain = paste_build_explain('php-tag', $p_title ?? '', $code_input, $lang_for_geshi);
  661. 661 }
  662. 662
  663. 663 if ($use_auto && empty($p_code_source) && function_exists('paste_autodetect_language')) {
  664. 664 $det = paste_autodetect_language($code_input, 'geshi', null);
  665. 665 $lang_for_geshi = $det['id']; // GeSHi id (mapped)
  666. 666 $p_code_effective = $lang_for_geshi;
  667. 667 $p_code_label = $det['label'];
  668. 668 $p_code_source = $det['source'];
  669. 669 $p_code_explain = $det['explain'] ?? '';
  670. 670 if ($p_code_explain === '') {
  671. 671 $p_code_explain = paste_build_explain($p_code_source, $p_title ?? '', $code_input, $lang_for_geshi);
  672. 672 }
  673. 673 if ($lang_for_geshi === 'markdown') {
  674. 674 // For Markdown, keep Parsedown path for consistent rendering
  675. 675 require_once $parsedown_path;
  676. 676 $Parsedown = new Parsedown();
  677. 677 if (method_exists($Parsedown, 'setSafeMode')) {
  678. 678 $Parsedown->setSafeMode(true);
  679. 679 if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
  680. 680 }
  681. 681 $rendered = $Parsedown->text($code_input);
  682. 682 $p_content = '<div class="md-body">' . sanitize_allowlist_html($rendered) . '</div>';
  683. 683 goto GESHI_DONE;
  684. 684 }
  685. 685 } elseif ($use_auto && empty($p_code_source) && function_exists('paste_probable_markdown') && paste_probable_markdown($code_input)) {
  686. 686 // Minimal fallback
  687. 687 require_once $parsedown_path;
  688. 688 $Parsedown = new Parsedown();
  689. 689 if (method_exists($Parsedown, 'setSafeMode')) {
  690. 690 $Parsedown->setSafeMode(true);
  691. 691 if (method_exists($Parsedown, 'setMarkupEscaped')) $Parsedown->setMarkupEscaped(true);
  692. 692 }
  693. 693 $rendered = $Parsedown->text($code_input);
  694. 694 $p_content = '<div class="md-body">' . sanitize_allowlist_html($rendered) . '</div>';
  695. 695 $p_code_effective = 'markdown';
  696. 696 $p_code_label = 'Markdown';
  697. 697 $p_code_source = 'markdown';
  698. 698 $p_code_explain = "Markdown probability based on headings/lists/links; rendering as Markdown.";
  699. 699 goto GESHI_DONE;
  700. 700 }
  701. 701
  702. 702 $geshi = new GeSHi($code_input, $lang_for_geshi, $path);
  703. 703
  704. 704 // Use classes, not inline CSS; let theme CSS style everything
  705. 705 if (method_exists($geshi, 'enable_classes')) $geshi->enable_classes();
  706. 706 if (method_exists($geshi, 'set_header_type')) $geshi->set_header_type(GESHI_HEADER_DIV);
  707. 707
  708. 708 // Line numbers (NORMAL to avoid rollovers). No inline style.
  709. 709 if (method_exists($geshi, 'enable_line_numbers')) $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
  710. 710 if (!empty($highlight) && method_exists($geshi, 'highlight_lines_extra')) {
  711. 711 $geshi->highlight_lines_extra($highlight);
  712. 712 }
  713. 713
  714. 714 // force plain integer formatting
  715. 715 if (method_exists($geshi, 'set_line_number_format')) {
  716. 716 $geshi->set_line_number_format('%d', 0);
  717. 717 }
  718. 718
  719. 719 // Parse HTML (class-based markup)
  720. 720 $p_content = $geshi->parse_code();
  721. 721
  722. 722 // Add a class to the requested lines so theme CSS can style them
  723. 723 if (!empty($highlight)) {
  724. 724 $p_content = geshi_add_line_highlight_class($p_content, $highlight, 'hljs-hl');
  725. 725 }
  726. 726
  727. 727 // No stylesheet injection here; theme CSS handles it.
  728. 728 $ges_style = '';
  729. 729
  730. 730 // Effective values for UI
  731. 731 $p_code_effective = $lang_for_geshi;
  732. 732 if (!isset($p_code_label)) {
  733. 733 $p_code_label = $geshiformats[$p_code_effective] ?? (function_exists('paste_friendly_label') ? paste_friendly_label($p_code_effective) : strtoupper($p_code_effective));
  734. 734 }
  735. 735
  736. 736 GESHI_DONE: ;
  737. 737 }
  738. 738 }
  739. 739
  740. 740 // ======= New comment submit (PRG) — supports parent_id for replies =======
  741. 741 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_comment') {
  742. 742 if (!$paste_id) {
  743. 743 $comment_error = 'Invalid paste.';
  744. 744 } elseif (!$comments_enabled) {
  745. 745 $comment_error = $lang['comments_off'] ?? 'Comments are disabled.';
  746. 746 } elseif (!$show_comments) {
  747. 747 $comment_error = $lang['comments_blocked_here'] ?? 'Comments are not available for this paste.';
  748. 748 } elseif ($comments_require_login && !isset($_SESSION['username'])) {
  749. 749 $comment_error = $lang['commentlogin'] ?? 'You must be logged in to comment.';
  750. 750 } elseif (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'] ?? '', (string)$_POST['csrf_token'])) {
  751. 751 $comment_error = $lang['invalidtoken'] ?? 'Invalid CSRF token.';
  752. 752 } else {
  753. 753 $uid = 0;
  754. 754 $uname = 'Guest';
  755. 755 if (isset($_SESSION['username'])) {
  756. 756 $uname = (string)$_SESSION['username'];
  757. 757 // fetch id to store as FK
  758. 758 try {
  759. 759 $q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
  760. 760 $q->execute([$uname]);
  761. 761 $uid = (int)($q->fetchColumn() ?: 0);
  762. 762 } catch (Throwable $e) { $uid = 0; }
  763. 763 }
  764. 764
  765. 765 $parent_id = null;
  766. 766 if (isset($_POST['parent_id']) && $_POST['parent_id'] !== '') {
  767. 767 $parent_id = (int)$_POST['parent_id'];
  768. 768 }
  769. 769
  770. 770 // Backward/forward compatible call to addPasteComment()
  771. 771 $okId = null;
  772. 772 try {
  773. 773 if (function_exists('addPasteComment')) {
  774. 774 $rf = new ReflectionFunction('addPasteComment');
  775. 775 if ($rf->getNumberOfParameters() >= 7) {
  776. 776 // Signature with parent_id
  777. 777 $okId = addPasteComment(
  778. 778 $pdo,
  779. 779 (int)$paste_id,
  780. 780 $uid ?: null,
  781. 781 $uname,
  782. 782 $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
  783. 783 (string)($_POST['comment_body'] ?? ''),
  784. 784 $parent_id
  785. 785 );
  786. 786 } else {
  787. 787 // no parent support
  788. 788 $okId = addPasteComment(
  789. 789 $pdo,
  790. 790 (int)$paste_id,
  791. 791 $uid ?: null,
  792. 792 $uname,
  793. 793 $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
  794. 794 (string)($_POST['comment_body'] ?? '')
  795. 795 );
  796. 796 }
  797. 797 }
  798. 798 } catch (Throwable $e) {
  799. 799 error_log('add_comment error: ' . $e->getMessage());
  800. 800 $okId = null;
  801. 801 }
  802. 802
  803. 803 if ($okId) {
  804. 804 // Avoid resubmit on refresh
  805. 805 $to = ($mod_rewrite == '1')
  806. 806 ? $baseurl . $paste_id . '#comments'
  807. 807 : $baseurl . 'paste.php?id=' . (int)$paste_id . '#comments';
  808. 808 header('Location: ' . $to);
  809. 809 exit;
  810. 810 }
  811. 811 $comment_error = 'Could not add comment.';
  812. 812 }
  813. 813 }
  814. 814
  815. 815 // ======= Delete comment (PRG) — hard delete; replies removed via FK CASCADE) =======
  816. 816 // (Allowed even if comments are currently disabled — so users can still remove their content.)
  817. 817 if ($_SERVER['REQUEST_METHOD'] === 'POST'
  818. 818 && isset($_POST['action']) && $_POST['action'] === 'delete_comment') {
  819. 819
  820. 820 $isAjax = (
  821. 821 (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
  822. 822 || (isset($_POST['ajax']) && $_POST['ajax'] === '1')
  823. 823 );
  824. 824
  825. 825 $err = '';
  826. 826 do {
  827. 827 if (empty($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'] ?? '', (string)$_POST['csrf_token'])) {
  828. 828 $err = $lang['invalidtoken'] ?? 'Invalid CSRF token.'; break;
  829. 829 }
  830. 830 if (!isset($_SESSION['username'])) {
  831. 831 $err = $lang['commentlogin'] ?? 'Log in required.'; break;
  832. 832 }
  833. 833
  834. 834 // Ensure current paste context (accept id from GET OR POST for robustness)
  835. 835 if (!$paste_id) {
  836. 836 $paste_id = (isset($_POST['id']) && $_POST['id'] !== '') ? (int)$_POST['id'] : $paste_id;
  837. 837 }
  838. 838 $cid = isset($_POST['comment_id']) ? (int)$_POST['comment_id'] : 0;
  839. 839 if ($cid <= 0 || !$paste_id) { $err = 'Invalid comment.'; break; }
  840. 840
  841. 841 // Resolve current user id
  842. 842 $uid = 0;
  843. 843 try {
  844. 844 $q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
  845. 845 $q->execute([$_SESSION['username']]);
  846. 846 $uid = (int)($q->fetchColumn() ?: 0);
  847. 847 } catch (Throwable $e) {}
  848. 848
  849. 849 if (!userOwnsComment($pdo, $cid, $uid, (string)$_SESSION['username'])) {
  850. 850 $err = $lang['forbidden'] ?? 'Not allowed.'; break;
  851. 851 }
  852. 852
  853. 853 $ok = false;
  854. 854 if (function_exists('deleteComment')) {
  855. 855 $ok = (bool) deleteComment($pdo, $cid);
  856. 856 } else {
  857. 857 // Guard by paste_id to avoid cross-paste deletes
  858. 858 $stmt = $pdo->prepare("DELETE FROM paste_comments WHERE id = ? AND paste_id = ?");
  859. 859 $ok = $stmt->execute([$cid, (int)$paste_id]);
  860. 860 }
  861. 861 if (!$ok) { $err = $lang['error'] ?? 'Delete failed.'; }
  862. 862 } while (false);
  863. 863
  864. 864 if ($isAjax) {
  865. 865 header('Content-Type: application/json; charset=utf-8');
  866. 866 echo json_encode([
  867. 867 'success' => ($err === ''),
  868. 868 'message' => $err,
  869. 869 ]);
  870. 870 exit;
  871. 871 }
  872. 872
  873. 873 // Redirect back to #comments (PRG). If error, carry as c_err=
  874. 874 $to = ($mod_rewrite == '1')
  875. 875 ? $baseurl . (int)$paste_id . '#comments'
  876. 876 : $baseurl . 'paste.php?id=' . (int)$paste_id . '#comments';
  877. 877 if ($err !== '') {
  878. 878 $to .= (strpos($to, '?') === false ? '?' : '&') . 'c_err=' . rawurlencode($err);
  879. 879 }
  880. 880 header('Location: ' . $to);
  881. 881 exit;
  882. 882 }
  883. 883
  884. 884 // header
  885. 885 $theme = 'theme/' . htmlspecialchars($default_theme, ENT_QUOTES, 'UTF-8');
  886. 886 require_once $theme . '/header.php';
  887. 887
  888. 888 // Fetch comments (tree if available), then decorate
  889. 889 if (function_exists('getPasteCommentsTree')) {
  890. 890 $comments = getPasteCommentsTree($pdo, (int)$paste_id);
  891. 891 // decorate recursively
  892. 892 $decorate = function (&$node) use (&$decorate, $pdo) {
  893. 893 $node['body_html'] = render_comment_html((string)($node['body'] ?? ''));
  894. 894 $node['can_delete'] = isset($_SESSION['username']) && isset($_SESSION['csrf_token']) && (function() use ($pdo, $node) {
  895. 895 $uid = 0;
  896. 896 try {
  897. 897 $q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
  898. 898 $q->execute([$_SESSION['username']]);
  899. 899 $uid = (int)($q->fetchColumn() ?: 0);
  900. 900 } catch (Throwable $e) { $uid = 0; }
  901. 901 return userOwnsComment($pdo, (int)$node['id'], $uid, (string)$_SESSION['username']);
  902. 902 })();
  903. 903 if (!empty($node['children'])) {
  904. 904 foreach ($node['children'] as &$ch) $decorate($ch);
  905. 905 unset($ch);
  906. 906 }
  907. 907 };
  908. 908 foreach ($comments as &$c) $decorate($c);
  909. 909 unset($c);
  910. 910 } else {
  911. 911 // flat fetch
  912. 912 $comments = getPasteComments($pdo, (int)$paste_id, 200, 0);
  913. 913 foreach ($comments as &$c) {
  914. 914 $c['body_html'] = render_comment_html((string)$c['body']);
  915. 915 $c['can_delete'] = isset($_SESSION['username']) && isset($_SESSION['csrf_token']) && (function() use ($pdo, $c) {
  916. 916 $uid = 0;
  917. 917 try {
  918. 918 $q = $pdo->prepare("SELECT id FROM users WHERE username = ?");
  919. 919 $q->execute([$_SESSION['username']]);
  920. 920 $uid = (int)($q->fetchColumn() ?: 0);
  921. 921 } catch (Throwable $e) { $uid = 0; }
  922. 922 return userOwnsComment($pdo, (int)$c['id'], $uid, (string)$_SESSION['username']);
  923. 923 })();
  924. 924 }
  925. 925 unset($c);
  926. 926 }
  927. 927
  928. 928 // Carry any error from PRG redirect
  929. 929 if ($comment_error === '' && isset($_GET['c_err']) && $_GET['c_err'] !== '') {
  930. 930 $comment_error = (string)$_GET['c_err'];
  931. 931 }
  932. 932
  933. 933 // view OR password prompt
  934. 934 if ($p_password === "NONE") {
  935. 935 updateMyView($pdo, (int) $paste_id);
  936. 936
  937. 937 $p_download = $mod_rewrite == '1' ? $baseurl . "download/$paste_id" : $baseurl . "paste.php?download&id=$paste_id";
  938. 938 $p_raw = $mod_rewrite == '1' ? $baseurl . "raw/$paste_id" : $baseurl . "paste.php?raw&id=$paste_id";
  939. 939 $p_embed = $mod_rewrite == '1' ? $baseurl . "embed/$paste_id" : $baseurl . "paste.php?embed&id=$paste_id";
  940. 940
  941. 941 require_once $theme . '/view.php';
  942. 942
  943. 943 // View-once (SELF) cleanup after increment
  944. 944 $current_views = getPasteViewCount($pdo, (int) $paste_id);
  945. 945 if ($p_expiry === "SELF" && $current_views >= 2) {
  946. 946 deleteMyPaste($pdo, (int) $paste_id);
  947. 947 }
  948. 948 } else {
  949. 949 // Password-protected flow shows the prompt via errors.php
  950. 950 $require_password = true;
  951. 951
  952. 952 $p_password_input = isset($_POST['mypass'])
  953. 953 ? trim((string) $_POST['mypass'])
  954. 954 : (string) ($_SESSION['p_password'] ?? '');
  955. 955
  956. 956 // Prebuild convenience links that carry the typed password
  957. 957 $p_download = $mod_rewrite == '1'
  958. 958 ? $baseurl . "download/$paste_id?password=" . rawurlencode($p_password_input)
  959. 959 : $baseurl . "paste.php?download&id=$paste_id&password=" . rawurlencode($p_password_input);
  960. 960 $p_raw = $mod_rewrite == '1'
  961. 961 ? $baseurl . "raw/$paste_id?password=" . rawurlencode($p_password_input)
  962. 962 : $baseurl . "paste.php?raw&id=$paste_id&password=" . rawurlencode($p_password_input);
  963. 963 $p_embed = $mod_rewrite == '1'
  964. 964 ? $baseurl . "embed/$paste_id?password=" . rawurlencode($p_password_input)
  965. 965 : $baseurl . "paste.php?embed&id=$paste_id&password=" . rawurlencode($p_password_input);
  966. 966
  967. 967 if ($p_password_input !== '' && password_verify($p_password_input, $p_password)) {
  968. 968 updateMyView($pdo, (int) $paste_id);
  969. 969 require_once $theme . '/view.php';
  970. 970
  971. 971 $current_views = getPasteViewCount($pdo, (int) $paste_id);
  972. 972 if ($p_expiry === "SELF" && $current_views >= 2) {
  973. 973 deleteMyPaste($pdo, (int) $paste_id);
  974. 974 }
  975. 975 } else {
  976. 976 $error = $p_password_input !== ''
  977. 977 ? ($lang['wrongpwd'] ?? 'Incorrect password.')
  978. 978 : ($lang['pwdprotected'] ?? 'This paste is password-protected.');
  979. 979 $_SESSION['p_password'] = $p_password_input;
  980. 980
  981. 981 require_once $theme . '/errors.php'; // partial renders password prompt
  982. 982 }
  983. 983 }
  984. 984
  985. 985 // footer
  986. 986 require_once $theme . '/footer.php';
  987. 987
  988. 988} catch (PDOException $e) {
  989. 989 error_log("paste.php: Database error: " . $e->getMessage());
  990. 990
  991. 991 // Still render a readable error page (no password box)
  992. 992 $error = ($lang['error'] ?? 'Database error.') . ': ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
  993. 993
  994. 994 global $default_theme, $baseurl, $mod_rewrite, $pdo, $require_password;
  995. 995 $require_password = false;
  996. 996
  997. 997 $theme = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
  998. 998 require_once $theme . '/header.php';
  999. 999 require_once $theme . '/errors.php';
  1000. 1000 require_once $theme . '/footer.php';
  1001. 1001}
  1002. 1002?>

Raw Paste

Comments 0
Login to join the discussion
  • No comments yet — be the first!
Login to post a comment. Login
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA), and — with your consent — to measure usage and show ads. See Privacy.