1: <?php
2: // $Id: class.tar.php,v 1.1 2007/05/15 02:34:21 minahito Exp $
3: /*
4: package::i.tools
5:
6: php-downloader v1.0 - www.ipunkt.biz
7:
8: (c) 2002 - www.ipunkt.biz (rok)
9: */
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: *
90: * @package kernel
91: * @subpackage core
92: */
93: class tar
94: {
95: /**#@+
96: * Unprocessed Archive Information
97: */
98: var $filename;
99: var $isGzipped;
100: var $tar_file;
101: /**#@-*/
102:
103: /**#@+
104: * Processed Archive Information
105: */
106: var $files;
107: var $directories;
108: var $numFiles;
109: var $numDirectories;
110: /**#@-*/
111:
112:
113: /**
114: * Class Constructor -- Does nothing...
115: */
116: function tar()
117: {
118: return true;
119: }
120:
121: /**
122: * Computes the unsigned Checksum of a file's header
123: * to try to ensure valid file
124: *
125: * @param string $bytestring
126: *
127: * @access private
128: */
129: function __computeUnsignedChecksum($bytestring)
130: {
131: $unsigned_chksum = '';
132: for($i=0; $i<512; $i++)
133: $unsigned_chksum += ord($bytestring[$i]);
134: for($i=0; $i<8; $i++)
135: $unsigned_chksum -= ord($bytestring[148 + $i]);
136: $unsigned_chksum += ord(" ") * 8;
137:
138: return $unsigned_chksum;
139: }
140:
141:
142: /**
143: * Converts a NULL padded string to a non-NULL padded string
144: *
145: * @param string $string
146: *
147: * @return string
148: *
149: * @access private
150: **/
151: function __parseNullPaddedString($string)
152: {
153: $position = strpos($string,chr(0));
154: return substr($string,0,$position);
155: }
156:
157: /**
158: * This function parses the current TAR file
159: *
160: * @return bool always TRUE
161: *
162: * @access private
163: **/
164: function __parseTar()
165: {
166: // Read Files from archive
167: $tar_length = strlen($this->tar_file);
168: $main_offset = 0;
169: $this->numFiles = 0;
170: while ( $main_offset < $tar_length ) {
171: // If we read a block of 512 nulls, we are at the end of the archive
172: if(substr($this->tar_file,$main_offset,512) == str_repeat(chr(0),512))
173: break;
174:
175: // Parse file name
176: $file_name = $this->__parseNullPaddedString(substr($this->tar_file,$main_offset,100));
177:
178: // Parse the file mode
179: $file_mode = substr($this->tar_file,$main_offset + 100,8);
180:
181: // Parse the file user ID
182: $file_uid = octdec(substr($this->tar_file,$main_offset + 108,8));
183:
184: // Parse the file group ID
185: $file_gid = octdec(substr($this->tar_file,$main_offset + 116,8));
186:
187: // Parse the file size
188: $file_size = octdec(substr($this->tar_file,$main_offset + 124,12));
189:
190: // Parse the file update time - unix timestamp format
191: $file_time = octdec(substr($this->tar_file,$main_offset + 136,12));
192:
193: // Parse Checksum
194: $file_chksum = octdec(substr($this->tar_file,$main_offset + 148,6));
195:
196: // Parse user name
197: $file_uname = $this->__parseNullPaddedString(substr($this->tar_file,$main_offset + 265,32));
198:
199: // Parse Group name
200: $file_gname = $this->__parseNullPaddedString(substr($this->tar_file,$main_offset + 297,32));
201:
202: // Make sure our file is valid
203: if($this->__computeUnsignedChecksum(substr($this->tar_file,$main_offset,512)) != $file_chksum)
204: return false;
205:
206: // Parse File Contents
207: $file_contents = substr($this->tar_file,$main_offset + 512,$file_size);
208:
209: /* ### Unused Header Information ###
210: $activeFile["typeflag"] = substr($this->tar_file,$main_offset + 156,1);
211: $activeFile["linkname"] = substr($this->tar_file,$main_offset + 157,100);
212: $activeFile["magic"] = substr($this->tar_file,$main_offset + 257,6);
213: $activeFile["version"] = substr($this->tar_file,$main_offset + 263,2);
214: $activeFile["devmajor"] = substr($this->tar_file,$main_offset + 329,8);
215: $activeFile["devminor"] = substr($this->tar_file,$main_offset + 337,8);
216: $activeFile["prefix"] = substr($this->tar_file,$main_offset + 345,155);
217: $activeFile["endheader"] = substr($this->tar_file,$main_offset + 500,12);
218: */
219:
220: if ( $file_size > 0 ) {
221: // Increment number of files
222: $this->numFiles++;
223:
224: // Create us a new file in our array
225: $activeFile = &$this->files[];
226:
227: // Asign Values
228: $activeFile["name"] = $file_name;
229: $activeFile["mode"] = $file_mode;
230: $activeFile["size"] = $file_size;
231: $activeFile["time"] = $file_time;
232: $activeFile["user_id"] = $file_uid;
233: $activeFile["group_id"] = $file_gid;
234: $activeFile["user_name"] = $file_uname;
235: $activeFile["group_name"] = $file_gname;
236: $activeFile["checksum"] = $file_chksum;
237: $activeFile["file"] = $file_contents;
238: } else {
239: // Increment number of directories
240: $this->numDirectories++;
241:
242: // Create a new directory in our array
243: $activeDir = &$this->directories[];
244:
245: // Assign values
246: $activeDir["name"] = $file_name;
247: $activeDir["mode"] = $file_mode;
248: $activeDir["time"] = $file_time;
249: $activeDir["user_id"] = $file_uid;
250: $activeDir["group_id"] = $file_gid;
251: $activeDir["user_name"] = $file_uname;
252: $activeDir["group_name"] = $file_gname;
253: $activeDir["checksum"] = $file_chksum;
254: }
255:
256: // Move our offset the number of blocks we have processed
257: $main_offset += 512 + (ceil($file_size / 512) * 512);
258: }
259:
260: return true;
261: }
262:
263: /**
264: * Read a non gzipped tar file in for processing.
265: *
266: * @param string $filename full filename
267: * @return bool always TRUE
268: *
269: * @access private
270: **/
271: function __readTar($filename='')
272: {
273: // Set the filename to load
274: if(!$filename)
275: $filename = $this->filename;
276:
277: // Read in the TAR file
278: $fp = fopen($filename,"rb");
279: $this->tar_file = fread($fp,filesize($filename));
280: fclose($fp);
281:
282: if($this->tar_file[0] == chr(31) && $this->tar_file[1] == chr(139) && $this->tar_file[2] == chr(8)) {
283: if(!function_exists("gzinflate"))
284: return false;
285:
286: $this->isGzipped = true;
287:
288: $this->tar_file = gzinflate(substr($this->tar_file,10,-4));
289: }
290:
291: // Parse the TAR file
292: $this->__parseTar();
293:
294: return true;
295: }
296:
297: /**
298: * Generates a TAR file from the processed data
299: *
300: * @return bool always TRUE
301: *
302: * @access private
303: **/
304: function __generateTAR()
305: {
306: // Clear any data currently in $this->tar_file
307: unset($this->tar_file);
308:
309: // Generate Records for each directory, if we have directories
310: if($this->numDirectories > 0) {
311: foreach($this->directories as $key => $information) {
312: unset($header);
313:
314: // Generate tar header for this directory
315: // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
316: $header .= str_pad($information["name"],100,chr(0));
317: $header .= str_pad(decoct($information["mode"]),7,"0",STR_PAD_LEFT) . chr(0);
318: $header .= str_pad(decoct($information["user_id"]),7,"0",STR_PAD_LEFT) . chr(0);
319: $header .= str_pad(decoct($information["group_id"]),7,"0",STR_PAD_LEFT) . chr(0);
320: $header .= str_pad(decoct(0),11,"0",STR_PAD_LEFT) . chr(0);
321: $header .= str_pad(decoct($information["time"]),11,"0",STR_PAD_LEFT) . chr(0);
322: $header .= str_repeat(" ",8);
323: $header .= "5";
324: $header .= str_repeat(chr(0),100);
325: $header .= str_pad("ustar",6,chr(32));
326: $header .= chr(32) . chr(0);
327: $header .= str_pad("",32,chr(0));
328: $header .= str_pad("",32,chr(0));
329: $header .= str_repeat(chr(0),8);
330: $header .= str_repeat(chr(0),8);
331: $header .= str_repeat(chr(0),155);
332: $header .= str_repeat(chr(0),12);
333:
334: // Compute header checksum
335: $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)),6,"0",STR_PAD_LEFT);
336: for($i=0; $i<6; $i++) {
337: $header[(148 + $i)] = substr($checksum,$i,1);
338: }
339: $header[154] = chr(0);
340: $header[155] = chr(32);
341:
342: // Add new tar formatted data to tar file contents
343: $this->tar_file .= $header;
344: }
345: }
346:
347: // Generate Records for each file, if we have files (We should...)
348: if($this->numFiles > 0) {
349: $this->tar_file = '';
350: foreach($this->files as $key => $information) {
351: unset($header);
352:
353: // Generate the TAR header for this file
354: // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
355: $header = str_pad($information["name"],100,chr(0));
356: $header .= str_pad(decoct($information["mode"]),7,"0",STR_PAD_LEFT) . chr(0);
357: $header .= str_pad(decoct($information["user_id"]),7,"0",STR_PAD_LEFT) . chr(0);
358: $header .= str_pad(decoct($information["group_id"]),7,"0",STR_PAD_LEFT) . chr(0);
359: $header .= str_pad(decoct($information["size"]),11,"0",STR_PAD_LEFT) . chr(0);
360: $header .= str_pad(decoct($information["time"]),11,"0",STR_PAD_LEFT) . chr(0);
361: $header .= str_repeat(" ",8);
362: $header .= "0";
363: $header .= str_repeat(chr(0),100);
364: $header .= str_pad("ustar",6,chr(32));
365: $header .= chr(32) . chr(0);
366: $header .= str_pad($information["user_name"],32,chr(0)); // How do I get a file's user name from PHP?
367: $header .= str_pad($information["group_name"],32,chr(0)); // How do I get a file's group name from PHP?
368: $header .= str_repeat(chr(0),8);
369: $header .= str_repeat(chr(0),8);
370: $header .= str_repeat(chr(0),155);
371: $header .= str_repeat(chr(0),12);
372:
373: // Compute header checksum
374: $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)),6,"0",STR_PAD_LEFT);
375: for($i=0; $i<6; $i++) {
376: $header[(148 + $i)] = substr($checksum,$i,1);
377: }
378: $header[154] = chr(0);
379: $header[155] = chr(32);
380:
381: // Pad file contents to byte count divisible by 512
382: $file_contents = str_pad($information["file"],(ceil($information["size"] / 512) * 512),chr(0));
383:
384: // Add new tar formatted data to tar file contents
385: $this->tar_file .= $header . $file_contents;
386: }
387: }
388:
389: // Add 512 bytes of NULLs to designate EOF
390: $this->tar_file .= str_repeat(chr(0),512);
391:
392: return true;
393: }
394:
395:
396: /**
397: * Open a TAR file
398: *
399: * @param string $filename
400: * @return bool
401: **/
402: function openTAR($filename)
403: {
404: // Clear any values from previous tar archives
405: unset($this->filename);
406: unset($this->isGzipped);
407: unset($this->tar_file);
408: unset($this->files);
409: unset($this->directories);
410: unset($this->numFiles);
411: unset($this->numDirectories);
412:
413: // If the tar file doesn't exist...
414: if(!file_exists($filename))
415: return false;
416:
417: $this->filename = $filename;
418:
419: // Parse this file
420: $this->__readTar();
421:
422: return true;
423: }
424:
425: /**
426: * Appends a tar file to the end of the currently opened tar file.
427: *
428: * @param string $filename
429: * @return bool
430: **/
431: function appendTar($filename)
432: {
433: // If the tar file doesn't exist...
434: if(!file_exists($filename))
435: return false;
436:
437: $this->__readTar($filename);
438:
439: return true;
440: }
441:
442: /**
443: * Retrieves information about a file in the current tar archive
444: *
445: * @param string $filename
446: * @return string FALSE on fail
447: **/
448: function getFile($filename)
449: {
450: if ( $this->numFiles > 0 ) {
451: foreach($this->files as $key => $information) {
452: if($information["name"] == $filename)
453: return $information;
454: }
455: }
456:
457: return false;
458: }
459:
460: /**
461: * Retrieves information about a directory in the current tar archive
462: *
463: * @param string $dirname
464: * @return string FALSE on fail
465: **/
466: function getDirectory($dirname)
467: {
468: if($this->numDirectories > 0) {
469: foreach($this->directories as $key => $information) {
470: if($information["name"] == $dirname)
471: return $information;
472: }
473: }
474:
475: return false;
476: }
477:
478: /**
479: * Check if this tar archive contains a specific file
480: *
481: * @param string $filename
482: * @return bool
483: **/
484: function containsFile($filename)
485: {
486: if ( $this->numFiles > 0 ) {
487: foreach($this->files as $key => $information) {
488: if($information["name"] == $filename)
489: return true;
490: }
491: }
492: return false;
493: }
494:
495: /**
496: * Check if this tar archive contains a specific directory
497: *
498: * @param string $dirname
499: * @return bool
500: **/
501: function containsDirectory($dirname)
502: {
503: if ( $this->numDirectories > 0 ) {
504: foreach ( $this->directories as $key => $information ) {
505: if ( $information["name"] == $dirname ) {
506: return true;
507: }
508: }
509: }
510: return false;
511: }
512:
513: /**
514: * Add a directory to this tar archive
515: *
516: * @param string $dirname
517: * @return bool
518: **/
519: function addDirectory($dirname)
520: {
521: if ( !file_exists($dirname) ) {
522: return false;
523: }
524:
525: // Get directory information
526: $file_information = stat($dirname);
527:
528: // Add directory to processed data
529: $this->numDirectories++;
530: $activeDir = &$this->directories[];
531: $activeDir["name"] = $dirname;
532: $activeDir["mode"] = $file_information["mode"];
533: $activeDir["time"] = $file_information["time"];
534: $activeDir["user_id"] = $file_information["uid"];
535: $activeDir["group_id"] = $file_information["gid"];
536: $activeDir["checksum"] = $checksum;
537:
538: return true;
539: }
540:
541: /**
542: * Add a file to the tar archive
543: *
544: * @param string $filename
545: * @param boolean $binary Binary file?
546: * @return bool
547: **/
548: function addFile($filename, $binary = false)
549: {
550: // Make sure the file we are adding exists!
551: if ( !file_exists($filename) ) {
552: return false;
553: }
554:
555: // Make sure there are no other files in the archive that have this same filename
556: if ( $this->containsFile($filename) ) {
557: return false;
558: }
559:
560: // Get file information
561: $file_information = stat($filename);
562:
563: // Read in the file's contents
564: if (!$binary) {
565: $fp = fopen($filename, "r");
566: } else {
567: $fp = fopen($filename, "rb");
568: }
569: $file_contents = filesize($filename) ? fread($fp,filesize($filename)) : '' ;
570: fclose($fp);
571:
572: // Add file to processed data
573: $this->numFiles++;
574: $activeFile = &$this->files[];
575: $activeFile["name"] = $filename;
576: $activeFile["mode"] = $file_information["mode"];
577: $activeFile["user_id"] = $file_information["uid"];
578: $activeFile["group_id"] = $file_information["gid"];
579: $activeFile["size"] = $file_information["size"];
580: $activeFile["time"] = $file_information["mtime"];
581: $activeFile["checksum"] = isset($checksum) ? $checksum : '';
582: $activeFile["user_name"] = "";
583: $activeFile["group_name"] = "";
584: // "trim" May be needless. by nao-pon
585: //$activeFile["file"] = trim($file_contents);
586: $activeFile["file"] = $file_contents;
587:
588: return true;
589: }
590:
591: /**
592: * Remove a file from the tar archive
593: *
594: * @param string $filename
595: * @return bool
596: **/
597: function removeFile($filename)
598: {
599: if ( $this->numFiles > 0 ) {
600: foreach ( $this->files as $key => $information ) {
601: if ( $information["name"] == $filename ) {
602: $this->numFiles--;
603: unset($this->files[$key]);
604: return true;
605: }
606: }
607: }
608: return false;
609: }
610:
611: /**
612: * Remove a directory from the tar archive
613: *
614: * @param string $dirname
615: * @return bool
616: **/
617: function removeDirectory($dirname)
618: {
619: if ( $this->numDirectories > 0 ) {
620: foreach ( $this->directories as $key => $information ) {
621: if ( $information["name"] == $dirname ) {
622: $this->numDirectories--;
623: unset($this->directories[$key]);
624: return true;
625: }
626: }
627: }
628: return false;
629: }
630:
631: /**
632: * Write the currently loaded tar archive to disk
633: *
634: * @return bool
635: **/
636: function saveTar()
637: {
638: if ( !$this->filename ) {
639: return false;
640: }
641:
642: // Write tar to current file using specified gzip compression
643: $this->toTar($this->filename,$this->isGzipped);
644:
645: return true;
646: }
647:
648: /**
649: * Saves tar archive to a different file than the current file
650: *
651: * @param string $filename
652: * @param bool $useGzip Use GZ compression?
653: * @return bool
654: **/
655: function toTar($filename,$useGzip)
656: {
657: if ( !$filename ) {
658: return false;
659: }
660:
661: // Encode processed files into TAR file format
662: $this->__generateTar();
663:
664: // GZ Compress the data if we need to
665: if ( $useGzip ) {
666: // Make sure we have gzip support
667: if ( !function_exists("gzencode") ) {
668: return false;
669: }
670:
671: $file = gzencode($this->tar_file);
672: } else {
673: $file = $this->tar_file;
674: }
675:
676: // Write the TAR file
677: $fp = fopen($filename,"wb");
678: fwrite($fp,$file);
679: fclose($fp);
680:
681: return true;
682: }
683:
684: /**
685: * Sends tar archive to stdout
686: *
687: * @param string $filename
688: * @param bool $useGzip Use GZ compression?
689: * @return string
690: **/
691: function toTarOutput($filename,$useGzip)
692: {
693: if ( !$filename ) {
694: return false;
695: }
696:
697: // Encode processed files into TAR file format
698: $this->__generateTar();
699:
700: // GZ Compress the data if we need to
701: if ( $useGzip ) {
702: // Make sure we have gzip support
703: if ( !function_exists("gzencode") ) {
704: return false;
705: }
706:
707: $file = gzencode($this->tar_file);
708: } else {
709: $file = $this->tar_file;
710: }
711:
712: return $file;
713: }
714: }
715:
716: ?>