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