1<?php 2/** 3 * Coppermine Photo Gallery 4 * 5 * v1.0 originally written by Gregory Demar 6 * 7 * @copyright Copyright (c) 2003-2020 Coppermine Dev Team 8 * @license GNU General Public License version 3 or later; see LICENSE 9 * 10 * include/archive.php 11 * @since 1.6.09 12 */ 13 14/*-------------------------------------------------- 15 | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.1 16 | By Devin Doucette 17 | Copyright (c) 2005 Devin Doucette 18 | Email: darksnoopy@shaw.ca 19 +-------------------------------------------------- 20 | Email bugs/suggestions to darksnoopy@shaw.ca 21 +-------------------------------------------------- 22 | This script has been created and released under 23 | the GNU GPL and is free to use and redistribute 24 | only if this copyright statement is not removed 25 +--------------------------------------------------*/ 26 27class archive 28{ 29 function __construct($name) 30 { 31 $this->options = array ( 32 'basedir' => '.', 33 'name' => $name, 34 'prepend' => '', 35 'inmemory' => 0, 36 'overwrite' => 0, 37 'recurse' => 1, 38 'storepaths' => 1, 39 'followlinks' => 0, 40 'level' => 3, 41 'method' => 1, 42 'sfx' => '', 43 'type' => '', 44 'comment' => '' 45 ); 46 $this->files = array (); 47 $this->exclude = array (); 48 $this->storeonly = array (); 49 $this->error = array (); 50 } 51 52 function set_options($options) 53 { 54 foreach ($options as $key => $value) 55 $this->options[$key] = $value; 56 if (!empty ($this->options['basedir'])) 57 { 58 $this->options['basedir'] = str_replace('\\', '/', $this->options['basedir']); 59 $this->options['basedir'] = preg_replace('/\/+/', '/', $this->options['basedir']); 60 $this->options['basedir'] = preg_replace('/\/$/', '', $this->options['basedir']); 61 } 62 if (!empty ($this->options['name'])) 63 { 64 $this->options['name'] = str_replace('\\', '/', $this->options['name']); 65 $this->options['name'] = preg_replace('/\/+/', '/', $this->options['name']); 66 } 67 if (!empty ($this->options['prepend'])) 68 { 69 $this->options['prepend'] = str_replace('\\', '/', $this->options['prepend']); 70 $this->options['prepend'] = preg_replace('/^(\.*\/+)+/', '', $this->options['prepend']); 71 $this->options['prepend'] = preg_replace('/\/+/', '/', $this->options['prepend']); 72 $this->options['prepend'] = preg_replace('/\/$/', '', $this->options['prepend']) . '/'; 73 } 74 } 75 76 function create_archive() 77 { 78 $this->make_list(); 79 80 if ($this->options['inmemory'] == 0) 81 { 82 $pwd = getcwd(); 83 chdir($this->options['basedir']); 84 if ($this->options['overwrite'] == 0 && file_exists($this->options['name'] . ($this->options['type'] == 'gzip' || $this->options['type'] == 'bzip' ? '.tmp' : ''))) 85 { 86 $this->error[] = "File {$this->options['name']} already exists."; 87 chdir($pwd); 88 return 0; 89 } 90 else if ($this->archive = @fopen($this->options['name'] . ($this->options['type'] == 'gzip' || $this->options['type'] == 'bzip' ? '.tmp' : ''), 'wb+')) 91 chdir($pwd); 92 else 93 { 94 $this->error[] = "Could not open {$this->options['name']} for writing."; 95 chdir($pwd); 96 return 0; 97 } 98 } 99 else 100 $this->archive = ''; 101 102 switch ($this->options['type']) 103 { 104 case 'zip': 105 if (!$this->create_zip()) 106 { 107 $this->error[] = 'Could not create zip file.'; 108 return 0; 109 } 110 break; 111 case 'bzip': 112 if (!$this->create_tar()) 113 { 114 $this->error[] = 'Could not create tar file.'; 115 return 0; 116 } 117 if (!$this->create_bzip()) 118 { 119 $this->error[] = 'Could not create bzip2 file.'; 120 return 0; 121 } 122 break; 123 case 'gzip': 124 if (!$this->create_tar()) 125 { 126 $this->error[] = 'Could not create tar file.'; 127 return 0; 128 } 129 if (!$this->create_gzip()) 130 { 131 $this->error[] = 'Could not create gzip file.'; 132 return 0; 133 } 134 break; 135 case 'tar': 136 if (!$this->create_tar()) 137 { 138 $this->error[] = 'Could not create tar file.'; 139 return 0; 140 } 141 } 142 143 if ($this->options['inmemory'] == 0) 144 { 145 fclose($this->archive); 146 if ($this->options['type'] == 'gzip' || $this->options['type'] == 'bzip') 147 unlink($this->options['basedir'] . '/' . $this->options['name'] . '.tmp'); 148 } 149 } 150 151 function add_data($data) 152 { 153 if ($this->options['inmemory'] == 0) 154 fwrite($this->archive, $data); 155 else 156 $this->archive .= $data; 157 } 158 159 function make_list() 160 { 161 if (!empty ($this->exclude)) 162 foreach ($this->files as $key => $value) 163 foreach ($this->exclude as $current) 164 if ($value['name'] == $current['name']) 165 unset ($this->files[$key]); 166 if (!empty ($this->storeonly)) 167 foreach ($this->files as $key => $value) 168 foreach ($this->storeonly as $current) 169 if ($value['name'] == $current['name']) 170 $this->files[$key]['method'] = 0; 171 unset ($this->exclude, $this->storeonly); 172 } 173 174 function add_files($list) 175 { 176 $temp = $this->list_files($list); 177 foreach ($temp as $current) 178 $this->files[] = $current; 179 } 180 181 function exclude_files($list) 182 { 183 $temp = $this->list_files($list); 184 foreach ($temp as $current) 185 $this->exclude[] = $current; 186 } 187 188 function store_files($list) 189 { 190 $temp = $this->list_files($list); 191 foreach ($temp as $current) 192 $this->storeonly[] = $current; 193 } 194 195 function list_files($list) 196 { 197 if (!is_array ($list)) 198 { 199 $temp = $list; 200 $list = array ($temp); 201 unset ($temp); 202 } 203 204 $files = array (); 205 206 $pwd = getcwd(); 207 chdir($this->options['basedir']); 208 209 foreach ($list as $current) 210 { 211 $current = str_replace('\\', '/', $current); 212 $current = preg_replace('/\/+/', '/', $current); 213 $current = preg_replace('/\/$/', '', $current); 214 if (strstr($current, '*')) 215 { 216 $regex = preg_replace('/([\\\^\$\.\[\]\|\(\)\?\+\{\}\/])/', '\\\\\\1', $current); 217 $regex = str_replace('*', '.*', $regex); 218 $dir = strstr($current, '/') ? substr($current, 0, strrpos($current, '/')) : '.'; 219 $temp = $this->parse_dir($dir); 220 foreach ($temp as $current2) 221 if (preg_match("/^{$regex}$/i", $current2['name'])) 222 $files[] = $current2; 223 unset ($regex, $dir, $temp, $current); 224 } 225 else if (@is_dir($current)) 226 { 227 $temp = $this->parse_dir($current); 228 foreach ($temp as $file) 229 $files[] = $file; 230 unset ($temp, $file); 231 } 232 else if (@file_exists($current)) 233 $files[] = array ('name' => $current, 'name2' => $this->options['prepend'] . 234 preg_replace('/(\.+\/+)+/', '', ($this->options['storepaths'] == 0 && strstr($current, '/')) ? 235 substr($current, strrpos($current, '/') + 1) : $current), 236 'type' => @is_link($current) && $this->options['followlinks'] == 0 ? 2 : 0, 237 'ext' => substr($current, strrpos($current, '.')), 'stat' => stat($current)); 238 } 239 240 chdir($pwd); 241 242 unset ($current, $pwd); 243 244 usort($files, array ($this, 'sort_files')); 245 246 return $files; 247 } 248 249 function parse_dir($dirname) 250 { 251 if ($this->options['storepaths'] == 1 && !preg_match('/^(\.+\/*)+$/', $dirname)) 252 $files = array (array ('name' => $dirname, 'name2' => $this->options['prepend'] . 253 preg_replace('/(\.+\/+)+/', '', ($this->options['storepaths'] == 0 && strstr($dirname, '/')) ? 254 substr($dirname, strrpos($dirname, '/') + 1) : $dirname), 'type' => 5, 'stat' => stat($dirname))); 255 else 256 $files = array (); 257 $dir = @opendir($dirname); 258 259 while ($file = @readdir($dir)) 260 { 261 $fullname = $dirname . '/' . $file; 262 if ($file == '.' || $file == '..') 263 continue; 264 else if (@is_dir($fullname)) 265 { 266 if (empty ($this->options['recurse'])) 267 continue; 268 $temp = $this->parse_dir($fullname); 269 foreach ($temp as $file2) 270 $files[] = $file2; 271 } 272 else if (@file_exists($fullname)) 273 $files[] = array ('name' => $fullname, 'name2' => $this->options['prepend'] . 274 preg_replace('/(\.+\/+)+/', '', ($this->options['storepaths'] == 0 && strstr($fullname, '/')) ? 275 substr($fullname, strrpos($fullname, '/') + 1) : $fullname), 276 'type' => @is_link($fullname) && $this->options['followlinks'] == 0 ? 2 : 0, 277 'ext' => substr($file, strrpos($file, '.')), 'stat' => stat($fullname)); 278 } 279 280 @closedir($dir); 281 282 return $files; 283 } 284 285 function sort_files($a, $b) 286 { 287 if ($a['type'] != $b['type']) 288 if ($a['type'] == 5 || $b['type'] == 2) 289 return -1; 290 else if ($a['type'] == 2 || $b['type'] == 5) 291 return 1; 292 else if ($a['type'] == 5) 293 return strcmp(strtolower($a['name']), strtolower($b['name'])); 294 else if ($a['ext'] != $b['ext']) 295 return strcmp($a['ext'], $b['ext']); 296 else if ($a['stat'][7] != $b['stat'][7]) 297 return $a['stat'][7] > $b['stat'][7] ? -1 : 1; 298 else 299 return strcmp(strtolower($a['name']), strtolower($b['name'])); 300 return 0; 301 } 302 303 function download_file() 304 { 305 if ($this->options['inmemory'] == 0) 306 { 307 $this->error[] = 'Can only use download_file() if archive is in memory. Redirect to file otherwise, it is faster.'; 308 return; 309 } 310 switch ($this->options['type']) 311 { 312 case 'zip': 313 header('Content-Type: application/zip'); 314 break; 315 case 'bzip': 316 header('Content-Type: application/x-bzip2'); 317 break; 318 case 'gzip': 319 header('Content-Type: application/x-gzip'); 320 break; 321 case 'tar': 322 header('Content-Type: application/x-tar'); 323 } 324 $header = 'Content-Disposition: attachment; filename="'; 325 $header .= strstr($this->options['name'], '/') ? substr($this->options['name'], strrpos($this->options['name'], '/') + 1) : $this->options['name']; 326 $header .= '"'; 327 header($header); 328 header('Content-Length: ' . strlen($this->archive)); 329 header('Content-Transfer-Encoding: binary'); 330 header('Cache-Control: no-cache, must-revalidate, max-age=60'); 331 header('Expires: Sat, 01 Jan 2000 12:00:00 GMT'); 332 print($this->archive); 333 } 334} 335 336class tar_file extends archive 337{ 338 function __construct($name) 339 { 340 parent::__construct($name); 341 $this->options['type'] = 'tar'; 342 } 343 344 function create_tar() 345 { 346 $pwd = getcwd(); 347 chdir($this->options['basedir']); 348 349 foreach ($this->files as $current) 350 { 351 if ($current['name'] == $this->options['name']) 352 continue; 353 if (strlen($current['name2']) > 99) 354 { 355 $path = substr($current['name2'], 0, strpos($current['name2'], '/', strlen($current['name2']) - 100) + 1); 356 $current['name2'] = substr($current['name2'], strlen($path)); 357 if (strlen($path) > 154 || strlen($current['name2']) > 99) 358 { 359 $this->error[] = "Could not add {$path}{$current['name2']} to archive because the filename is too long."; 360 continue; 361 } 362 } 363 $block = pack('a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12', $current['name2'], sprintf('%07o', 364 $current['stat'][2]), sprintf('%07o', $current['stat'][4]), sprintf('%07o', $current['stat'][5]), 365 sprintf('%011o', $current['type'] == 2 ? 0 : $current['stat'][7]), sprintf('%011o', $current['stat'][9]), 366 ' ', $current['type'], $current['type'] == 2 ? @readlink($current['name']) : '', 'ustar ', ' ', 367 'Unknown', 'Unknown', '', '', !empty ($path) ? $path : '', ''); 368 369 $checksum = 0; 370 for ($i = 0; $i < 512; $i++) 371 $checksum += ord(substr($block, $i, 1)); 372 $checksum = pack('a8', sprintf('%07o', $checksum)); 373 $block = substr_replace($block, $checksum, 148, 8); 374 375 if ($current['type'] == 2 || $current['stat'][7] == 0) 376 $this->add_data($block); 377 else if ($fp = @fopen($current['name'], 'rb')) 378 { 379 $this->add_data($block); 380 while ($temp = fread($fp, 1048576)) 381 $this->add_data($temp); 382 if ($current['stat'][7] % 512 > 0) 383 { 384 $temp = ''; 385 for ($i = 0; $i < 512 - $current['stat'][7] % 512; $i++) 386 $temp .= "\0"; 387 $this->add_data($temp); 388 } 389 fclose($fp); 390 } 391 else 392 $this->error[] = "Could not open file {$current['name']} for reading. It was not added."; 393 } 394 395 $this->add_data(pack('a1024', '')); 396 397 chdir($pwd); 398 399 return 1; 400 } 401 402 function extract_files() 403 { 404 $pwd = getcwd(); 405 chdir($this->options['basedir']); 406 407 if ($fp = $this->open_archive()) 408 { 409 if ($this->options['inmemory'] == 1) 410 $this->files = array (); 411 412 while ($block = fread($fp, 512)) 413 { 414 $temp = unpack('a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp', $block); 415 $file = array ( 416 'name' => $temp['prefix'] . $temp['name'], 417 'stat' => array ( 418 2 => $temp['mode'], 419 4 => octdec($temp['uid']), 420 5 => octdec($temp['gid']), 421 7 => octdec($temp['size']), 422 9 => octdec($temp['mtime']), 423 ), 424 'checksum' => octdec($temp['checksum']), 425 'type' => $temp['type'], 426 'magic' => $temp['magic'], 427 ); 428 if ($file['checksum'] == 0x00000000) 429 break; 430 else if (substr($file['magic'], 0, 5) != 'ustar') 431 { 432 $this->error[] = 'This script does not support extracting this type of tar file.'; 433 break; 434 } 435 $block = substr_replace($block, ' ', 148, 8); 436 $checksum = 0; 437 for ($i = 0; $i < 512; $i++) 438 $checksum += ord(substr($block, $i, 1)); 439 if ($file['checksum'] != $checksum) 440 $this->error[] = "Could not extract from {$this->options['name']}, it is corrupt."; 441 442 if ($this->options['inmemory'] == 1) 443 { 444 $file['data'] = fread($fp, $file['stat'][7]); 445 fread($fp, (512 - $file['stat'][7] % 512) == 512 ? 0 : (512 - $file['stat'][7] % 512)); 446 unset ($file['checksum'], $file['magic']); 447 $this->files[] = $file; 448 } 449 else if ($file['type'] == 5) 450 { 451 if (!is_dir($file['name'])) 452 mkdir($file['name'], $file['stat'][2]); 453 } 454 else if ($this->options['overwrite'] == 0 && file_exists($file['name'])) 455 { 456 $this->error[] = "{$file['name']} already exists."; 457 continue; 458 } 459 else if ($file['type'] == 2) 460 { 461 symlink($temp['symlink'], $file['name']); 462 chmod($file['name'], $file['stat'][2]); 463 } 464 else if ($new = @fopen($file['name'], 'wb')) 465 { 466 fwrite($new, fread($fp, $file['stat'][7])); 467 fread($fp, (512 - $file['stat'][7] % 512) == 512 ? 0 : (512 - $file['stat'][7] % 512)); 468 fclose($new); 469 chmod($file['name'], $file['stat'][2]); 470 } 471 else 472 { 473 $this->error[] = "Could not open {$file['name']} for writing."; 474 continue; 475 } 476 chown($file['name'], $file['stat'][4]); 477 chgrp($file['name'], $file['stat'][5]); 478 touch($file['name'], $file['stat'][9]); 479 unset ($file); 480 } 481 } 482 else 483 $this->error[] = "Could not open file {$this->options['name']}"; 484 485 chdir($pwd); 486 } 487 488 function open_archive() 489 { 490 return @fopen($this->options['name'], 'rb'); 491 } 492} 493 494class gzip_file extends tar_file 495{ 496 function __construct($name) 497 { 498 parent::__construct($name); 499 $this->options['type'] = 'gzip'; 500 } 501 502 function create_gzip() 503 { 504 if ($this->options['inmemory'] == 0) 505 { 506 $pwd = getcwd(); 507 chdir($this->options['basedir']); 508 if ($fp = gzopen($this->options['name'], "wb{$this->options['level']}")) 509 { 510 fseek($this->archive, 0); 511 while ($temp = fread($this->archive, 1048576)) 512 gzwrite($fp, $temp); 513 gzclose($fp); 514 chdir($pwd); 515 } 516 else 517 { 518 $this->error[] = "Could not open {$this->options['name']} for writing."; 519 chdir($pwd); 520 return 0; 521 } 522 } 523 else 524 $this->archive = gzencode($this->archive, $this->options['level']); 525 526 return 1; 527 } 528 529 function open_archive() 530 { 531 return @gzopen($this->options['name'], 'rb'); 532 } 533} 534 535class bzip_file extends tar_file 536{ 537 function __construct($name) 538 { 539 parent::__construct($name); 540 $this->options['type'] = 'bzip'; 541 } 542 543 function create_bzip() 544 { 545 if ($this->options['inmemory'] == 0) 546 { 547 $pwd = getcwd(); 548 chdir($this->options['basedir']); 549 if ($fp = bzopen($this->options['name'], 'wb')) 550 { 551 fseek($this->archive, 0); 552 while ($temp = fread($this->archive, 1048576)) 553 bzwrite($fp, $temp); 554 bzclose($fp); 555 chdir($pwd); 556 } 557 else 558 { 559 $this->error[] = "Could not open {$this->options['name']} for writing."; 560 chdir($pwd); 561 return 0; 562 } 563 } 564 else 565 $this->archive = bzcompress($this->archive, $this->options['level']); 566 567 return 1; 568 } 569 570 function open_archive() 571 { 572 return @bzopen($this->options['name'], 'rb'); 573 } 574} 575 576class zip_file extends archive 577{ 578 function __construct($name) 579 { 580 parent::__construct($name); 581 $this->options['type'] = 'zip'; 582 } 583 584 function create_zip() 585 { 586 $files = 0; 587 $offset = 0; 588 $central = ''; 589 590 if (!empty ($this->options['sfx'])) 591 if ($fp = @fopen($this->options['sfx'], 'rb')) 592 { 593 $temp = fread($fp, filesize($this->options['sfx'])); 594 fclose($fp); 595 $this->add_data($temp); 596 $offset += strlen($temp); 597 unset ($temp); 598 } 599 else 600 $this->error[] = "Could not open sfx module from {$this->options['sfx']}."; 601 602 $pwd = getcwd(); 603 chdir($this->options['basedir']); 604 605 foreach ($this->files as $current) 606 { 607 if ($current['name'] == $this->options['name']) 608 continue; 609 610 $timedate = explode(' ', date('Y n j G i s', $current['stat'][9])); 611 $timedate = ($timedate[0] - 1980 << 25) | ($timedate[1] << 21) | ($timedate[2] << 16) | 612 ($timedate[3] << 11) | ($timedate[4] << 5) | ($timedate[5]); 613 614 $block = pack('VvvvV', 0x04034b50, 0x000A, 0x0000, (isset($current['method']) || $this->options['method'] == 0) ? 0x0000 : 0x0008, $timedate); 615 616 if ($current['stat'][7] == 0 && $current['type'] == 5) 617 { 618 $block .= pack('VVVvv', 0x00000000, 0x00000000, 0x00000000, strlen($current['name2']) + 1, 0x0000); 619 $block .= $current['name2'] . '/'; 620 $this->add_data($block); 621 $central .= pack('VvvvvVVVVvvvvvVV', 0x02014b50, 0x0014, $this->options['method'] == 0 ? 0x0000 : 0x000A, 0x0000, 622 (isset($current['method']) || $this->options['method'] == 0) ? 0x0000 : 0x0008, $timedate, 623 0x00000000, 0x00000000, 0x00000000, strlen($current['name2']) + 1, 0x0000, 0x0000, 0x0000, 0x0000, $current['type'] == 5 ? 0x00000010 : 0x00000000, $offset); 624 $central .= $current['name2'] . '/'; 625 $files++; 626 $offset += (31 + strlen($current['name2'])); 627 } 628 else if ($current['stat'][7] == 0) 629 { 630 $block .= pack('VVVvv', 0x00000000, 0x00000000, 0x00000000, strlen($current['name2']), 0x0000); 631 $block .= $current['name2']; 632 $this->add_data($block); 633 $central .= pack('VvvvvVVVVvvvvvVV', 0x02014b50, 0x0014, $this->options['method'] == 0 ? 0x0000 : 0x000A, 0x0000, 634 (isset($current['method']) || $this->options['method'] == 0) ? 0x0000 : 0x0008, $timedate, 635 0x00000000, 0x00000000, 0x00000000, strlen($current['name2']), 0x0000, 0x0000, 0x0000, 0x0000, $current['type'] == 5 ? 0x00000010 : 0x00000000, $offset); 636 $central .= $current['name2']; 637 $files++; 638 $offset += (30 + strlen($current['name2'])); 639 } 640 else if ($fp = @fopen($current['name'], 'rb')) 641 { 642 $temp = fread($fp, $current['stat'][7]); 643 fclose($fp); 644 $crc32 = crc32($temp); 645 if (!isset($current['method']) && $this->options['method'] == 1) 646 { 647 $temp = gzcompress($temp, $this->options['level']); 648 $size = strlen($temp) - 6; 649 $temp = substr($temp, 2, $size); 650 } 651 else 652 $size = strlen($temp); 653 $block .= pack('VVVvv', $crc32, $size, $current['stat'][7], strlen($current['name2']), 0x0000); 654 $block .= $current['name2']; 655 $this->add_data($block); 656 $this->add_data($temp); 657 unset ($temp); 658 $central .= pack('VvvvvVVVVvvvvvVV', 0x02014b50, 0x0014, $this->options['method'] == 0 ? 0x0000 : 0x000A, 0x0000, 659 (isset($current['method']) || $this->options['method'] == 0) ? 0x0000 : 0x0008, $timedate, 660 $crc32, $size, $current['stat'][7], strlen($current['name2']), 0x0000, 0x0000, 0x0000, 0x0000, 0x00000000, $offset); 661 $central .= $current['name2']; 662 $files++; 663 $offset += (30 + strlen($current['name2']) + $size); 664 } 665 else 666 $this->error[] = "Could not open file {$current['name']} for reading. It was not added."; 667 } 668 669 $this->add_data($central); 670 671 $this->add_data(pack('VvvvvVVv', 0x06054b50, 0x0000, 0x0000, $files, $files, strlen($central), $offset, 672 !empty ($this->options['comment']) ? strlen($this->options['comment']) : 0x0000)); 673 674 if (!empty ($this->options['comment'])) 675 $this->add_data($this->options['comment']); 676 677 chdir($pwd); 678 679 return 1; 680 } 681} 682//EOF