/ www / html / programs / wwwdiff /
/www/html/programs/wwwdiff/index.php
1 <?php
2 /* Copyright (C) 2012, 2018 The Meme Factory, Inc. http://www.meme.com/
3 * Copyright (C) 2006, 2007, 2008, 2011, Karl O. Pinc <kop@meme.com>
4 *
5 * This file is part of Gombe-MI.
6 *
7 * Gombe-MI is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Gombe-MI. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * Web interface for (w)diff
21 *
22 * Bugs:
23 * Does not detect bad return codes from cmp.
24 *
25 * Karl O. Pinc <kop@meme.com>
26 */
27
28 // Includes
29 require('constants.php.inc');
30 require('lib.php.inc');
31
32 // Constants
33
34 // dtype values -- what sort of diff to do.
35 define('D_TABLE', 'table');
36 define('D_WORD', 'word');
37 define('D_LINE', 'line');
38 define('D_FILE', 'file');
39
40 define('DTYPE_DEFAULT', D_TABLE); // default $dtype value
41
42 // Constants for converting wdiff markup to html
43 define('DELETED_MARKUP', '<em class="deleted">');
44 define('ADDED_MARKUP', '<em class="added">');
45 define('CLOSE_MARKUP', '</em>');
46 define('WD_DELETED_PATTERN', '^\[-.*-]$');
47 define('WD_ADDED_PATTERN', '^\{\+.*\+\}$');
48 define('WD_MARKUP_LEN', '4'); // Total length of wdiff markup (e.g. {++})
49 define('WD_DELETED_START', '[-');
50 define('WD_DELETED_END', '-]');
51 define('WD_ADDED_START', '{+');
52 define('WD_ADDED_END', '+}');
53 define('WD_DELETED_START_LEN', '2');
54 define('WD_DELETED_END_LEN', '2');
55 define('WD_ADDED_START_LEN', '2');
56 define('WD_ADDED_END_LEN', '2');
57 $oldmarks = array(WD_DELETED_START
58 , WD_DELETED_END
59 , WD_ADDED_START
60 , WD_ADDED_END);
61 $newmarks = array( DELETED_MARKUP
62 , CLOSE_MARKUP
63 , ADDED_MARKUP
64 , CLOSE_MARKUP);
65
66 // Some constants for table diffs
67 define('DF_SAME', 's');
68 define('DF_DELETED', 'd');
69 define('DF_ADDED', 'a');
70
71 //
72 // Functions.
73 //
74
75 function move_diff_file($dtype, $from_path, $to_path) {
76 /*
77 * Move a file we will diff, converting eol sequences if necessary.
78 *
79 * Input:
80 * $dtype Type of diff we will do
81 * $from_path Pathname to move.
82 * $to_path Place to move to.
83 *
84 * Returns: True on success, false otherwise.
85 *
86 * Side Effects: moves file, fiddles eol
87 *
88 * Bugs:
89 */
90
91 if ($dtype == D_FILE) {
92 return move_uploaded_file($from_path, $to_path);
93 } else {
94 if (!is_uploaded_file($from_path)) {
95 return FALSE;
96 }
97 return move_and_fix_eol($from_path, $to_path) == 0;
98 }
99 }
100
101 function textual($f, $dtype, $letter) {
102 /*
103 * See if a file is ASCII text.
104 *
105 * Input:
106 * $f Path to file.
107 * $dtype Type of comparsion being done.
108 * $letter Letter identifying file A or B.
109 *
110 * Returns: True when file is text.
111 *
112 * Side Effects: Outputs html when non-textual.
113 *
114 * Bugs: It's probably a bad idea to have side effects.
115 */
116
117 exec("file -b $f", $result, $err);
118 reset($result);
119 $line = current($result);
120
121 if (preg_match('/( |^)[Tt]ext(,?|$)/', $line) != 1) {
122 print '<p class="error"> File ' . $letter . ' cannot be compared by ';
123 switch ($dtype) {
124 case D_TABLE:
125 case D_WORD:
126 print 'word';
127 break;
128
129 case D_LINE:
130 print 'line';
131 break;
132 };
133 print " because it is not text. File $letter is: ";
134 print htmlentities($line, ENT_QUOTES);
135 print '</p>';
136
137 return FALSE;
138 };
139
140 return TRUE;
141 };
142
143 function compare_files($dtype) {
144 /*
145 * Compare two files (if any have been uploaded).
146 *
147 * Input:
148 * $dtype The type of comparison to do.
149 *
150 * Returns: TRUE when there is HTML output, FALSE otherwise.
151 *
152 * Side Effects: Outputs html.
153 *
154 * Bugs:
155 */
156
157 global $newmarks, $oldmarks;
158
159 //
160 // Get the rest of the form data.
161 //
162
163 // Filea
164 if (array_key_exists('filea', $_FILES)) {
165 $filea = htmlentities($_FILES['filea']['name'], ENT_QUOTES);
166 $filea_tmp = $_FILES['filea']['tmp_name'];
167 $filea_err = $_FILES['filea']['error'];
168 } else {
169 $filea = '';
170 $filea_tmp = '';
171 }
172
173 // Fileb
174 if (array_key_exists('fileb', $_FILES)) {
175 $fileb = htmlentities($_FILES['fileb']['name'], ENT_QUOTES);
176 $fileb_tmp = $_FILES['fileb']['tmp_name'];
177 $fileb_err = $_FILES['fileb']['error'];
178 } else {
179 $fileb = '';
180 $fileb_tmp = '';
181 }
182
183 //
184 // Set about comparing the files.
185 //
186
187 if ($filea_tmp == '' && $fileb_tmp == '') {
188 // We've not yet gotten uploads from the user so we
189 // don't need to compare anything.
190 return FALSE;
191 }
192
193 if ($filea_err != UPLOAD_ERR_OK
194 || $fileb_err != UPLOAD_ERR_OK) {
195 // Some sort of error uploading.
196 print explain_error("File A [Old File] ($filea)", $filea_err);
197 print explain_error("File B [New File] ($fileb)", $fileb_err);
198 return TRUE;
199 }
200
201 //
202 // Process input values
203 //
204 switch ($dtype) {
205 case D_TABLE:
206 $command = 'wdiff -n';
207 break;
208
209 case D_WORD:
210 $command = 'wdiff -n';
211 break;
212
213 case D_LINE:
214 $command = 'diff -u';
215 break;
216
217 case D_FILE:
218 $command = 'cmp';
219 break;
220 }
221
222 // Compare the two files.
223 $tmpa = '/var/tmp/FileA.' . getmypid();
224 $tmpb = '/var/tmp/FileB.' . getmypid();
225 if (!move_diff_file($dtype, $filea_tmp, $tmpa)
226 || !move_diff_file($dtype, $fileb_tmp, $tmpb)) {
227 print '<p class="error">Unexpected failure trying to move uploaded files.</p>';
228 return TRUE;
229 }
230
231 print '<p>';
232 print "File A [Old File]: " . $filea . "<br />";
233 print "File B [New File]: " . $fileb;
234 print '</p>';
235
236 if ($dtype == D_TABLE || $dtype == D_WORD || $dtype == D_LINE) {
237 // Make sure the files are text and can be compared.
238 $aistext = textual($tmpa, $dtype, 'A');
239 $bistext = textual($tmpb, $dtype, 'B');
240 if (!($aistext && $bistext)) {
241 return TRUE;
242 };
243 };
244
245 $command = "$command $tmpa $tmpb";
246 if ($dtype == D_FILE) {
247 $command .= ' 2>&1';
248 }
249 exec($command, $result, $err);
250 unlink($tmpa);
251 unlink($tmpb);
252
253 if ($err == 0) {
254
255 // The files are identical.
256
257 if (trim($filea) == trim($fileb)) {
258 // File names are identical too!
259 print '<p><em class="error">Uh, the same file was uploaded twice....</em><br />';
260 print '(Or you uploaded 2 files having identical names <em>and</em> content.)</p>';
261 print '<hr />';
262 } else {
263 print '<p class="success">The files are identical!';
264 print '<em class="remark">(You should be so proud!)</em></p>';
265 }
266
267 return TRUE;
268 };
269
270 // The files are not identical.
271
272 //
273 // Display the result
274 //
275 switch ($dtype) {
276 case D_FILE:
277 reset($result);
278 $line = substr(current($result), 5); // Skip over "cmp: "
279 print '<p class="error">';
280 print htmlentities($line);
281 print '</p>';
282
283 break;
284
285 case D_TABLE:
286 // Tabular by word output
287
288 /* Debug
289 print '<hr />';
290 print '<pre>';
291 foreach ($result as $line) {
292 print "$line\n";
293 }
294 print '</pre>';
295 reset($result);
296 */ // Debug
297
298 print '<hr />';
299
300 // Go through the result and display it line by line
301 // in a fixed-width font, and colorize it.
302 print "<table class=\"comparison\">\n";
303
304 $lineno = 1;
305 foreach ($result as $line) {
306 // Start a new row in the table.
307 print '<tr><td>Line'
308 . str_repeat('&nbsp;', 5 - strlen($lineno))
309 . "$lineno</td>";
310
311 if ($line != '') {
312 $td_close = '';
313 $words = preg_split('/[[:space:]]/'
314 , htmlentities($line, ENT_QUOTES));
315
316 // Go through the words of the line and pair
317 // the changes.
318 $paired_words = array();
319 $next_pwi = 0; // pwi is "paired_words index"
320 $max_pwi = $next_pwi;
321 $wtype = DF_SAME;
322 $next_wtype = $wtype;
323 foreach ($words as $word) {
324 // Look at the word and see if it begins
325 // an add or delete sequence.
326 // Strip off beginning of sequence marks.
327 $prefix = substr($word, 0, WD_DELETED_START_LEN);
328 if ($prefix == WD_DELETED_START) {
329 // Start of delete sequence.
330 $next_wtype = DF_DELETED;
331 $word = substr($word, WD_DELETED_START_LEN);
332 $pwi_saved = $next_pwi;
333 } elseif ($prefix == WD_ADDED_START) {
334 // Start of add sequence.
335 if ($wtype == DF_DELETED) {
336 // Add sequence follows delete sequence.
337 // Rewind to pair adds with deletes.
338 $next_pwi = $pwi_saved;
339 }
340 $word = substr($word, WD_ADDED_START_LEN);
341 $next_wtype = DF_ADDED;
342 }
343
344 // Remember characteristics of current word and
345 // prep for next iteration.
346 // We do this so early because when the word
347 // ends an add or delete sequence that affects
348 // both the markup of of the next word and it's
349 // column.
350 $pwi = $next_pwi;
351 $wtype = $next_wtype;
352
353 $next_pwi++;
354 if ($next_pwi > $max_pwi) {
355 // Remember how far we got before the adds
356 // "rewound" back over deletes.
357 $max_pwi = $next_pwi;
358 }
359
360 // See if the word ends an add or delete sequence.
361 // Remember seqeuence for future.
362 // Strip off end of sequence marks.
363 $suffix = substr($word, strlen($word)
364 - WD_DELETED_END_LEN);
365 if ($suffix == WD_DELETED_END) {
366 // Finish delete sequence.
367 $word = substr($word, 0, strlen($word)
368 - WD_DELETED_END_LEN);
369 $next_wtype = DF_SAME;
370 } elseif ($suffix == WD_ADDED_END) {
371 // Finish add sequence.
372 $word = substr($word
373 , 0
374 , strlen($word)
375 - WD_ADDED_END_LEN);
376 $next_wtype = DF_SAME;
377 // Make sure that ending an add resynronizes
378 // and regular data is not paired with deletes.
379 $next_pwi = $max_pwi;
380 }
381
382 // Put html markup on the word.
383 if ($wtype == DF_DELETED) {
384 // In a delete sequence.
385 $word = DELETED_MARKUP . $word . CLOSE_MARKUP;
386 } elseif ($wtype == DF_ADDED) {
387 // In a add sequence.
388 $word = ADDED_MARKUP . $word . CLOSE_MARKUP;
389 }
390
391 // Save the word.
392 if (array_key_exists($pwi, $paired_words)) {
393 // There's already something in the pair.
394 $paired_words[$pwi] .= " $word";
395 } else {
396 $paired_words[$pwi] = $word;
397 }
398 }
399
400 // Output the line.
401 foreach ($paired_words as $td) {
402 print "<td class=\"comparison\">$td</td>\n";
403 }
404 }
405 print "</tr>\n";
406 $lineno++;
407 }
408 print "</table>\n";
409
410 break;
411
412 case D_WORD:
413
414 print '<hr />';
415
416 print '<p class="comparison">';
417
418 $lineno = 1;
419 foreach ($result as $line) {
420 print str_repeat('&nbsp;', 5 - strlen($lineno))
421 . $lineno
422 . ': '
423 . str_replace($oldmarks
424 , $newmarks
425 , htmlentities($line, ENT_QUOTES))
426 . '<br />';
427 print "\n";
428 $lineno++;
429 }
430 print '</p>';
431
432 break;
433
434 case D_LINE:
435
436 print '<hr />';
437
438 print '<pre>';
439 $colored = FALSE;
440
441 foreach ($result as $line) {
442 $c = substr($line, 0, 1);
443 switch ($c) {
444 case '+':
445 print '</pre><pre class="added">';
446 $colored = TRUE;
447 break;
448 case '-':
449 print '</pre><pre class="deleted">';
450 $colored = TRUE;
451 break;
452 default:
453 if ($colored) {
454 print '</pre><pre>';
455 $colored = FALSE;
456 }
457 break;
458 }
459 print htmlentities($line, ENT_QUOTES);
460 print "\n";
461 }
462 print '</pre>';
463
464 break;
465 }
466
467 if ($dtype != D_FILE) {
468 ?>
469 <p>Color Key:<br />
470 <em class="deleted">In File A [Old File] but not in File B.</em><br />
471 <em class="added">In File B [New File] but not in File A.</em><br />
472 </p>
473 <?php
474 }
475
476 // break;
477 //
478 // default:
479 // Some error running wdiff.
480 // print '<p class="error">Unexpected failure in wdiff command. Error: '
481 // . $err
482 // . '</p>';
483 //
484 // print '</p>';
485
486 return TRUE;
487 };
488
489 //
490 // Body of code
491 //
492
493 ?><?xml version="1.0" encoding="UTF-8" standalone="no"?>
494 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
495 <html xmlns="http://www.w3.org/1999/xhtml">
496 <head>
497 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
498 <title>wwwdiff -- A web based diff program</title>
499 <link rel="stylesheet" href="/gombemi.css" type="text/css" />
500 </head>
501 <body>
502
503 <p class="navbar">
504 <a href="<?php print HOME_TARGET; ?>">HOME</a>
505 </p>
506
507 <h1>Compare Two Files</h1>
508
509 <?php
510
511 // Get some of the form data.
512
513 // Type of diff
514 if (array_key_exists('dtype', $_REQUEST)) {
515 $dtype = $_REQUEST['dtype'];
516 } else {
517 $dtype = DTYPE_DEFAULT;
518 }
519
520 // Compare the files (assuming anything was uploaded).
521 if (compare_files($dtype)) {
522 print '<hr />';
523 };
524
525 //
526 // Ouput a form where the user can upload files to compare.
527 ?>
528
529 <form action="" enctype="multipart/form-data" method="post">
530
531 <p>
532 <label for="filea_id">File A [Old File]</label>
533 <input name="filea"
534 tabindex="1"
535 id="filea_id"
536 type="file"
537 size="30" />
538 </p>
539
540 <p>
541 <label for="fileb_id">File B [New File]</label>
542 <input name="fileb"
543 tabindex="2"
544 id="fileb_id"
545 type="file"
546 size="30" />
547 </p>
548
549 <?php
550 // The kind of diff to do.
551 ?>
552
553 <p>
554 <input name="dtype"
555 tabindex="10"
556 id="dtype-table"
557 type="radio"
558 value="<?php print D_TABLE; ?>"
559 <?php if ($dtype == D_TABLE) {
560 print 'checked="checked"'; } ?>
561 />&nbsp;<label for="dtype-table">Tabular by Word</label><br />
562
563 <input name="dtype"
564 tabindex="11"
565 id="dtype-word"
566 type="radio"
567 value="<?php print D_WORD; ?>"
568 <?php if ($dtype == D_WORD) {
569 print 'checked="checked"'; } ?>
570 />&nbsp;<label for="dtype-word">By Word</label><br />
571
572 <input name="dtype"
573 tabindex="12"
574 id="dtype-line"
575 type="radio"
576 value="<?php print D_LINE; ?>"
577 <?php if ($dtype == D_LINE) {
578 print 'checked="checked"'; } ?>
579 />&nbsp;<label for="dtype-line">By Line</label><br />
580
581 <input name="dtype"
582 tabindex="13"
583 id="dtype-file"
584 type="radio"
585 value="<?php print D_FILE; ?>"
586 <?php if ($dtype == D_FILE) {
587 print 'checked="checked"'; } ?>
588 />&nbsp;<label for="dtype-file">By File</label>
589 </p>
590
591 <p>
592 <input value="Diff" tabindex="20" type="submit" />
593 </p>
594 </form>
595
596
597
598 <p class="navbar">
599 <a href="<?php print HOME_TARGET; ?>">HOME</a>
600 </p>
601
602 </body>
603 </html>