<?php
/* daCode http://www.dacode.org/
 * src/phplib/tar.php3
 * $Id: tar.php3,v 1.13 2001/07/11 09:22:27 penso Exp $
 * Denis Barbier <barbier@linuxfr.org>
 *
 * Create and extract tar archives
 *
 */

Class Tar {
	var $format, $x_fmt, $c_fmt, $begin, $debug, $noact;

	Function tar() {
		global $config;

		$this->format = array(
			'name'      => array(   0,   100,  "a99x") ,
			'mode'      => array( 100,     8,   "A7x", "%07o\0") ,
			'uid'       => array( 108,     8,   "A7x", "%07o\0") ,
			'gid'       => array( 116,     8,   "A7x", "%07o\0") ,
			'size'      => array( 124,    11,   "A11", "%011o") ,
			'space1'    => array( 135,     1,   "A1") ,
			'mtime'     => array( 136,    11,   "a11", "%011o") ,
			'space2'    => array( 147,     1,   "A1") ,
			'chksum'    => array( 148,     6,   "a6",  "%06o") ,
			'space3'    => array( 154,     2,   "A2") ,
			'typeflag'  => array( 156,     1,   "a1") ,
			'linkname'  => array( 157,   100,   "a99x") ,
			'magic'     => array( 257,     8,   "a8"),
			'uname'     => array( 265,    32,   "a31x") ,
			'gname'     => array( 297,    32,   "a31x") ,
			'devmajor'  => array( 329,     8,   "a7x"),
			'devminor'  => array( 337,     8,   "a7x"),
			'prefix'    => array( 345,   155,   "a154x") ,
			'space4'    => array( 500,    12,   "a12")
		);
		$s = '';
		reset($this->format);
		while (list($key, $val) = each($this->format)) {
			if (ereg('x$', $val[2])) {
				$s .= '/'.substr($val[2], 0, -1).$key.'/x';
			} else {
				$s .= '/'.$val[2].$key;
			}
		}
		$this->x_fmt = substr($s, 1);
		$s = '';
		reset($this->format);
		while (list($key, $val) = each($this->format)) {
			$s .= $val[2];
		}
		$this->c_fmt = $s;

		$this->olddir = '';

		//  When $this->begin is 1, file descriptor points to
		//  a header, otherwise it points to body file
		$this->begin = 1;

		//   When set, be verbose
		$this->debug = 0;
		//   When set, do not perform any action
		$this->noact = 0;
	}

	Function verbose($string) {
		if ($this->debug) {
			echo $string."\n";
		}
	}

	//   Define functions to work with compressed and uncompressed files in
	//   a transparent manner
	Function fopen($filename,$mode) {
		if ($this->gz) {
			$fd = gzopen($filename,$mode);
		} else {
			$fd = fopen($filename,$mode);
		}
		return $fd;
	}
	Function fclose($fd) {
		if ($this->gz) {
			gzclose($fd);
		} else {
			fclose($fd);
		}
	}
	Function fread($fd,$size) {
		if ($this->gz) {
			$str = gzread($fd,$size);
		} else {
			$str = fread($fd,$size);
		}
		return $str;
	}
	Function fwrite($fd,$string,$size="") {
		if ($this->gz) {
			if ($size) {
				gzwrite($fd,$string,$size);
			} else {
				gzwrite($fd,$string);
			}
		} else {
			if ($size) {
				fwrite($fd,$string,$size);
			} else {
				fwrite($fd,$string);
			}
		}
	}
	Function feof($fd) {
		if ($this->gz) {
			$eof = gzeof($fd);
		} else {
			$eof = feof($fd);
		}
		return $eof;
	}

	//   Open a tarfile, it may be at a different location
	Function open_read($filename,$destdir="") {
		$this->destdir = $destdir;
		$fd = $this->fopen($filename, "rb");
		if (!$fd) {
			echo lecho("Cannot read file")." $filename\n";
			return;
		}
		if ($destdir) {
			//   Store previous location
			$this->olddir = chop(`pwd`);
			chdir($destdir);
		}
		return $fd;
	}

	//   Read a 512-byte block containing file informations
	Function read_header($fd,$subs) {
		$header = array();
		if (!$this->begin) {
			echo lecho("File pointer misplaced: not at header position");
			return;
		}
		$data = $this->fread($fd, 512);
		$header = unpack($this->x_fmt, $data);
		$this->begin = 0;
		reset($subs);
		while ($f = each($subs)) {
			$g = current($subs);
			$header["name"] = ereg_replace($f[1], $g, $header["name"]);
			next($subs);
		}
		if ($this->debug) {
			$this->display_header($header);
		}
		return $header;
	}

	//   Read file contents
	Function read_body($fd,$type,$size) {
		if ($this->begin) {
			echo lecho("File pointer misplaced: not at body position");
			return;
		}
		$this->begin = 1;
		$body = '';

		if ($type == 5) {
			//   Directory
			return '';
		} elseif ($type == 0) {
			//   Regular file
			$size = octdec($size);
			$res = $size % 512;
			$body = $this->fread($fd, $size);
			if ($res > 0) {
				$this->fread($fd, 512 - $res);
			}
		} else {
			echo lecho("Error: file type not authorized")."<br>";
		}
		return $body;
	}

	//   Display header informations
	Function display_header($header) {
		if (!$header || !is_array($header)) {
			return;
		}
		reset($header);
		while (list($key, $val) = each($header)) {
			if (!ereg('space', $key)) {
				if ($key == 'uid' || $key == 'gid' || $key == 'size' ) {
					$val = octdec($val);
				} elseif ($key == 'mtime') {
					$val = date("Y-M-d H:i:s", octdec($val));
				}
				echo '<br><b>'.$key.'</b>:'.$val."\n";
			}
		}
		echo "<br>\n";
	}

	//   Extract a tar file
	Function extract($filename,$gz,$subs="",$destdir="",$filemode="0644",$dirmode="0755") {
		$this->gz = $gz;
		if (!is_array($subs)) {
			$subs = array();
		}
		$fd = $this->open_read($filename,$destdir);
		if (!$fd) {
			return 0;
		}
		$seen = 0;
		while (!$this->feof($fd)) {
			$header = $this->read_header($fd,$subs);
			if (empty($header["name"]) && $seen) {
				//  EOF
				break;
			}
			$seen = 1;
			$body = $this->read_body($fd,$header["typeflag"],$header["size"]);
			if ($header["typeflag"] == 5) {
				$this->verbose('Create dir '.$header["name"].'<br>');
				if (!$this->noact) {
					mkdir($header["name"], octdec($dirmode));
				}
			} elseif ($header["typeflag"] == 0) {
				$this->verbose('Create file '.$header["name"].
					" size: ".octdec($header['size'])." bytes<br>\n");
				if (!$this->noact) {
					$out = fopen($header["name"], "w");
					if (!$out) {
						$this->verbose('Unable to write to '.$header["name"]);
						continue;
					}
					fwrite($out, $body);
					fclose($out);
				}
			} else {
				echo lecho("Error: file type not authorized")."<br>";
			}
		}
		$this->fclose($fd);
		if ($this->olddir) {
			chdir($this->olddir);
		}
		return $fd;
	}

	//   Display contents of a tar file
	Function files($filename,$gz,$subs="",$destdir="") {
		$this->gz = $gz;
		if (!is_array($subs)) {
			$subs = array();
		}
		$list = array();
		$fd = $this->open_read($filename,$destdir);
		if (!$fd) {
			return;
		}
		$seen = 0;
		while (!$this->feof($fd)) {
			$header = $this->read_header($fd,$subs);
			if (empty($header["name"]) && $seen) {
				//  EOF
				break;
			}
			$seen = 1;
			$body = $this->read_body($fd,$header["typeflag"],$header["size"]);
			if ($header["typeflag"] == 0) {
				$list[] = $header["name"];
			}
		}
		$this->fclose($fd);
		if ($this->olddir) {
			chdir($this->olddir);
		}
		return $list;
	}

	//   Show content of a file in the archive
	Function file_content($filename,$gz,$file,$subs="") {
		$this->gz = $gz;
		if (!is_array($subs)) {
			$subs = array();
		}
		$fd = $this->open_read($filename);
		if (!$fd) {
			return;
		}
		$seen = 0;
		while (!$this->feof($fd)) {
			$header = $this->read_header($fd,$subs);
			if (empty($header["name"]) && $seen) {
				//  EOF
				break;
			}
			$seen = 1;
			$body = $this->read_body($fd,$header["typeflag"],$header["size"]);
			if ($header["name"] == $file) {
				$this->fclose($fd);
				return $body;
			}
		}
		$this->fclose($fd);
		if ($this->olddir) {
			chdir($this->olddir);
		}
	}

	//   Open a tarfile for writing
	Function open_write($filename,$fromdir="") {
		if ($fromdir) {
			$this->olddir = chop(`pwd`);
			chdir($fromdir);
		}
		umask(022);
		$fd = $this->fopen($filename, "wb");
		if (!$fd) {
			echo lecho("Cannot write file")." $filename\n";
			if ($fromdir) chdir($this->olddir);
			return;
		}
		return $fd;
	}

	//   Compute checksum
	Function compute_checksum($bindata) {
		$chksum = 0;
		$nbytes = $this->format["chksum"][1]+2;
		$hexstr = bin2hex(substr($bindata,0,$this->format["chksum"][0]).
			pack("A".$nbytes, ' ').
			substr($bindata,$this->format["chksum"][0]+$nbytes));
		for ($i = 0; $i < 512; $i++) {
			$chksum += hexdec(substr($hexstr, 2*$i, 2));
		}
		$chksum = $chksum % 65535;
		$chksum = sprintf($this->format["chksum"][3], $chksum);
		return $chksum;
	}

	//   Write a 512-byte block containing file informations
	Function write_header($fd,$header) {
		if (!$this->begin) {
			echo lecho("File pointer misplaced: not at header position");
			return;
		}
		$bindata = '';
		reset($header);
		while (list($key, $val) = each($header)) {
			if (isset($this->format[$key][3])) {
				$val = sprintf($this->format[$key][3], $val);
			}
			$bindata .= pack($this->format[$key][2], $val);
		}
		$chksum = $this->compute_checksum($bindata);
		$bindata = substr($bindata,0,$this->format["chksum"][0]).
			pack($this->format["chksum"][2], $chksum).
			substr($bindata,$this->format["chksum"][0]+$this->format["chksum"][1]);
		if (!$this->noact) {
			$this->totalsize += 512;
			$this->fwrite($fd, $bindata, 512);
		}
		$this->begin = 0;
	}

	//   Write file contents
	Function write_body($fd,$type,$size,$name) {
		if ($this->begin) {
			echo lecho("File pointer misplaced: not at body position");
			return;
		}
		$this->begin = 1;
		if ($type == 5) {
			//   Directory
			return;
		} elseif ($type == 0) {
			//   Regular file
			$in = fopen($name, "rb");
			if (!$in) {
				echo lecho("Cannot read file")." $name <br>";
				continue;
			}
			$body = fread($in, $size);
			fclose($in);
			if (!$this->noact) {
				$this->totalsize += $size;
				$this->fwrite($fd, $body, $size+0);
				$res = $size % 512;
				if ($res > 0) {
					$this->totalsize += 512 - $res;
					$this->fwrite($fd, pack("a".(512 - $res), ''));
				}
			}
		} else {
			echo lecho("Error: file type not authorized")."<br>";
		}
	}

	//   Add an entry to the tarfile
	Function add_entry($fd,$name,$excludes,$subs) {
		reset($excludes);
		while ($f = each($excludes)) {
			if (ereg($f[1], $name)) {
				return;
			}
		}
		$filename = $name;
		reset($subs);
		while ($f = each($subs)) {
			$g = current($subs);
			$filename = ereg_replace($f[1], $g, $filename);
			next($subs);
		}

		$info = stat($name);
		$size = $info[7];
		if (!is_readable($name)) {
			$this->verbose('Skipped non-existing '.$filename.'<br>');
			return;
		} elseif (is_file($name)) {
			$type = 0;
			$this->verbose('Add file '.$filename.' size '.$size.' bytes <br>');
		} elseif (is_dir($name)) {
			$type = 5;
			$name .= '/';
			$size = 0;
			$this->verbose('Add dir '.$filename.'<br>');
		} else {
			echo lecho("Error: file type not authorized")." for file $filename <br>";
			return;
		}
		$header = array(
			'name'      => $filename,
			'mode'      => $info[2],
			'uid'       => 0,
			'gid'       => 0,
			'size'      => $size,
			'space1'    => "\0",
			'mtime'     => $info[9],
			'space2'    => "\0",
			'chksum'    => '0',
			'space3'    => "\0",
			'typeflag'  => $type,
			'linkname'  => '',
			'magic'     => 'ustar  ',
			'uname'     => 'root',
			'gname'     => 'root',
			'devmajor'  => '',
			'devminor'  => '',
			'prefix'    => '',
			'space4'    => ''
		);
		$this->write_header($fd,$header);
		$this->write_body($fd,$type,$size,$name);
		if (is_dir($name)) {
			//   Process directory recursively
			$handle=opendir($name);
			while ($file = readdir($handle)) {
				if ($file == '.' || $file == '..') continue;
				$this->add_entry($fd,$name.$file,$excludes,$subs);
			}
		}
	}

	//  Write an archive
	Function create($filename,$contents,$gz="0",$excludes="",$subs="",$fromdir="") {
		$this->gz = $gz;
		if (!$this->noact) {
			$fd = $this->open_write($filename,$fromdir);
			if (!$fd) {
				return 0;
			}
		} else {
			$fd = 0;
		}
		if (is_string($contents)) {
			$contents = array($contents);
		}
		if (!is_array($excludes)) {
			$excludes = array();
		}
		if (!is_array($subs)) {
			$subs = array();
		}
		$this->totalsize = 0;
		while ($piece = each($contents)) {
			$this->add_entry($fd, $piece[1],$excludes,$subs);
		}
		if (!$this->noact) {
			$res = $this->totalsize % 10240;
			if ($res > 0) {
				$this->totalsize += 10240 - $res;
				$this->fwrite($fd, pack("a".(10240 - $res), ''));
			}
			$this->fclose($fd);
		}
		if ($fromdir) {
			chdir($this->olddir);
		}
		//   When noact is set, this function returns 0 because tarfile
		//   is not created
		return $fd;
	}
}

?>
