Location: PHPKode > projects > Viscacha > classes/class.tar.php
<?php
if (defined('VISCACHA_CORE') == false) { die('Error: Hacking Attempt'); }

/*
** PHP TAR Implementation
**
** GNU Tar creation and extraction
** Usage based on the C and Perl GNU modules
**
** Copyright (c) 2001 - 2003 Invision Power Services
** Code by Matt Mecham <hide@address.com>
** Report all bugs / improvements to hide@address.com
**
** Modified by Matthias Mohr, http://www.viscacha.org
**
** This code has been created and released under the GNU license and may be
** freely used and distributed.
*/

/*************************************************************
|
| EXTRACTION USAGE:
|
| $tar = new tar("/foo/bar", "myTar.tar");
| $files = $tar->list_files();
| $tar->extract_files( "/extract/to/here/dir" );
|
| CREATION USAGE:
|
| $tar = new tar("/foo/bar" , "myNewTar.tar");
| $tar->add_files( $file_names_with_path_array );
| (or $tar->add_directory( "/foo/bar/myDir" ); to archive a complete dir)
| $tar->write_tar();
|
*************************************************************/

class tar {

	var $tar_header_length = '512';
	var $tar_unpack_header = 'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typeflag/a100linkname/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix';
	var $tar_pack_header = 'A100 A8 A8 A8 A12 A12 A8 A1 A100 A6 A2 A32 A32 A8 A8 A155';
	var $unpack_dir = "";
	var $pack_dir = "";
	var $error = "";
	var $tar_in_mem = array();
	var $tar_filename = "";
	var $filehandle = "";
	var $warnings = array();
	var $ignore_chmod = false;
	var $tarfile_name = "";
	var $tarfile_path = "";
	var $tarfile_path_name = "";
	var $workfiles = array();
	var $dirs = array();

	//+--------------------------------------------------------------------------
	// Set the tarname. If we are extracting a tarball, then it must be the
	// path to the tarball, and it's name (eg: $tar->new_tar("/foo/bar" ,'myTar.tar')
	// or if we are creating a tar, then it must be the path and name of the tar file
	// to create.
	//+--------------------------------------------------------------------------
	function tar($tarpath, $tarname) {

		$this->tarfile_name = $tarname;
		$this->tarfile_path = $tarpath;

		// Make sure there isn't a trailing slash on the path
		$this->tarfile_path = rtrim($this->tarfile_path, '/\\');

		$this->tarfile_path_name = $this->tarfile_path .'/'. $this->tarfile_name;

	}

	//+--------------------------------------------------------------------------
	// Easy way to overwrite defaults
	//+--------------------------------------------------------------------------
	function ignore_chmod($value = true) {
		$this->ignore_chmod = (bool) $value;
	}

	//+--------------------------------------------------------------------------
	// Returns an array with all the filenames in the tar file
	//
	// $advanced == false - return name only
	// $advanced == true  - return name, size, mtime, mode
	//+--------------------------------------------------------------------------
	function list_files($advanced = false) {

		$data = $this->read_tar();

		$final = array();

		foreach($data as $d) {
			if ($advanced == true) {
				$final[] = array (
								'name'  => $d['name'],
								'size'  => $d['size'],
								'mtime' => $d['mtime'],
								'mode'  => substr(decoct($d['mode']), -4),
							);
			}
			else {
				$final[] = $d['name'];
			}
		}

		return $final;
	}

	//+--------------------------------------------------------------------------
	// Add a directory to the tar files.
	//
	// $tar->add_directory( str(TO DIRECTORY) )
	//	Can be used in the following methods.
	//	  $tar->add_directory( "/foo/bar" );
	//	  $tar->write_tar( "/foo/bar" );
	//+--------------------------------------------------------------------------
	function add_directory($dir, $remove_path = false) {

		$this->error = "";

		if (!is_dir($dir)) {
			$this->error = "Extract files error: Destination directory ($dir) does not exist";
			return false;
		}

		$this->get_dir_contents($dir);
		$rpath = ($remove_path == false) ? '' : $dir;
		$this->add_files($this->workfiles, $rpath);

	}

