1<?php 2// 3 4/** 5 * package::i.tools 6 * 7 * php-downloader v1.0 - www.ipunkt.biz 8 * 9 * (c) 2002 - www.ipunkt.biz (rok) 10 */ 11/** 12 * ======================================================================= 13 * Name: 14 * tar Class 15 * 16 * Author: 17 * Josh Barger <joshb@npt.com> 18 * 19 * Description: 20 * This class reads and writes Tape-Archive (TAR) Files and Gzip 21 * compressed TAR files, which are mainly used on UNIX systems. 22 * This class works on both windows AND unix systems, and does 23 * NOT rely on external applications!! Woohoo! 24 * 25 * Usage: 26 * Copyright (C) 2002 Josh Barger 27 * 28 * This library is free software; you can redistribute it and/or 29 * modify it under the terms of the GNU Lesser General Public 30 * License as published by the Free Software Foundation; either 31 * version 2.1 of the License, or (at your option) any later version. 32 * 33 * This library is distributed in the hope that it will be useful, 34 * but WITHOUT ANY WARRANTY; without even the implied warranty of 35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 36 * Lesser General Public License for more details at: 37 * http://www.gnu.org/copyleft/lesser.html 38 * 39 * If you use this script in your application/website, please 40 * send me an e-mail letting me know about it :) 41 * 42 * Bugs: 43 * Please report any bugs you might find to my e-mail address 44 * at joshb@npt.com. If you have already created a fix/patch 45 * for the bug, please do send it to me so I can incorporate it into my release. 46 * 47 * Version History: 48 * 1.0 04/10/2002 - InitialRelease 49 * 50 * 2.0 04/11/2002 - Merged both tarReader and tarWriter 51 * classes into one 52 * - Added support for gzipped tar files 53 * Remember to name for .tar.gz or .tgz 54 * if you use gzip compression! 55 * :: THIS REQUIRES ZLIB EXTENSION :: 56 * - Added additional comments to 57 * functions to help users 58 * - Added ability to remove files and 59 * directories from archive 60 * 2.1 04/12/2002 - Fixed serious bug in generating tar 61 * - Created another example file 62 * - Added check to make sure ZLIB is 63 * installed before running GZIP 64 * compression on TAR 65 * 2.2 05/07/2002 - Added automatic detection of Gzipped 66 * tar files (Thanks go to Jidgen Falch 67 * for the idea) 68 * - Changed "private" functions to have 69 * special function names beginning with 70 * two underscores 71 * ======================================================================= 72 * XOOPS changes onokazu <webmaster@xoops.org> 73 * 74 * 12/25/2002 - Added flag to addFile() function for binary files 75 * 76 * ======================================================================= 77 */ 78 79/** 80 * tar Class 81 * 82 * This class reads and writes Tape-Archive (TAR) Files and Gzip 83 * compressed TAR files, which are mainly used on UNIX systems. 84 * This class works on both windows AND unix systems, and does 85 * NOT rely on external applications!! Woohoo! 86 * 87 * @author Josh Barger <joshb@npt.com> 88 * @copyright Copyright (C) 2002 Josh Barger 89 * @package kernel 90 * @subpackage core 91 */ 92class Tar 93{ 94 /** 95 * *#@+ 96 * Unprocessed Archive Information 97 */ 98 public $filename; 99 public $isGzipped; 100 public $tar_file; 101 /** 102 * *#@- 103 */ 104 105 /** 106 * *#@+ 107 * Processed Archive Information 108 */ 109 public $files; 110 public $directories; 111 public $numFiles; 112 public $numDirectories; 113 /** 114 * *#@- 115 */ 116 117 /** 118 * Class Constructor -- Does nothing... 119 */ 120 public function __construct() 121 { 122 } 123 124 /** 125 * Computes the unsigned Checksum of a file's header 126 * to try to ensure valid file 127 * 128 * @param string $bytestring 129 * 130 * @return int|string 131 * @access private 132 */ 133 public function __computeUnsignedChecksum($bytestring) 134 { 135 $unsigned_chksum = ''; 136 for ($i = 0; $i < 512; ++$i) { 137 $unsigned_chksum += ord($bytestring[$i]); 138 } 139 for ($i = 0; $i < 8; ++$i) { 140 $unsigned_chksum -= ord($bytestring[148 + $i]); 141 $unsigned_chksum += ord(' ') * 8; 142 } 143 144 return $unsigned_chksum; 145 } 146 147 /** 148 * Converts a NULL padded string to a non-NULL padded string 149 * 150 * @param string $string 151 * @return string 152 * @access private 153 */ 154 public function __parseNullPaddedString($string) 155 { 156 $position = strpos($string, chr(0)); 157 158 return substr($string, 0, $position); 159 } 160 161 /** 162 * This function parses the current TAR file 163 * 164 * @return bool always TRUE 165 * @access private 166 */ 167 public function __parseTar() 168 { 169 // Read Files from archive 170 $tar_length = strlen($this->tar_file); 171 $main_offset = 0; 172 $this->numFiles = 0; 173 while ($main_offset < $tar_length) { 174 // If we read a block of 512 nulls, we are at the end of the archive 175 if (substr($this->tar_file, $main_offset, 512) == str_repeat(chr(0), 512)) { 176 break; 177 } 178 // Parse file name 179 $file_name = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset, 100)); 180 // Parse the file mode 181 $file_mode = substr($this->tar_file, $main_offset + 100, 8); 182 // Parse the file user ID 183 $file_uid = octdec(substr($this->tar_file, $main_offset + 108, 8)); 184 // Parse the file group ID 185 $file_gid = octdec(substr($this->tar_file, $main_offset + 116, 8)); 186 // Parse the file size 187 $file_size = octdec(substr($this->tar_file, $main_offset + 124, 12)); 188 // Parse the file update time - unix timestamp format 189 $file_time = octdec(substr($this->tar_file, $main_offset + 136, 12)); 190 // Parse Checksum 191 $file_chksum = octdec(substr($this->tar_file, $main_offset + 148, 6)); 192 // Parse user name 193 $file_uname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 265, 32)); 194 // Parse Group name 195 $file_gname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 297, 32)); 196 // Make sure our file is valid 197 if ($this->__computeUnsignedChecksum(substr($this->tar_file, $main_offset, 512)) != $file_chksum) { 198 return false; 199 } 200 // Parse File Contents 201 $file_contents = substr($this->tar_file, $main_offset + 512, $file_size); 202 203 /** 204 * ### Unused Header Information ### 205 * $activeFile["typeflag"] = substr($this->tar_file,$main_offset + 156,1); 206 * $activeFile["linkname"] = substr($this->tar_file,$main_offset + 157,100); 207 * $activeFile["magic"] = substr($this->tar_file,$main_offset + 257,6); 208 * $activeFile["version"] = substr($this->tar_file,$main_offset + 263,2); 209 * $activeFile["devmajor"] = substr($this->tar_file,$main_offset + 329,8); 210 * $activeFile["devminor"] = substr($this->tar_file,$main_offset + 337,8); 211 * $activeFile["prefix"] = substr($this->tar_file,$main_offset + 345,155); 212 * $activeFile["endheader"] = substr($this->tar_file,$main_offset + 500,12); 213 */ 214 215 if ($file_size > 0) { 216 // Increment number of files 217 $this->numFiles++; 218 // Create us a new file in our array 219 $activeFile =& $this->files[]; 220 // Asign Values 221 $activeFile['name'] = $file_name; 222 $activeFile['mode'] = $file_mode; 223 $activeFile['size'] = $file_size; 224 $activeFile['time'] = $file_time; 225 $activeFile['user_id'] = $file_uid; 226 $activeFile['group_id'] = $file_gid; 227 $activeFile['user_name'] = $file_uname; 228 $activeFile['group_name'] = $file_gname; 229 $activeFile['checksum'] = $file_chksum; 230 $activeFile['file'] = $file_contents; 231 } else { 232 // Increment number of directories 233 $this->numDirectories++; 234 // Create a new directory in our array 235 $activeDir =& $this->directories[]; 236 // Assign values 237 $activeDir['name'] = $file_name; 238 $activeDir['mode'] = $file_mode; 239 $activeDir['time'] = $file_time; 240 $activeDir['user_id'] = $file_uid; 241 $activeDir['group_id'] = $file_gid; 242 $activeDir['user_name'] = $file_uname; 243 $activeDir['group_name'] = $file_gname; 244 $activeDir['checksum'] = $file_chksum; 245 } 246 // Move our offset the number of blocks we have processed 247 $main_offset += 512 + (ceil($file_size / 512) * 512); 248 } 249 250 return true; 251 } 252 253 /** 254 * Read a non gzipped tar file in for processing. 255 * 256 * @param string $filename full filename 257 * @return bool always TRUE 258 * @access private 259 */ 260 public function __readTar($filename = '') 261 { 262 // Set the filename to load 263 if (!$filename) { 264 $filename = $this->filename; 265 } 266 // Read in the TAR file 267 $fp = fopen($filename, 'rb'); 268 $this->tar_file = fread($fp, filesize($filename)); 269 fclose($fp); 270 271 if ($this->tar_file[0] == chr(31) && $this->tar_file[1] == chr(139) && $this->tar_file[2] == chr(8)) { 272 if (!function_exists('gzinflate')) { 273 return false; 274 } 275 $this->isGzipped = true; 276 $this->tar_file = gzinflate(substr($this->tar_file, 10, -4)); 277 } 278 // Parse the TAR file 279 $this->__parseTar(); 280 281 return true; 282 } 283 284 /** 285 * Generates a TAR file from the processed data 286 * 287 * @return bool always TRUE 288 * @access private 289 */ 290 public function __generateTar() 291 { 292 // Clear any data currently in $this->tar_file 293 unset($this->tar_file); 294 // Generate Records for each directory, if we have directories 295 if ($this->numDirectories > 0) { 296 foreach ($this->directories as $key => $information) { 297 unset($header); 298 // Generate tar header for this directory 299 // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end 300 $header .= str_pad($information['name'], 100, chr(0)); 301 $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0); 302 $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0); 303 $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0); 304 $header .= str_pad(decoct(0), 11, '0', STR_PAD_LEFT) . chr(0); 305 $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0); 306 $header .= str_repeat(' ', 8); 307 $header .= '5'; 308 $header .= str_repeat(chr(0), 100); 309 $header .= str_pad('ustar', 6, chr(32)); 310 $header .= chr(32) . chr(0); 311 $header .= str_pad('', 32, chr(0)); 312 $header .= str_pad('', 32, chr(0)); 313 $header .= str_repeat(chr(0), 8); 314 $header .= str_repeat(chr(0), 8); 315 $header .= str_repeat(chr(0), 155); 316 $header .= str_repeat(chr(0), 12); 317 // Compute header checksum 318 $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT); 319 for ($i = 0; $i < 6; ++$i) { 320 $header[148 + $i] = substr($checksum, $i, 1); 321 } 322 $header[154] = chr(0); 323 $header[155] = chr(32); 324 // Add new tar formatted data to tar file contents 325 $this->tar_file .= $header; 326 } 327 } 328 // Generate Records for each file, if we have files (We should...) 329 if ($this->numFiles > 0) { 330 $this->tar_file = ''; 331 foreach ($this->files as $key => $information) { 332 unset($header); 333 // Generate the TAR header for this file 334 // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end 335 $header = str_pad($information['name'], 100, chr(0)); 336 $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0); 337 $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0); 338 $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0); 339 $header .= str_pad(decoct($information['size']), 11, '0', STR_PAD_LEFT) . chr(0); 340 $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0); 341 $header .= str_repeat(' ', 8); 342 $header .= '0'; 343 $header .= str_repeat(chr(0), 100); 344 $header .= str_pad('ustar', 6, chr(32)); 345 $header .= chr(32) . chr(0); 346 $header .= str_pad($information['user_name'], 32, chr(0)); // How do I get a file's user name from PHP? 347 $header .= str_pad($information['group_name'], 32, chr(0)); // How do I get a file's group name from PHP? 348 $header .= str_repeat(chr(0), 8); 349 $header .= str_repeat(chr(0), 8); 350 $header .= str_repeat(chr(0), 155); 351 $header .= str_repeat(chr(0), 12); 352 // Compute header checksum 353 $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT); 354 for ($i = 0; $i < 6; ++$i) { 355 $header[148 + $i] = substr($checksum, $i, 1); 356 } 357 $header[154] = chr(0); 358 $header[155] = chr(32); 359 // Pad file contents to byte count divisible by 512 360 $file_contents = str_pad($information['file'], ceil($information['size'] / 512) * 512, chr(0)); 361 // Add new tar formatted data to tar file contents 362 $this->tar_file .= $header . $file_contents; 363 } 364 } 365 // Add 512 bytes of NULLs to designate EOF 366 $this->tar_file .= str_repeat(chr(0), 512); 367 368 return true; 369 } 370 371 /** 372 * Open a TAR file 373 * 374 * @param string $filename 375 * @return bool 376 */ 377 public function openTAR($filename) 378 { 379 // Clear any values from previous tar archives 380 unset($this->filename, $this->isGzipped, $this->tar_file, $this->files, $this->directories, $this->numFiles, $this->numDirectories); 381 382 // If the tar file doesn't exist... 383 if (!file_exists($filename)) { 384 return false; 385 } 386 387 $this->filename = $filename; 388 // Parse this file 389 $this->__readTar(); 390 391 return true; 392 } 393 394 /** 395 * Appends a tar file to the end of the currently opened tar file. 396 * 397 * @param string $filename 398 * @return bool 399 */ 400 public function appendTar($filename) 401 { 402 // If the tar file doesn't exist... 403 if (!file_exists($filename)) { 404 return false; 405 } 406 $this->__readTar($filename); 407 408 return true; 409 } 410 411 /** 412 * Retrieves information about a file in the current tar archive 413 * 414 * @param string $filename 415 * @return string|false FALSE on fail 416 */ 417 public function getFile($filename) 418 { 419 if ($this->numFiles > 0) { 420 foreach ($this->files as $key => $information) { 421 if ($information['name'] == $filename) { 422 return $information; 423 } 424 } 425 } 426 427 return false; 428 } 429 430 /** 431 * Retrieves information about a directory in the current tar archive 432 * 433 * @param string $dirname 434 * @return string|false FALSE on fail 435 */ 436 public function getDirectory($dirname) 437 { 438 if ($this->numDirectories > 0) { 439 foreach ($this->directories as $key => $information) { 440 if ($information['name'] == $dirname) { 441 return $information; 442 } 443 } 444 } 445 446 return false; 447 } 448 449 /** 450 * Check if this tar archive contains a specific file 451 * 452 * @param string $filename 453 * @return bool 454 */ 455 public function containsFile($filename) 456 { 457 if ($this->numFiles > 0) { 458 foreach ($this->files as $key => $information) { 459 if ($information['name'] == $filename) { 460 return true; 461 } 462 } 463 } 464 465 return false; 466 } 467 468 /** 469 * Check if this tar archive contains a specific directory 470 * 471 * @param string $dirname 472 * @return bool 473 */ 474 public function containsDirectory($dirname) 475 { 476 if ($this->numDirectories > 0) { 477 foreach ($this->directories as $key => $information) { 478 if ($information['name'] == $dirname) { 479 return true; 480 } 481 } 482 } 483 484 return false; 485 } 486 487 /** 488 * Add a directory to this tar archive 489 * 490 * @param string $dirname 491 * @return bool 492 */ 493 public function addDirectory($dirname) 494 { 495 if (!file_exists($dirname)) { 496 return false; 497 } 498 // Get directory information 499 $file_information = stat($dirname); 500 // Add directory to processed data 501 $this->numDirectories++; 502 $activeDir =& $this->directories[]; 503 $activeDir['name'] = $dirname; 504 $activeDir['mode'] = $file_information['mode']; 505 $activeDir['time'] = $file_information['time']; 506 $activeDir['user_id'] = $file_information['uid']; 507 $activeDir['group_id'] = $file_information['gid']; 508 $activeDir['checksum'] = $checksum; 509 510 return true; 511 } 512 513 /** 514 * Add a file to the tar archive 515 * 516 * @param string $filename 517 * @param boolean $binary Binary file? 518 * @return bool 519 */ 520 public function addFile($filename, $binary = false) 521 { 522 // Make sure the file we are adding exists! 523 if (!file_exists($filename)) { 524 return false; 525 } 526 // Make sure there are no other files in the archive that have this same filename 527 if ($this->containsFile($filename)) { 528 return false; 529 } 530 // Get file information 531 $file_information = stat($filename); 532 // Read in the file's contents 533 if (!$binary) { 534 $fp = fopen($filename, 'r'); 535 } else { 536 $fp = fopen($filename, 'rb'); 537 } 538 $file_contents = fread($fp, filesize($filename)); 539 fclose($fp); 540 // Add file to processed data 541 $this->numFiles++; 542 $activeFile =& $this->files[]; 543 $activeFile['name'] = $filename; 544 $activeFile['mode'] = $file_information['mode']; 545 $activeFile['user_id'] = $file_information['uid']; 546 $activeFile['group_id'] = $file_information['gid']; 547 $activeFile['size'] = $file_information['size']; 548 $activeFile['time'] = $file_information['mtime']; 549 $activeFile['checksum'] = isset($checksum) ? $checksum : ''; 550 $activeFile['user_name'] = ''; 551 $activeFile['group_name'] = ''; 552 $activeFile['file'] = trim($file_contents); 553 554 return true; 555 } 556 557 /** 558 * Remove a file from the tar archive 559 * 560 * @param string $filename 561 * @return bool 562 */ 563 public function removeFile($filename) 564 { 565 if ($this->numFiles > 0) { 566 foreach ($this->files as $key => $information) { 567 if ($information['name'] == $filename) { 568 $this->numFiles--; 569 unset($this->files[$key]); 570 571 return true; 572 } 573 } 574 } 575 576 return false; 577 } 578 579 /** 580 * Remove a directory from the tar archive 581 * 582 * @param string $dirname 583 * @return bool 584 */ 585 public function removeDirectory($dirname) 586 { 587 if ($this->numDirectories > 0) { 588 foreach ($this->directories as $key => $information) { 589 if ($information['name'] == $dirname) { 590 $this->numDirectories--; 591 unset($this->directories[$key]); 592 593 return true; 594 } 595 } 596 } 597 598 return false; 599 } 600 601 /** 602 * Write the currently loaded tar archive to disk 603 * 604 * @return bool 605 */ 606 public function saveTar() 607 { 608 if (!$this->filename) { 609 return false; 610 } 611 // Write tar to current file using specified gzip compression 612 $this->toTar($this->filename, $this->isGzipped); 613 614 return true; 615 } 616 617 /** 618 * Saves tar archive to a different file than the current file 619 * 620 * @param string $filename 621 * @param bool $useGzip Use GZ compression? 622 * @return bool 623 */ 624 public function toTar($filename, $useGzip) 625 { 626 if (!$filename) { 627 return false; 628 } 629 // Encode processed files into TAR file format 630 $this->__generateTar(); 631 // GZ Compress the data if we need to 632 if ($useGzip) { 633 // Make sure we have gzip support 634 if (!function_exists('gzencode')) { 635 return false; 636 } 637 $file = gzencode($this->tar_file); 638 } else { 639 $file = $this->tar_file; 640 } 641 // Write the TAR file 642 $fp = fopen($filename, 'wb'); 643 fwrite($fp, $file); 644 fclose($fp); 645 646 return true; 647 } 648 649 /** 650 * Sends tar archive to stdout 651 * 652 * @param string $filename 653 * @param bool $useGzip Use GZ compression? 654 * @return string 655 */ 656 public function toTarOutput($filename, $useGzip) 657 { 658 if (!$filename) { 659 return false; 660 } 661 // Encode processed files into TAR file format 662 $this->__generateTar(); 663 // GZ Compress the data if we need to 664 if ($useGzip) { 665 // Make sure we have gzip support 666 if (!function_exists('gzencode')) { 667 return false; 668 } 669 $file = gzencode($this->tar_file); 670 } else { 671 $file = $this->tar_file; 672 } 673 674 return $file; 675 } 676} 677