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