	//+--------------------------------------------------------------------------
	// add files:
	//  Takes an array of files, and adds them to the tar file
	//  Optionally takes a path to remove from the paths in the file.
	//+--------------------------------------------------------------------------
	function add_files($files, $remove_path = '') {

		$count	= 0;

		if (!is_array($files)) {
			$files = array($files);
		}

		foreach ($files as $file) {

			// is it a Mac OS X work file?
			if (preg_match("/\.ds_store$/i", $file )) {
				continue;
			}
			// is it a Windows thumbnail cache file?
			if (preg_match("/Thumbs\.db$/i", $file )) {
				continue;
			}

			$typeflag = 0;
			$data= "";
			$linkname = "";

			$stat = stat($file);
			// Did stat fail?
			if (!is_array($stat)) {
				$this->warnings[] = "Stat failed on {$file}";
				continue;
			}

			$mode  = $stat['mode']; // prior: fileperms($file)
			$uid   = $stat['uid'];
			$gid   = $stat['gid'];
			$rdev  = $stat['rdev'];
			$size  = $stat['size']; // prior: filesize($file)
			$mtime = $stat['mtime']; // prior: filemtime($file)

			if (is_file($file)) {
				// It's a plain file, so lets suck it up
				$typeflag = 0;
				if ($FH = fopen($file, 'rb')) {
					$data = fread($FH, $stat['size']); // prior: filesize($file)
					fclose($FH);
				}
				else {
					$this->warnings[] = "Failed to open {$file}";
					continue;
				}
			}
			else if (is_link($file)) {
				$typeflag = 1;
				$linkname = @readlink($file);
			}
			else if (is_dir($file)) {
				$typeflag = 5;
				// Mod: Add / after directory if needed
				$last = substr($file, -1);
				if ($last == '/' || $last == '\\') {
					$file = rtrim($file, '/\\').'/';
				}
			}
			else {
				// Sockets, Pipes and char/block specials are not
				// supported, so - lets use a silly value to keep the
				// tar ball legitimate.
				$typeflag = 9;
			}

			$filename = str_replace($remove_path, '', $file);

			// Mod: Add missing directories
			if ($typeflag == 5) {
				$this->dirs[$remove_path.$filename] = null;
				$this->addSubDirs($filename, $remove_path);
			}
			else if ($typeflag == 0 && strpos($filename, '/') !== false) {
				$dirname = dirname($filename).'/';
				$this->addSubDirs($dirname, $remove_path);
			}

			// Add this data to our in memory tar file
			$this->tar_in_mem[] = array (
										'name'	 => $filename,
										'mode'	 => $mode,
										'uid'	  => $uid,
										'gid'	  => $gid,
										'size'	 => $stat['size'], // prior: strlen($data)
										'mtime'	=> $mtime,
										'chksum'   => "      ",
										'typeflag' => $typeflag,
										'linkname' => $linkname,
										'magic'	=> '', // prior: ustar\0
										'version'  => '', // prior: 00
										'uname'	=> '', // prior: unknown
										'gname'	=> '', // prior: unknown
										'devmajor' => '',
										'devminor' => '',
										'prefix'   => '',
										'data'	 => $data
									);
			// Clear the stat cache
			@clearstatcache();

			$count++;
		}

		//Return the number of files to anyone who's interested
		return $count;

	}

	function addSubDirs($dirname, $remove_path) {
		if (!isset($this->dirs[$remove_path.$dirname])) {
			$this->dirs[$remove_path.$dirname] = $remove_path;
		}
		if (substr_count($dirname, '/') > 1) {
			$dirname = dirname($dirname).'/';
			$this->addSubDirs($dirname, $remove_path);
		}
	}

	function get_dir_contents($dir) {

		$dir = rtrim($dir, '/\\');
		if (file_exists($dir)) {
			if (is_dir($dir)) {
				$handle = opendir($dir);
				while (($filename = readdir($handle)) !== false) {
					if ($filename != '.' && $filename != '..') {
						if (is_dir($dir.'/'.$filename)) {
							// Mod: Add directories
							$this->workfiles[] = $dir.'/'.$filename.'/';
							$this->get_dir_contents($dir.'/'.$filename);
						}
						else {
							$this->workfiles[] = $dir.'/'.$filename;
						}
					}
				}
				closedir($handle);
			}
			else {
				$this->error = "{$dir} is not a directory";
				return false;
			}
		}
		else {
			$this->error = "Could not locate {$dir}";
			return false;
		}
	}

