Untitled

PHP Detected boxlabs 103 Views Size: 23.14 KB Posted on: Sep 8, 25 @ 7:19 PM
  1. <?php
  2. /*
  3.  * Paste $v3.2 2025/09/07 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.  */
  18. declare(strict_types=1);
  19.  
  20. require_once __DIR__ . '/includes/session.php';
  21. require_once __DIR__ . '/config.php';
  22. require_once __DIR__ . '/includes/functions.php';
  23. // Error handling
  24. paste_enable_themed_errors();
  25.  
  26. // Highlighter bootstrap + language lists
  27. require_once __DIR__ . '/includes/hlbootstrap.php';
  28. require_once __DIR__ . '/includes/list_languages.php';
  29.  
  30. ini_set('display_errors','0');
  31. ini_set('log_errors','1');
  32.  
  33. // Load paste by ID, decrypting as needed. Returns ['title','content','code'] or null
  34. function load_paste(PDO $pdo, int $id): ?array {
  35.     try {
  36.         $st = $pdo->prepare("SELECT id,title,content,code,encrypt FROM pastes WHERE id=? LIMIT 1");
  37.         $st->execute([$id]);
  38.         $r = $st->fetch();
  39.         if (!$r) return null;
  40.  
  41.         $title   = (string)$r['title'];
  42.         $content = (string)$r['content'];
  43.  
  44.         if ((string)$r['encrypt'] === "1" && defined('SECRET')) {
  45.             $title   = decrypt($title,   hex2bin(SECRET))   ?? $title;
  46.             $content = decrypt($content, hex2bin(SECRET))   ?? $content;
  47.         }
  48.  
  49.         // Stored content is HTML-escaped before encrypt; decode for diffing
  50.         $content = html_entity_decode($content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  51.  
  52.         return [
  53.             'title'   => $title,
  54.             'content' => $content,
  55.             'code'    => (string)($r['code'] ?? 'text'),
  56.         ];
  57.     } catch (Throwable $e) {
  58.         error_log("diff.php load_paste($id): ".$e->getMessage());
  59.         return null;
  60.     }
  61. }
  62.  
  63. // Inline (word/char) diff > [leftHTML, rightHTML] with <span class="diff-inside-...">
  64. function inline_diff(string $a, string $b): array {
  65.     $split = static function(string $s): array {
  66.         preg_match_all('/\s+|[^\s]+/u', $s, $m);
  67.         return $m[0] ?: [$s];
  68.     };
  69.     $Aw = $split($a); $Bw = $split($b);
  70.     $useChar = (count($Aw) <= 4 || count($Bw) <= 4);
  71.     if ($useChar) {
  72.         $Aw = preg_split('//u', $a, -1, PREG_SPLIT_NO_EMPTY);
  73.         $Bw = preg_split('//u', $b, -1, PREG_SPLIT_NO_EMPTY);
  74.     }
  75.     $n=count($Aw); $m=count($Bw);
  76.     $L = array_fill(0,$n+1,array_fill(0,$m+1,0));
  77.     for($i=$n-1;$i>=0;$i--) for($j=$m-1;$j>=0;$j--) {
  78.         $L[$i][$j]=($Aw[$i]===$Bw[$j])?($L[$i+1][$j+1]+1):max($L[$i+1][$j],$L[$i][$j+1]);
  79.     }
  80.     $i=0;$j=0;$left='';$right='';
  81.     $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  82.     while($i<$n && $j<$m){
  83.         if($Aw[$i]===$Bw[$j]){ $left.=$esc($Aw[$i]); $right.=$esc($Bw[$j]); $i++; $j++; }
  84.         elseif($L[$i+1][$j] >= $L[$i][$j+1]){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
  85.         else { $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
  86.     }
  87.     while($i<$n){ $left.='<span class="diff-inside-del">'.$esc($Aw[$i]).'</span>'; $i++; }
  88.     while($j<$m){ $right.='<span class="diff-inside-add">'.$esc($Bw[$j]).'</span>'; $j++; }
  89.     return [$left,$right];
  90. }
  91.  
  92. /**
  93.  * Index-based diff at line level > opcodes referencing original arrays.
  94.  * Opcodes:
  95.  *   - ['op'=>'eq','ai'=>i,'bi'=>j]
  96.  *   - ['op'=>'del','ai'=>i]
  97.  *   - ['op'=>'add','bi'=>j]
  98.  *
  99.  *   1) xdiff accelerator (parse unified diff) if available
  100.  *   2) Myers O((N+M)D) fallback in pure PHP
  101.  */
  102. function diff_lines_idx(array $A, array $B, ?callable $normalizer=null): array {
  103.     $An = $normalizer ? array_map($normalizer, $A) : $A;
  104.     $Bn = $normalizer ? array_map($normalizer, $B) : $B;
  105.  
  106.     if (function_exists('xdiff_string_diff')) {
  107.         return _diff_idx_via_xdiff($An, $Bn);
  108.     }
  109.     return _diff_idx_via_myers($An, $Bn);
  110. }
  111.  
  112. /* ---------- Fast path: use xdiff to compute unified diff, parse to opcodes ---------- */
  113. function _diff_idx_via_xdiff(array $A, array $B): array {
  114.     $N = count($A); $M = count($B);
  115.  
  116.     // Join as text; ensure trailing newline to keep line counts exact
  117.     $left  = ($N ? implode("\n", $A) : '') . "\n";
  118.     $right = ($M ? implode("\n", $B) : '') . "\n";
  119.  
  120.     // 0-context unified diff, non-minimal for speed
  121.     $ud = xdiff_string_diff($left, $right, 0, false);
  122.     if ($ud === false) {
  123.         // Fallback to trivial equality handling
  124.         $ops = [];
  125.         $eq = min($N, $M);
  126.         for ($i=0; $i<$eq; $i++) $ops[] = ['op'=>'eq','ai'=>$i,'bi'=>$i];
  127.         for ($i=$eq; $i<$N; $i++) $ops[] = ['op'=>'del','ai'=>$i];
  128.         for ($j=$eq; $j<$M; $j++) $ops[] = ['op'=>'add','bi'=>$j];
  129.         return $ops;
  130.     }
  131.     if ($ud === '') {
  132.         $ops = [];
  133.         for ($i=0; $i<$N && $i<$M; $i++) $ops[]=['op'=>'eq','ai'=>$i,'bi'=>$i];
  134.         for ($i=$M; $i<$N; $i++) $ops[]=['op'=>'del','ai'=>$i];
  135.         for ($j=$N; $j<$M; $j++) $ops[]=['op'=>'add','bi'=>$j];
  136.         return $ops;
  137.     }
  138.  
  139.     $ops = [];
  140.     $ai = 0; $bi = 0;
  141.  
  142.     $lines = preg_split("/\R/u", $ud);
  143.     foreach ($lines as $ln) {
  144.         if ($ln === '' && $ln !== '0') continue;
  145.  
  146.         if (preg_match('/^@@\s+-([0-9]+)(?:,([0-9]+))?\s+\+([0-9]+)(?:,([0-9]+))?\s+@@/', $ln, $m)) {
  147.             $startA = max(0, (int)$m[1] - 1); // 0-based
  148.             $startB = max(0, (int)$m[3] - 1);
  149.             // Emit equal block before this hunk (between current cursors and hunk start)
  150.             $eq = min(max(0, $startA - $ai), max(0, $startB - $bi));
  151.             for ($k=0; $k<$eq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  152.             continue;
  153.         }
  154.  
  155.         $tag = $ln[0] ?? '';
  156.         if     ($tag === ' ') { $ops[] = ['op'=>'eq',  'ai'=>$ai,  'bi'=>$bi];  $ai++; $bi++; }
  157.         elseif ($tag === '-') { $ops[] = ['op'=>'del', 'ai'=>$ai];              $ai++;        }
  158.         elseif ($tag === '+') { $ops[] = ['op'=>'add',              'bi'=>$bi];         $bi++; }
  159.         else {
  160.             // headers '---', '+++', empty, etc > ignore
  161.         }
  162.     }
  163.  
  164.     // Trailing equals after last hunk
  165.     $tailEq = min($N - $ai, $M - $bi);
  166.     for ($k=0; $k<$tailEq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  167.     for (; $ai<$N; $ai++) $ops[] = ['op'=>'del','ai'=>$ai];
  168.     for (; $bi<$M; $bi++) $ops[] = ['op'=>'add','bi'=>$bi];
  169.  
  170.     return $ops;
  171. }
  172.  
  173. /* ---------- Fallback: Myers O((N+M)D) with path reconstruction ---------- */
  174. function _diff_idx_via_myers(array $A, array $B): array {
  175.     $N = count($A); $M = count($B);
  176.  
  177.     if ($N === 0 && $M === 0) return [];
  178.     if ($N === 0) { $ops=[]; for($j=0;$j<$M;$j++) $ops[]=['op'=>'add','bi'=>$j]; return $ops; }
  179.     if ($M === 0) { $ops=[]; for($i=0;$i<$N;$i++) $ops[]=['op'=>'del','ai'=>$i]; return $ops; }
  180.  
  181.     $max = $N + $M;
  182.     $off = $max;
  183.     $V = array_fill(0, 2 * $max + 1, 0);
  184.     $trace = [];
  185.  
  186.     $Dend = 0;
  187.     for ($d = 0; $d <= $max; $d++) {
  188.         $trace[$d] = $V;
  189.  
  190.         for ($k = -$d; $k <= $d; $k += 2) {
  191.             if ($k == -$d || ($k != $d && $V[$off + $k - 1] < $V[$off + $k + 1])) {
  192.                 $x = $V[$off + $k + 1];       // down (insert B)
  193.             } else {
  194.                 $x = $V[$off + $k - 1] + 1;   // right (delete A)
  195.             }
  196.             $y = $x - $k;
  197.  
  198.             while ($x < $N && $y < $M && $A[$x] === $B[$y]) { $x++; $y++; }
  199.  
  200.             $V[$off + $k] = $x;
  201.  
  202.             if ($x >= $N && $y >= $M) {
  203.                 $trace[$d] = $V;
  204.                 $Dend = $d;
  205.                 break 2;
  206.             }
  207.         }
  208.     }
  209.  
  210.     $ops = [];
  211.     $x = $N; $y = $M;
  212.     for ($d = $Dend; $d > 0; $d--) {
  213.         $Vprev = $trace[$d-1];
  214.         $k  = $x - $y;
  215.  
  216.         $down = ($k == -$d) || ($k != $d && $Vprev[$off + $k - 1] < $Vprev[$off + $k + 1]);
  217.         $kPrev   = $down ? $k + 1 : $k - 1;
  218.         $xStart  = $down ? $Vprev[$off + $kPrev] : $Vprev[$off + $kPrev] + 1;
  219.         $yStart  = $xStart - $kPrev;
  220.  
  221.         while ($x > $xStart && $y > $yStart) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
  222.  
  223.         if ($down) {
  224.             $yStart--;
  225.             $ops[] = ['op'=>'add','bi'=>$yStart];
  226.         } else {
  227.             $xStart--;
  228.             $ops[] = ['op'=>'del','ai'=>$xStart];
  229.         }
  230.  
  231.         $x = $xStart; $y = $yStart;
  232.     }
  233.  
  234.     while ($x > 0 && $y > 0) { $x--; $y--; $ops[] = ['op'=>'eq','ai'=>$x,'bi'=>$y]; }
  235.     while ($x > 0) { $x--; $ops[] = ['op'=>'del','ai'=>$x]; }
  236.     while ($y > 0) { $y--; $ops[] = ['op'=>'add','bi'=>$y]; }
  237.  
  238.     return array_reverse($ops);
  239. }
  240.  
  241. // Build side-by-side & unified row arrays from ops (index-based).
  242. function build_tables_idx(array $ops, array $leftLines, array $rightLines): array {
  243.     $side=[]; $uni=[]; $li=1; $ri=1;
  244.     foreach ($ops as $op) {
  245.         if ($op['op']==='eq') {
  246.             $L=(string)($leftLines[$op['ai']] ?? '');
  247.             $R=(string)($rightLines[$op['bi']] ?? '');
  248.             $side[]=['lno'=>$li,'rno'=>$ri,'lclass'=>'ctx','rclass'=>'ctx','lhtml'=>$L,'rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
  249.             $uni[] =['lno'=>$li,'rno'=>$ri,'class'=>'ctx','html'=>$L,'intra'=>false];
  250.             $li++; $ri++;
  251.         } elseif ($op['op']==='del') {
  252.             $L=(string)($leftLines[$op['ai']] ?? '');
  253.             $side[]=['lno'=>$li,'rno'=>'','lclass'=>'del','rclass'=>'empty','lhtml'=>$L,'rhtml'=>'','l_intra'=>false,'r_intra'=>false];
  254.             $uni[] =['lno'=>$li,'rno'=>'','class'=>'del','html'=>$L,'intra'=>false];
  255.             $li++;
  256.         } else { // add
  257.             $R=(string)($rightLines[$op['bi']] ?? '');
  258.             $side[]=['lno'=>'','rno'=>$ri,'lclass'=>'empty','rclass'=>'add','lhtml'=>'','rhtml'=>$R,'l_intra'=>false,'r_intra'=>false];
  259.             $uni[] =['lno'=>'','rno'=>$ri,'class'=>'add','html'=>$R,'intra'=>false];
  260.             $ri++;
  261.         }
  262.     }
  263.     return [$side,$uni];
  264. }
  265.  
  266. // Apply inline word/char diff across adjacent del/add in side-by-side rows.
  267. function apply_inline_sxs(array &$sideRows): void {
  268.     for ($i=0; $i<count($sideRows)-1; $i++) {
  269.         $a=$sideRows[$i]; $b=$sideRows[$i+1];
  270.         if ($a['lclass']==='del' && $b['rclass']==='add') {
  271.             [$L,$R] = inline_diff((string)$a['lhtml'], (string)$b['rhtml']);
  272.             $sideRows[$i]['lhtml']=$L; $sideRows[$i]['l_intra']=true;
  273.             $sideRows[$i+1]['rhtml']=$R; $sideRows[$i+1]['r_intra']=true;
  274.             $i++;
  275.         }
  276.     }
  277. }
  278.  
  279. // Proper unified .diff (xdiff if present; else POSIX-ish fallback)
  280. function unified_diff_download(string $left, string $right, string $nameA='a', string $nameB='b', int $ctx=3): string {
  281.     if (function_exists('xdiff_string_diff')) {
  282.         // Use non-minimal for speed; we rewrite headers below
  283.         $ud = xdiff_string_diff($left, $right, $ctx, false);
  284.         if ($ud !== false) {
  285.             $ts = gmdate('Y-m-d H:i:s O');
  286.             $hdr = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
  287.             $ud = preg_replace('~^--- .*\R\+\+\+ .*\R~', $hdr, $ud, 1);
  288.             return $ud;
  289.         }
  290.     }
  291.  
  292.     // Manual fallback
  293.     $aOrig = preg_split("/\R/u", $left);
  294.     $bOrig = preg_split("/\R/u", $right);
  295.     if ($aOrig === false) $aOrig = [$left];
  296.     if ($bOrig === false) $bOrig = [$right];
  297.  
  298.     $ops = diff_lines_idx($aOrig, $bOrig, null);
  299.  
  300.     $hunks = [];
  301.     $flush = static function (&$hunks, &$buf) {
  302.         if (empty($buf['lines'])) return;
  303.         $oldLen = max(0, $buf['oldLen']);
  304.         $newLen = max(0, $buf['newLen']);
  305.         $h = "@@ -{$buf['oldStart']}" . ($oldLen===1?'':",$oldLen")
  306.            . " +{$buf['newStart']}" . ($newLen===1?'':",$newLen") . " @@\n";
  307.         $h .= implode('', $buf['lines']);
  308.         $hunks[] = $h;
  309.         $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
  310.     };
  311.  
  312.     $buf = ['oldStart'=>0,'newStart'=>0,'oldLen'=>0,'newLen'=>0,'lines'=>[],'open'=>false];
  313.     $ctxAhead = 0;
  314.     $ai=1; $bi=1;
  315.  
  316.     $grab_context = static function($aOrig, $bOrig, $ai, $bi, $ctx) use (&$buf) {
  317.         $startA = max(1, $ai - $ctx);
  318.         $startB = max(1, $bi - $ctx);
  319.         $buf['oldStart'] = $startA;
  320.         $buf['newStart'] = $startB;
  321.         for ($k=0; $k<($ai-$startA); $k++) {
  322.             $buf['lines'][] = ' ' . rtrim((string)$aOrig[$startA-1+$k], "\r") . "\n";
  323.         }
  324.         $buf['oldLen'] += ($ai-$startA);
  325.         $buf['newLen'] += ($bi-$startB);
  326.     };
  327.  
  328.     foreach ($ops as $op) {
  329.         if ($op['op'] === 'eq') {
  330.             if ($buf['open']) {
  331.                 if ($ctxAhead < 3) {
  332.                     $line = rtrim((string)$aOrig[$op['ai']], "\r");
  333.                     $buf['lines'][] = ' ' . $line . "\n";
  334.                     $buf['oldLen']++; $buf['newLen']++; $ctxAhead++;
  335.                 } else {
  336.                     $flush($hunks, $buf);
  337.                     $ctxAhead = 0;
  338.                 }
  339.             }
  340.             $ai++; $bi++;
  341.         } elseif ($op['op'] === 'del') {
  342.             if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
  343.             $line = rtrim((string)$aOrig[$op['ai']], "\r");
  344.             $buf['lines'][] = '-' . $line . "\n";
  345.             $buf['oldLen']++; $ai++;
  346.         } else { // add
  347.             if (!$buf['open']) { $buf['open']=true; $ctxAhead=0; $grab_context($aOrig, $bOrig, $ai, $bi, 3); }
  348.             $line = rtrim((string)$bOrig[$op['bi']], "\r");
  349.             $buf['lines'][] = '+' . $line . "\n";
  350.             $buf['newLen']++; $bi++;
  351.         }
  352.     }
  353.     if ($buf['open']) $flush($hunks, $buf);
  354.  
  355.     $ts = gmdate('Y-m-d H:i:s O');
  356.     $out  = "--- {$nameA}\t{$ts}\n+++ {$nameB}\t{$ts}\n";
  357.     $out .= implode('', $hunks);
  358.     return $out;
  359. }
  360.  
  361. // Render a single line with highlight.php if available (else plain-escaped).
  362. function hl_render_line(string $text, string $lang='text'): string {
  363.     global $highlighter;
  364.     static $hl = null;
  365.  
  366.     $esc = static fn($s)=>htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
  367.  
  368.     if (($highlighter ?? 'geshi') === 'highlight') {
  369.         if ($hl === null) $hl = make_highlighter();
  370.         if ($hl) {
  371.             try {
  372.                 if ($lang && !in_array(strtolower($lang), ['autodetect','text','plaintext'], true)) {
  373.                     $res = $hl->highlight($lang, $text);
  374.                     return '<span class="hljs">'.$res->value.'</span>';
  375.                 }
  376.                 $res = $hl->highlightAuto($text);
  377.                 return '<span class="hljs">'.$res->value.'</span>';
  378.             } catch (Throwable $e) { /* fall through */ }
  379.         }
  380.     }
  381.     return $esc($text);
  382. }
  383.  
  384. /* =========================================================
  385.  * Page inputs
  386.  * =======================================================*/
  387.  
  388. $left  = '';
  389. $right = '';
  390. $leftLabel  = 'Old';
  391. $rightLabel = 'New';
  392.  
  393. /* ---------- Minimal site/bootstrap so header/footer look right ---------- */
  394. $mod_rewrite = $mod_rewrite ?? '0';
  395. $baseurl     = $baseurl     ?? '/';
  396. $site_name   = $site_name   ?? '';
  397. $title       = 'Diff';
  398. $ges_style   = '';          // keep themes CSS-only
  399. $ads_1 = $ads_2 = $text_ads = ''; // optional ad slots
  400.  
  401. try {
  402.     // site_info
  403.     $stmt = $pdo->query("SELECT * FROM site_info WHERE id='1'");
  404.     if ($stmt) {
  405.         $si = $stmt->fetch() ?: [];
  406.         $title     = trim($si['title'] ?? $title);
  407.         $des       = trim($si['des'] ?? '');
  408.         $baseurl   = rtrim(trim($si['baseurl'] ?? $baseurl), '/') . '/';
  409.         $keyword   = trim($si['keyword'] ?? '');
  410.         $site_name = trim($si['site_name'] ?? $site_name);
  411.         $email     = trim($si['email'] ?? '');
  412.         $twit      = trim($si['twit'] ?? '');
  413.         $face      = trim($si['face'] ?? '');
  414.         $gplus     = trim($si['gplus'] ?? '');
  415.         $ga        = trim($si['ga'] ?? '');
  416.         $additional_scripts = trim($si['additional_scripts'] ?? '');
  417.         if (isset($si['mod_rewrite']) && $si['mod_rewrite'] !== '') {
  418.             $mod_rewrite = (string)$si['mod_rewrite'];
  419.         }
  420.     }
  421.  
  422.     // interface
  423.     $stmt = $pdo->query("SELECT * FROM interface WHERE id='1'");
  424.     if ($stmt) {
  425.         $iface = $stmt->fetch() ?: [];
  426.         $default_lang  = trim($iface['lang']  ?? 'en.php');
  427.         $default_theme = trim($iface['theme'] ?? 'default');
  428.         if (is_file(__DIR__ . "/langs/$default_lang")) {
  429.             require_once __DIR__ . "/langs/$default_lang";
  430.         }
  431.     } else {
  432.         $default_theme = $default_theme ?? 'default';
  433.     }
  434.  
  435.     // permissions
  436.     $stmt = $pdo->query("SELECT * FROM site_permissions WHERE id='1'");
  437.     if ($stmt) {
  438.         $perm = $stmt->fetch() ?: [];
  439.         $disableguest = trim($perm['disableguest'] ?? 'off');
  440.         $siteprivate  = trim($perm['siteprivate']  ?? 'off');
  441.     }
  442.  
  443.     // ads (optional)
  444.     $stmt = $pdo->query("SELECT * FROM ads WHERE id='1'");
  445.     if ($stmt) {
  446.         $ads  = $stmt->fetch() ?: [];
  447.         $text_ads = trim($ads['text_ads'] ?? '');
  448.         $ads_1    = trim($ads['ads_1'] ?? '');
  449.         $ads_2    = trim($ads['ads_2'] ?? '');
  450.     }
  451. } catch (Throwable $e) {
  452.     // keep sane defaults, but don't break the page
  453.     error_log('diff.php bootstrap: ' . $e->getMessage());
  454.     $default_theme = $default_theme ?? 'default';
  455. }
  456.  
  457. /* ---------- Paste IDs from query (supports ?a & ?b) ---------- */
  458. $lid = isset($_GET['a']) ? (int)$_GET['a'] : (isset($_GET['left_id']) ? (int)$_GET['left_id'] : 0);
  459. $rid = isset($_GET['b']) ? (int)$_GET['b'] : (isset($_GET['right_id']) ? (int)$_GET['right_id'] : 0);
  460.  
  461. if ($lid) { $p = load_paste($pdo, $lid); if ($p){ $left=$p['content'];  $leftLabel='Paste #'.$lid; } }
  462. if ($rid) { $p = load_paste($pdo, $rid); if ($p){ $right=$p['content']; $rightLabel='Paste #'.$rid; } }
  463.  
  464. /* ---------- POST inputs (compare / download keeps buffers) ---------- */
  465. if ($_SERVER['REQUEST_METHOD']==='POST') {
  466.     $left       = (string)($_POST['left_text']  ?? $left);
  467.     $right      = (string)($_POST['right_text'] ?? $right);
  468.     $leftLabel  = trim((string)($_POST['left_label']  ?? $leftLabel))  ?: $leftLabel;
  469.     $rightLabel = trim((string)($_POST['right_label'] ?? $rightLabel)) ?: $rightLabel;
  470. }
  471.  
  472. /* ---------- Language engine + maps ---------- */
  473. $engine = function_exists('paste_current_engine') ? paste_current_engine() : ($highlighter ?? 'geshi');
  474.  
  475. if ($engine === 'highlight') {
  476.     $langs         = highlight_supported_languages();
  477.     $language_map  = highlight_language_map($langs);
  478.     $alias_map     = highlight_alias_map($langs);
  479.     $popular_langs = paste_popular_formats_highlight();
  480. } else {
  481.     $language_map  = geshi_language_map();
  482.     $alias_map     = geshi_alias_map($language_map);
  483.     $popular_langs = paste_popular_formats_geshi();
  484. }
  485.  
  486. /* ---------- Picked languages ---------- */
  487. $lang_left  = strtolower((string)($_POST['left_lang']  ?? $_GET['left_lang']  ?? 'autodetect'));
  488. $lang_right = strtolower((string)($_POST['right_lang'] ?? $_GET['right_lang'] ?? 'autodetect'));
  489. $lang_left  = $alias_map[$lang_left]  ?? 'autodetect';
  490. $lang_right = $alias_map[$lang_right] ?? 'autodetect';
  491.  
  492. $lang_left_label  = $language_map[$lang_left]  ?? ucfirst($lang_left);
  493. $lang_right_label = $language_map[$lang_right] ?? ucfirst($lang_right);
  494.  
  495. /* ---------- highlight.php default style (no picker) ---------- */
  496. if (($highlighter ?? 'geshi') === 'highlight') {
  497.     $hl_style = $hl_style ?? 'hybrid.css'; // header.php reads this
  498. }
  499.  
  500. /* ---------- View options ---------- */
  501. $view_mode = ($_GET['view'] ?? 'side') === 'unified' ? 'unified' : 'side';
  502. $wrap      = isset($_GET['wrap'])      ? (int)$_GET['wrap']      : 0;
  503. $lineno    = isset($_GET['lineno'])    ? (int)$_GET['lineno']    : 1;
  504.  
  505. /* ---------- Ignore trailing whitespace (toggle via ?ignore_ws=1) ---------- */
  506. $ignore_ws = isset($_GET['ignore_ws']) ? (int)$_GET['ignore_ws'] : 0;
  507. $normalizer = $ignore_ws ? static fn($s) => rtrim($s, " \t") : null;
  508.  
  509. /* ---------- Persisted split percentage ---------- */
  510. $split_pct = 50.0;
  511. if (isset($_POST['split_pct'])) {
  512.     $split_pct = (float)$_POST['split_pct'];
  513. } elseif (isset($_COOKIE['diffSplitPct'])) {
  514.     $split_pct = (float)$_COOKIE['diffSplitPct'];
  515. }
  516. $split_pct = max(20.0, min(80.0, (float)$split_pct));
  517. setcookie('diffSplitPct', (string)$split_pct, [
  518.     'expires'  => time() + 60*60*24*30,
  519.     'path'     => '/',
  520.     'secure'   => !empty($_SERVER['HTTPS']),
  521.     'httponly' => false,
  522.     'samesite' => 'Lax',
  523. ]);
  524.  
  525. /* ---------- Download unified diff ---------- */
  526. if (isset($_GET['download']) && $_GET['download'] === '1') {
  527.     $nameA = $leftLabel  ?: 'Old';
  528.     $nameB = $rightLabel ?: 'New';
  529.     $ud = unified_diff_download($left, $right, $nameA, $nameB, 3);
  530.     // Surface engine in headers for debug
  531.     header('X-Diff-Engine: '.(function_exists('xdiff_string_diff') ? 'xdiff' : 'myers'));
  532.     header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
  533.     header('Content-Type: text/x-diff; charset=utf-8');
  534.     header('Content-Disposition: attachment; filename="paste.diff"');
  535.     echo $ud;
  536.     exit;
  537. }
  538.  
  539. /* ---------- Compute opcodes ---------- */
  540. $leftLines  = preg_split("/\R/u", $left);
  541. $rightLines = preg_split("/\R/u", $right);
  542. if ($leftLines === false)  $leftLines  = [$left];
  543. if ($rightLines === false) $rightLines = [$right];
  544.  
  545. $ops = diff_lines_idx($leftLines, $rightLines, $normalizer);
  546.  
  547. /* ---------- Build tables server-side ---------- */
  548. [$sideRows, $uniRows] = build_tables_idx($ops, $leftLines, $rightLines);
  549.  
  550. /* ---------- Limit expensive inline diff pass for very large diffs ---------- */
  551. $perform_inline = true;
  552. $totalBytes = strlen($left) + strlen($right);
  553. if (count($sideRows) > 4000 || $totalBytes > 4*1024*1024) {
  554.     $perform_inline = false;
  555. }
  556. if ($perform_inline) {
  557.     apply_inline_sxs($sideRows);
  558. }
  559.  
  560. /* ---------- Expose engine + toggle info to theme and headers ---------- */
  561. $engine_is_xdiff = function_exists('xdiff_string_diff');
  562. $engine_label    = $engine_is_xdiff ? 'xdiff' : 'myers';
  563. header('X-Diff-Engine: '.$engine_label);
  564. header('X-Diff-Ignore-WS: '.($ignore_ws ? '1':'0'));
  565.  
  566. // Convenience strings the theme can show in the toolbar:
  567. $diff_engine_badge = '<span class="badge bg-secondary" title="Diff engine">'.$engine_label.'</span>';
  568. $ignore_ws_on      = (bool)$ignore_ws;
  569.  
  570. // Build toggle URL for ignore_ws (preserve other query params)
  571. $qs = $_GET;
  572. $qs['ignore_ws'] = $ignore_ws ? 0 : 1;
  573. $ignore_ws_toggle_url = strtok($_SERVER['REQUEST_URI'], '?') . '?' . http_build_query($qs);
  574.  
  575. /* ---------- Render theme ---------- */
  576. $themeDir = 'theme/' . htmlspecialchars($default_theme ?? 'default', ENT_QUOTES, 'UTF-8');
  577.  
  578. // expose split pct to the view if needed by JS
  579. $GLOBALS['split_pct'] = $split_pct;
  580.  
  581. // Also expose new goodies for the toolbar (the theme may choose to use them)
  582. $GLOBALS['diff_engine_badge']  = $diff_engine_badge;
  583. $GLOBALS['ignore_ws_on']       = $ignore_ws_on;
  584. $GLOBALS['ignore_ws_toggle']   = $ignore_ws_toggle_url;
  585.  
  586. require_once $themeDir . '/header.php';
  587. require_once $themeDir . '/diff.php';
  588. require_once $themeDir . '/footer.php';

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
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 or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.