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