	//+--------------------------------------------------------------------------
	// Extract the tarball
	// $tar->extract_files( str(TO DIRECTORY), [ array( FILENAMES )  ] )
	//	Can be used in the following methods.
	//	  $tar->extract( "/foo/bar" , $files );
	// 	  This will seek out the files in the user array and extract them
	//	$tar->extract( "/foo/bar" );
	//	Will extract the complete tar file into the user specified directory
	// Returns: Files that could not be extracted
	//+--------------------------------------------------------------------------
	function extract_files($to_dir, $files = null) {
		global $filesystem;

		$this->error = "";

		// Make sure the $to_dir is pointing to a valid dir, or we error and return
		if (!is_dir($to_dir)) {
			$this->error = "Extract files error: Destination directory ($to_dir) does not exist";
			return false;
		}
		$to_dir = realpath($to_dir).DIRECTORY_SEPARATOR;

		//+------------------------------
		// Get the file info from the tar
		//+------------------------------
		$in_files = $this->read_tar();
		if (!empty($this->error)) {
			return false;
		}

		$error_files = array();
		foreach ($in_files as $k => $file) {

			//-----------------------------------------
			// Stop any potential file traversal issues
			//-----------------------------------------

			$file['name'] = str_replace( '..', '', $file['name'] );
			$error_files[$k] = $file['name'];

			//---------------------------------------------
			// Are we choosing which files to extract?
			//---------------------------------------------
			if (is_array($files) && !in_array($file['name'], $files)) {
				continue;
			}

			//---------------------------------------------
			// GNU TAR format dictates that all paths *must* be in the *nix
			// format - if this is not the case, blame the tar vendor, not me!
			//---------------------------------------------
			if (preg_match("#/#", $file['name'])) {
				$path_info = explode("/", $file['name'] );
				$file_name = array_pop($path_info);
			}
			else {
				$path_info = array();
				$file_name = $file['name'];
			}

			//---------------------------------------------
			// If we have a path, then we must build the directory tree
			//---------------------------------------------
			$cur_dir = $to_dir;
			if (count($path_info) > 0) {
				foreach($path_info as $dir_component) {
					if (empty($dir_component)) {
						continue;
					}
					$cur_dir .= $dir_component.'/';
					if ((file_exists($cur_dir)) && (!is_dir($cur_dir))) {
						$this->warnings[] = "{$cur_dir} exists, but is not a directory";
						continue;
					}
					if (!is_dir($cur_dir)) {
						$filesystem->mkdir($cur_dir, 0777);
					}
					else {
						$filesystem->chmod($cur_dir, 0777);
					}
				}
			}

			//---------------------------------------------
			// check the typeflags, and work accordingly
			//---------------------------------------------
			if (empty($file['typeflag'])) {
				$chmod_changed = false;
				if (file_exists($cur_dir.$file_name)) {
					$chmod = get_chmod($cur_dir.$file_name, true);
					$filesystem->chmod($cur_dir.$file_name, 0666);
					$chmod_changed = true;
				}
				if ($filesystem->file_put_contents($cur_dir.$file_name, $file['data'])) {
					unset($error_files[$k]);
				}
				else {
					$this->warnings[] = "Could not write data to {$cur_dir}{$file_name}";
				}
				if ($chmod_changed == true) {
					$filesystem->chmod($cur_dir.$file_name, $chmod);
				}
			}
			else if ($file['typeflag'] == 5) {
				if ((file_exists($cur_dir.$file_name)) && (!is_dir($cur_dir.$file_name))) {
					$this->warnings[] = "{$cur_dir}{$file_name} exists, but is not a directory";
					continue;
				}
				if (!is_dir($cur_dir.$file_name)) {
					if ($filesystem->mkdir($cur_dir.$file_name, 0777)) {
						unset($error_files[$k]);
					}
				}
				else {
					$filesystem->chmod($cur_dir.$file_name, 0777);
					unset($error_files[$k]);
				}
			}
			else if ($file['typeflag'] == 6) {
				$this->warnings[] = "Cannot handle named pipes";
				continue;
			}
			else if ($file['typeflag'] == 1) {
				$this->warnings[] = "Cannot handle system links";
				continue;
			}
			else if ($file['typeflag'] == 4) {
				$this->warnings[] = "Cannot handle device files";
				continue;
			}
			else if ($file['typeflag'] == 3) {
				$this->warnings[] = "Cannot handle device files";
				continue;
			}
			else {
				$this->warnings[] = "Unknown typeflag found";
				continue;
			}

			if ($this->ignore_chmod == false) {
				if (!$filesystem->chmod($cur_dir.$file_name, $file['mode'])) {
					$this->warnings[] = "CHMOD {$file['mode']} on {$cur_dir}{$file_name} failed!";
				}
			}

			@touch($cur_dir.$file_name, $file['mtime']);
		}

		return $error_files;
	}

