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