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