	//+--------------------------------------------------------------------------
	// Writes the tarball into the directory / file specified in the constructor
	//+--------------------------------------------------------------------------
	function write_tar() {
		global $filesystem;

		if ($this->tarfile_path_name == "") {
			$this->error = 'No filename or path was specified to create a new tar file';
			return false;
		}

		if (count($this->tar_in_mem) < 1) {
			$this->error = 'No data to write to the new tar file';
			return false;
		}

		// Mod: Add missing directories
		foreach ($this->dirs as $dirname => $remove_path) {
			if ($remove_path !== null) {
				$this->add_files($dirname, $remove_path);
			}
		}

		$tardata = "";
		foreach ($this->tar_in_mem as $file) {
			$prefix = "";
			$tmp	= "";
			$last   = "";

			// make sure the filename isn't longer than 99 characters.
			if (strlen($file['name']) > 99) {
				$pos = strrpos($file['name'], "/");
				if ($pos !== false) {
					// filename alone is longer than 99 characters!
					$this->error[] = "Filename {$file['name']} exceeds the length allowed by GNU Tape ARchives";
					continue;
				}
				$prefix = substr($file['name'], 0, $pos);  // Move the path to the prefix
				$file['name'] = substr($file['name'], ($pos+1));
				if (strlen($prefix) > 154) {
					$this->error[] = "File path exceeds the length allowed by GNU Tape ARchives";
					continue;
				}
			}

			// BEGIN FORMATTING (a8a1a100)
			$mode  = sprintf("%6s ", decoct($file['mode']));
			$uid   = sprintf("%6s ", decoct($file['uid']));
			$gid   = sprintf("%6s ", decoct($file['gid']));
			$size  = sprintf("%11s ", decoct($file['size']));
			$mtime = sprintf("%11s ", decoct($file['mtime']));
			$tmp  = pack("a100a8a8a8a12a12",$file['name'],$mode,$uid,$gid,$size,$mtime);

			$last  = pack("a1"   , $file['typeflag']);
			$last .= pack("a100" , $file['linkname']);
			$last .= pack("a6", ""); // prior: pack("a6", "ustar") // magic
			$last .= pack("a2", "" ); // version
			$last .= pack("a32", $file['uname']);
			$last .= pack("a32", $file['gname']);
			$last .= pack("a8", ""); // devmajor
			$last .= pack("a8", ""); // devminor
			$last .= pack("a155", $prefix);
			//$last .= pack("a12", "");
			$test_len = $tmp . $last . "12345678";
			$last .= str_repeat("\0", ($this->tar_header_length - strlen($test_len)));

			// Here comes the science bit, handling
			// the checksum.
			$checksum = 0;
			for ($i = 0 ; $i < 148 ; $i++ ) {
				$checksum += ord( substr($tmp, $i, 1) );
			}
			for ($i = 148 ; $i < 156 ; $i++) {
				$checksum += ord(' ');
			}
			for ($i = 156, $j = 0 ; $i < 512 ; $i++, $j++) {
				$checksum += ord( substr($last, $j, 1) );
			}
			$checksum = sprintf("%6s ", decoct($checksum));

			$tmp .= pack("a8", $checksum);
			$tmp .= $last;
			$tmp .= $file['data'];

			// Tidy up this chunk to the power of 512
			if ($file['size'] > 0) {
				if ($file['size'] % 512 != 0) {
					$homer = str_repeat( "\0" , (512 - ($file['size'] % 512)) );
					$tmp .= $homer;
				}
			}

			$tardata .= $tmp;
		}

		// Add the footer
		$tardata .= pack("a512", "");

		// print it to the tar file
		if ($filesystem->file_put_contents($this->tarfile_path_name, $tardata)) {
			return true;
		}
		else {
			$this->error[] = "File {$this->tarfile_path_name} is not writable.";
			return false;
		}
	}

	//+--------------------------------------------------------------------------
	// Read the tarball - builds an associative array
	//+--------------------------------------------------------------------------
	function read_tar() {

		$filename = $this->tarfile_path_name;

		if (empty($filename)) {
			$this->error = 'No filename specified when attempting to read a tar file';
			return array();
		}

		if (!file_exists($filename)) {
			$this->error = 'Cannot locate the file '.$filename;
			return array();
		}

		$tar_info = array();

		// Open up the tar file and start the loop
		if (!$FH = fopen($filename , 'rb' )) {
			$this->error = "Cannot open {$filename} for reading";
			return array();
		}

		// Grrr, perl allows spaces, PHP doesn't. Pack strings are hard to read without
		// them, so to save my sanity, I'll create them with spaces and remove them here
		$this->tar_unpack_header = preg_replace( "/\s/", "" , $this->tar_unpack_header);

		while (!feof($FH)) {

			$buffer = fread($FH , $this->tar_header_length);

			// check the block
			$checksum = 0;

			for ($i = 0 ; $i < 148 ; $i++) {
				$checksum += ord( substr($buffer, $i, 1) );
			}
			for ($i = 148 ; $i < 156 ; $i++) {
				$checksum += ord(' ');
			}
			for ($i = 156 ; $i < 512 ; $i++) {
				$checksum += ord( substr($buffer, $i, 1) );
			}

			$fa = unpack( $this->tar_unpack_header, $buffer);

			$name	 = trim($fa['filename']);
			$mode	 = OctDec(trim($fa['mode']));
			$uid	  = OctDec(trim($fa['uid']));
			$gid	  = OctDec(trim($fa['gid']));
			$size	 = OctDec(trim($fa['size']));
			$mtime	= OctDec(trim($fa['mtime']));
			$chksum   = OctDec(trim($fa['chksum']));
			$typeflag = trim($fa['typeflag']);
			$linkname = trim($fa['linkname']);
			$magic	= trim($fa['magic']);
			$version  = trim($fa['version']);
			$uname	= trim($fa['uname']);
			$gname	= trim($fa['gname']);
			$devmajor = OctDec(trim($fa['devmajor']));
			$devminor = OctDec(trim($fa['devminor']));
			$prefix   = trim($fa['prefix']);

			if ( ($checksum == 256) && ($chksum == 0) ) {
				//EOF!
				break;
			}

			// Mod: Added empty
			if (!empty($prefix)) {
				$name = $prefix.'/'.$name;
			}

			// Some broken tars don't set the type flag
			// correctly for directories, so we assume that
			// if it ends in / it's a directory...
			if (preg_match("#/$#" , $name)) {
				$typeflag = 5;
			}

			// If it's the end of the tarball...
			$test = str_repeat( '\0' , 512 );
			if ($buffer == $test) {
				break;
			}

			// Read the next chunk
			// Mod: Protect against error on 0 byte files
			if ($size > 0) {
				$data = @fread($FH, $size);
			}
			else {
				$data = '';
			}

			if (strlen($data) != $size) {
				$this->error = "Read error on tar file";
				fclose($FH);
				return array();
			}

			$diff = $size % 512;
			if ($diff != 0) {
				// Padding, throw away
				$crap = fread( $FH, (512-$diff) );
			}

			// Protect against tarfiles with garbage at the end
			if ($name == "") {
				break;
			}

			$tar_info[] = array (
							'name' => $name,
							'mode' => $mode,
							'uid' => $uid,
							'gid' => $gid,
							'size' => $size,
							'mtime' => $mtime,
							'chksum' => $chksum,
							'typeflag' => $typeflag,
							'linkname' => $linkname,
							'magic'	=> $magic,
							'version' => $version,
							'uname'	=> $uname,
							'gname'	=> $gname,
							'devmajor' => $devmajor,
							'devminor' => $devminor,
							'prefix' => $prefix,
							'data' => $data
						);
		}

		fclose($FH);

		return $tar_info;
	}

}
?>
Return current item: Viscacha