Creating repository for dokuwiki modifications for sudaraka.org
[sudaraka-org:dokuwiki-mods.git] / inc / TarLib.class.php
1 <?php
2 /**
3  * TAR format class - Creates TAR archives
4  *
5  * This class is part or the MaxgComp suite and originally named
6  * MaxgTar class.
7  *
8  * Modified for Dokuwiki
9  *
10  * @license LGPL-2.1
11  * @link    http://docs.maxg.info
12  * @author  Bouchon <tarlib@bouchon.org> (Maxg)
13  * @author  Christopher Smith <chris@jalakai.co.uk>
14  */
15
16
17 /**
18  * Those constants represent the compression method to use.
19  * COMPRESS_GZIP is used for the GZIP compression; COMPRESS_BZIP for
20  * BZIP2 and COMPRESS_NONE for no compression.
21  *
22  * On the other hand, COMPRESS_AUTO is a bit harder. It will first check
23  * if the zlib extensions are loaded.
24  * If it is, GZIP will be used. Else it will check if the bz2 extensions
25  * are loaded. If it is, BZIP2 will be used. Else no compression will be
26  * performed.
27  *
28  * You can then use getCompression() to know the compression chosen.
29  *
30  * If you selected a compression which can't be used (i.e extension not
31  * present), it will be just disabled, and won't produce any error !
32  * As a consequence, getCompression() will return COMPRESS_NONE
33  *
34  * ARCHIVE_DYNAMIC can be passed as the first argument of the constructor, to
35  * create an archive in memory instead of a file. See also: MaxgTar(),
36  * getDynamicArchive() and writeArchive()
37  *
38  * ARCHIVE_RENAMECOMP is a flag that can be multiplied by the compression method
39  * (i.e COMPRESS_AUTO * ARCHIVE_RENAMECOMP). This will add the correct extension
40  * to the archive name, which is useful with COMPRESS_AUTO, ie .bz2 if you gave
41  * COMPRESS_BZIP. See also getCompression(TRUE) which does exactly the
42  * same
43  *
44  * COMPRESS_DETECT does exactly the opposite and try to detect the
45  * compression to use to read the archive depending on its extension. (i.e if
46  * the archive ends with .tar.gz TarLib will try to decompress it with
47  * GZIP). See also setCompression()
48  *
49  * FULL_ARCHIVE is a -1 constant that means "the complete archive" when
50  * extracting. This is explained in Extract()
51  */
52 #define('COMPRESS_GZIP',1);
53 #define('COMPRESS_BZIP',2);
54 #define('COMPRESS_AUTO',3);
55 #define('COMPRESS_NONE',0);
56 #define('TARLIB_VERSION','1.2');
57 #define('FULL_ARCHIVE',-1);
58 #define('ARCHIVE_DYNAMIC',0);
59 #define('ARCHIVE_RENAMECOMP',5);
60 #define('COMPRESS_DETECT',-1);
61
62 class TarLib {
63     var $_comptype;
64     var $_compzlevel;
65     var $_fp;
66     var $_memdat;
67     var $_nomf;
68     var $_result;
69     var $_initerror;
70
71     const   COMPRESS_GZIP = 1;
72     const   COMPRESS_BZIP = 2;
73     const   COMPRESS_AUTO = 3;
74     const   COMPRESS_NONE = 0;
75     const   TARLIB_VERSION = '1.2';
76     const   FULL_ARCHIVE = -1;
77     const   ARCHIVE_DYNAMIC = 0;
78     const   ARCHIVE_RENAMECOMP = 5;
79     const   COMPRESS_DETECT = -1;
80
81     /**
82      * constructor, initialize the class
83      *
84      * The constructor initialize the variables and prepare the class for the
85      * archive, and return the object created. Note that you can use multiple
86      * instances of the MaxgTar class, if you call this function another time and
87      * store the object in an other variable.
88      *
89      * In fact, MaxgTar accepts the following arguments (all are optional) :
90      *
91      * filename can be either a file name (absolute or relative). In this
92      * case, it can be used both for reading and writing. You can also open
93      * remote archive if you add a protocole name at the beginning of the file
94      * (ie https://host.dom/archive.tar.gz), but for reading only and if the
95      * directive allow_url_fopen is enabled in PHP.INI (this can be checked with
96      * TarInfo()). If you pass a file that doesn't exist, the script
97      * will try to create it. If the archive already exists and contains files,
98      * you can use Add() to append files.But by default this parameter
99      * is ARCHIVE_DYNAMIC (write only) so the archive is created in memory and
100      * can be sent to a file [writeArchive()] or to the client
101      * [sendClient()]
102      *
103      * compression_type should be a constant that represents a type of
104      * compression, or its integer value. The different values are described in
105      * the constants.
106      *
107      * compression_level is an integer between 1 and 9 (by default) an
108      * represent the GZIP or BZIP compression level.  1 produce fast compression,
109      * and 9 produce smaller files. See the RFC 1952 for more infos.
110      */
111     function tarlib($p_filen = TarLib::ARCHIVE_DYNAMIC , $p_comptype = TarLib::COMPRESS_AUTO, $p_complevel = 9) {
112         $this->_initerror = 0;
113         $this->_nomf = $p_filen;
114         $flag=0;
115         if($p_comptype && $p_comptype % 5 == 0){
116             $p_comptype /= TarLib::ARCHIVE_RENAMECOMP;
117             $flag=1;
118         }
119
120         if($p_complevel > 0 && $p_complevel <= 9) $this->_compzlevel = $p_complevel;
121         else $p_complevel = 9;
122
123         if($p_comptype == TarLib::COMPRESS_DETECT) {
124             if(strtolower(substr($p_filen,-3)) == '.gz') $p_comptype = TarLib::COMPRESS_GZIP;
125             elseif(strtolower(substr($p_filen,-4)) == '.bz2') $p_comptype = TarLib::COMPRESS_BZIP;
126             else $p_comptype = TarLib::COMPRESS_NONE;
127         }
128
129         switch($p_comptype) {
130         case TarLib::COMPRESS_GZIP:
131                 if(!extension_loaded('zlib')) $this->_initerror = -1;
132                 $this->_comptype = TarLib::COMPRESS_GZIP;
133                 break;
134
135             case TarLib::COMPRESS_BZIP:
136                 if(!extension_loaded('bz2')) $this->_initerror = -2;
137                 $this->_comptype = TarLib::COMPRESS_BZIP;
138                 break;
139
140             case TarLib::COMPRESS_AUTO:
141                 if(extension_loaded('zlib'))
142                     $this->_comptype = TarLib::COMPRESS_GZIP;
143                 elseif(extension_loaded('bz2'))
144                     $this->_comptype = TarLib::COMPRESS_BZIP;
145                 else
146                     $this->_comptype = TarLib::COMPRESS_NONE;
147                 break;
148
149             default:
150                 $this->_comptype = TarLib::COMPRESS_NONE;
151         }
152
153         if($this->_initerror < 0) $this->_comptype = TarLib::COMPRESS_NONE;
154
155         if($flag) $this->_nomf.= '.'.$this->getCompression(1);
156         $this->_result = true;
157     }
158
159     /**
160      * Recycle a TAR object.
161      *
162      * This function does exactly the same as TarLib (constructor), except it
163      * returns a status code.
164      */
165     function setArchive($p_name='', $p_comp = TarLib::COMPRESS_AUTO, $p_level=9) {
166         $this->_CompTar();
167         $this->TarLib($p_name, $p_comp, $p_level);
168         return $this->_result;
169     }
170
171     /**
172      * Get the compression used to generate the archive
173      *
174      * This is a very useful function when you're using dynamical archives.
175      * Besides, if you let the script chose which compression to use, you'll have
176      * a problem when you'll want to send it to the client if you don't know
177      * which compression was used.
178      *
179      * There are two ways to call this function : if you call it without argument
180      * or with FALSE, it will return the compression constants, explained on the
181      * MaxgTar Constants.  If you call it with GetExtension on TRUE it will
182      * return the extension without starting dot (ie "tar" or "tar.bz2" or
183      * "tar.gz")
184      *
185      * NOTE: This can be done with the flag ARCHIVE_RENAMECOMP, see the
186      * MaxgTar Constants
187      */
188     function getCompression($ext = false) {
189         $exts = Array('tar','tar.gz','tar.bz2');
190         if($ext) return $exts[$this->_comptype];
191         return $this->_comptype;
192     }
193
194     /**
195      * Change the compression mode.
196      *
197      * This function will change the compression methode to read or write
198      * the archive. See the MaxgTar Constants to see which constants you can use.
199      * It may look strange, but it returns the GZIP compression level.
200      */
201     function setCompression($p_comp = TarLib::COMPRESS_AUTO) {
202         $this->setArchive($this->_nomf, $p_comp, $this->_compzlevel);
203         return $this->_compzlevel;
204     }
205
206     /**
207      * Returns the compressed dynamic archive.
208      *
209      * When you're working with dynamic archives, use this function to grab
210      * the final compressed archive in a string ready to be put in a SQL table or
211      * in a file.
212      */
213     function getDynamicArchive() {
214         return $this->_encode($this->_memdat);
215     }
216
217     /**
218      * Write a dynamical archive into a file
219      *
220      * This function attempts to write a dynamicaly-genrated archive into
221      * TargetFile on the webserver.  It returns a TarErrorStr() status
222      * code.
223      *
224      * To know the extension to add to the file if you're using AUTO_DETECT
225      * compression, you can use getCompression().
226      */
227     function writeArchive($p_archive) {
228         if(!$this->_memdat) return -7;
229         $fp = @fopen($p_archive, 'wb');
230         if(!$fp) return -6;
231
232         fwrite($fp, $this->_memdat);
233         fclose($fp);
234
235         return true;
236     }
237
238     /**
239      * Send a TAR archive to the client browser.
240      *
241      * This function will send an archive to the client, and return a status
242      * code, but can behave differently depending on the arguments you give. All
243      * arguments are optional.
244      *
245      * ClientName is used to specify the archive name to give to the browser. If
246      * you don't give one, it will send the constructor filename or return an
247      * error code in case of dynamical archive.
248      *
249      * FileName is optional and used to send a specific archive. Leave it blank
250      * to send dynamical archives or the current working archive.
251      *
252      * If SendHeaders is enabled (by default), the library will send the HTTP
253      * headers itself before it sends the contents. This headers are :
254      * Content-Type, Content-Disposition, Content-Length and Accept-Range.
255      *
256      * Please note that this function DOES NOT stops the script so don't forget
257      * to exit() to avoid your script sending other data and corrupt the archive.
258      * Another note : for AUTO_DETECT dynamical archives you can know the
259      * extension to add to the name with getCompression()
260      */
261     function sendClient($name = '', $archive = '', $headers = true) {
262         if(!$name && !$this->_nomf) return -9;
263         if(!$archive && !$this->_memdat) return -10;
264         if(!$name) $name = basename($this->_nomf);
265
266         if($archive){ if(!file_exists($archive)) return -11; }
267         else $decoded = $this->getDynamicArchive();
268
269         if($headers) {
270             header('Content-Type: application/x-gtar');
271             header('Content-Disposition: attachment; filename='.basename($name));
272             header('Accept-Ranges: bytes');
273             header('Content-Length: '.($archive ? filesize($archive) : strlen($decoded)));
274         }
275
276         if($archive) {
277             $fp = @fopen($archive,'rb');
278             if(!$fp) return -4;
279
280             while(!feof($fp)) echo fread($fp,2048);
281         } else {
282             echo $decoded;
283         }
284
285         return true;
286     }
287
288     /**
289      * Extract part or totality of the archive.
290      *
291      * This function can extract files from an archive, and returns then a
292      * status codes that can be converted with TarErrorStr() into a
293      * human readable message.
294      *
295      * Only the first argument is required, What and it can be either the
296      * constant FULL_ARCHIVE or an indexed array containing each file you want to
297      * extract.
298      *
299      * To contains the target folder to extract the archive. It is optional and
300      * the default value is '.' which means the current folder. If the target
301      * folder doesn't exist, the script attempts to create it and give it
302      * permissions 0777 by default.
303      *
304      * RemovePath is very usefull when you want to extract files from a subfoler
305      * in the archive to a root folder. For instance, if you have a file in the
306      * archive called some/sub/folder/test.txt and you want to extract it to the
307      * script folder, you can call Extract with To = '.' and RemovePath =
308      * 'some/sub/folder/'
309      *
310      * FileMode is optional and its default value is 0755. It is in fact the UNIX
311      * permission in octal mode (prefixed with a 0) that will be given on each
312      * extracted file.
313      */
314     function Extract($p_what = TarLib::FULL_ARCHIVE, $p_to = '.', $p_remdir='', $p_mode = 0755) {
315         if(!$this->_OpenRead()) return -4;
316         //  if(!@is_dir($p_to)) if(!@mkdir($p_to, 0777)) return -8;   --CS
317         if(!@is_dir($p_to)) if(!$this->_dirApp($p_to)) return -8;   //--CS (route through correct dir fn)
318
319         $ok = $this->_extractList($p_to, $p_what, $p_remdir, $p_mode);
320         $this->_CompTar();
321
322         return $ok;
323     }
324
325     /**
326      * Create a new package with the given files
327      *
328      * This function will attempt to create a new archive with global headers
329      * then add the given files into.  If the archive is a real file, the
330      * contents are written directly into the file, if it is a dynamic archive
331      * contents are only stored in memory. This function should not be used to
332      * add files to an existing archive, you should use Add() instead.
333      *
334      * The FileList actually supports three different modes :
335      *
336      * - You can pass a string containing filenames separated by pipes '|'.
337      *   In this case the file are read from the webserver filesystem and the
338      *   root folder is the folder where the script using the MaxgTar is called.
339      *
340      * - You can also give a unidimensional indexed array containing the
341      *   filenames. The behaviour for the content reading is the same that a
342      *   '|'ed string.
343      *
344      * - The more useful usage is to pass bidimensional arrays, where the
345      *   first element contains the filename and the second contains the file
346      *   contents. You can even add empty folders to the package if the filename
347      *   has a leading '/'. Once again, have a look at the exemples to understand
348      *   better.
349      *
350      * Note you can also give arrays with both dynamic contents and static files.
351      *
352      * The optional parameter RemovePath can be used to delete a part of the tree
353      * of the filename you're adding, for instance if you're adding in the root
354      * of a package a file that is stored somewhere in the server tree.
355      *
356      * On the contrary the parameter AddPath can be used to add a prefix folder
357      * to the file you store. Note also that the RemovePath is applied before the
358      * AddPath is added, so it HAS a sense to use both parameters together.
359      */
360     function Create($p_filelist,$p_add='',$p_rem='') {
361         if(!$fl = $this->_fetchFilelist($p_filelist)) return -5;
362         if(!$this->_OpenWrite()) return -6;
363
364         $ok = $this->_addFileList($fl,$p_add,$p_rem);
365
366         if($ok){
367             $this->_writeFooter();
368         }else{
369             $this->_CompTar();
370             @unlink($this->_nomf);
371         }
372
373         return $ok;
374     }
375
376     /**
377      * Add files to an existing package.
378      *
379      * This function does exactly the same than Create() exept it
380      * will append the given files at the end of the archive.  Please not the is
381      * safe to call Add() on a newly created archive whereas the
382      * contrary will fail !
383      *
384      * This function returns a status code, you can use TarErrorStr() on
385      * it to get the human-readable description of the error.
386      */
387     function Add($p_filelist, $p_add = '', $p_rem = '') {
388         if (($this->_nomf != TarLib::ARCHIVE_DYNAMIC && @is_file($this->_nomf)) ||
389             ($this->_nomf == TarLib::ARCHIVE_DYNAMIC && !$this->_memdat)){
390              return $this->Create($p_filelist, $p_add, $p_rem);
391         }
392
393         if(!$fl = $this->_fetchFilelist($p_filelist)) return -5;
394         return $this->_append($fl, $p_add, $p_rem);
395     }
396
397     /**
398      * Read the contents of a TAR archive
399      *
400      * This function attempts to get the list of the files stored in the
401      * archive, and return either an error code or an indexed array of
402      * associative array containing for each file the following information :
403      *
404      * checksum    Tar Checksum of the file
405      * filename    The full name of the stored file (up to 100 c.)
406      * mode        UNIX permissions in DECIMAL, not octal
407      * uid         The Owner ID
408      * gid         The Group ID
409      * size        Uncompressed filesize
410      * mtime       Timestamp of last modification
411      * typeflag    Empty for files, set for folders
412      * link        For the links, did you guess it ?
413      * uname       Owner name
414      * gname       Group name
415      */
416     function ListContents() {
417         if(!$this->_nomf) return -3;
418         if(!$this->_OpenRead()) return -4;
419
420         $result = Array();
421
422         while ($dat = $this->_read(512)) {
423             $dat = $this->_readHeader($dat);
424             if(!is_array($dat)) continue;
425
426             $this->_seek(ceil($dat['size']/512)*512,1);
427             $result[] = $dat;
428         }
429
430         return  $result;
431     }
432
433     /**
434      * Convert a status code into a human readable message
435      *
436      * Some MaxgTar functions like Create(), Add() ... return numerical
437      * status code.  You can pass them to this function to grab their english
438      * equivalent.
439      */
440     function TarErrorStr($i) {
441         $ecodes = Array(
442                 1 => true,
443                 0 => "Undocumented error",
444                 -1 => "Can't use COMPRESS_GZIP compression : ZLIB extensions are not loaded !",
445                 -2 => "Can't use COMPRESS_BZIP compression : BZ2 extensions are not loaded !",
446                 -3 => "You must set a archive file to read the contents !",
447                 -4 => "Can't open the archive file for read !",
448                 -5 => "Invalide file list !",
449                 -6 => "Can't open the archive in write mode !",
450                 -7 => "There is no ARCHIVE_DYNAMIC to write !",
451                 -8 => "Can't create the directory to extract files !",
452                 -9 => "Please pass a archive name to send if you made created an ARCHIVE_DYNAMIC !",
453                 -10 => "You didn't pass an archive filename and there is no stored ARCHIVE_DYNAMIC !",
454                 -11 => "Given archive doesn't exist !"
455                 );
456
457         return isset($ecodes[$i]) ? $ecodes[$i] : $ecodes[0];
458     }
459
460     function _seek($p_flen, $tell=0) {
461         if($this->_nomf === TarLib::ARCHIVE_DYNAMIC)
462             $this->_memdat=substr($this->_memdat,0,($tell ? strlen($this->_memdat) : 0) + $p_flen);
463         elseif($this->_comptype == TarLib::COMPRESS_GZIP)
464             @gzseek($this->_fp, ($tell ? @gztell($this->_fp) : 0)+$p_flen);
465         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
466             @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen);
467         else
468             @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen);
469     }
470
471     function _OpenRead() {
472         if($this->_comptype == TarLib::COMPRESS_GZIP)
473             $this->_fp = @gzopen($this->_nomf, 'rb');
474         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
475             $this->_fp = @bzopen($this->_nomf, 'rb');
476         else
477             $this->_fp = @fopen($this->_nomf, 'rb');
478
479         return ($this->_fp ? true : false);
480     }
481
482     function _OpenWrite($add = 'w') {
483         if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) return true;
484
485         if($this->_comptype == TarLib::COMPRESS_GZIP)
486             $this->_fp = @gzopen($this->_nomf, $add.'b'.$this->_compzlevel);
487         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
488             $this->_fp = @bzopen($this->_nomf, $add.'b');
489         else
490             $this->_fp = @fopen($this->_nomf, $add.'b');
491
492         return ($this->_fp ? true : false);
493     }
494
495     function _CompTar() {
496         if($this->_nomf === TarLib::ARCHIVE_DYNAMIC || !$this->_fp) return;
497
498         if($this->_comptype == TarLib::COMPRESS_GZIP) @gzclose($this->_fp);
499         elseif($this->_comptype == TarLib::COMPRESS_BZIP) @bzclose($this->_fp);
500         else @fclose($this->_fp);
501     }
502
503     function _read($p_len) {
504         if($this->_comptype == TarLib::COMPRESS_GZIP)
505             return @gzread($this->_fp,$p_len);
506         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
507             return @bzread($this->_fp,$p_len);
508         else
509             return @fread($this->_fp,$p_len);
510     }
511
512     function _write($p_data) {
513         if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) $this->_memdat .= $p_data;
514         elseif($this->_comptype == TarLib::COMPRESS_GZIP)
515             return @gzwrite($this->_fp,$p_data);
516
517         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
518             return @bzwrite($this->_fp,$p_data);
519
520         else
521             return @fwrite($this->_fp,$p_data);
522     }
523
524     function _encode($p_dat) {
525         if($this->_comptype == TarLib::COMPRESS_GZIP)
526             return gzencode($p_dat, $this->_compzlevel);
527         elseif($this->_comptype == TarLib::COMPRESS_BZIP)
528             return bzcompress($p_dat, $this->_compzlevel);
529         else return $p_dat;
530     }
531
532     function _readHeader($p_dat) {
533         if (!$p_dat || strlen($p_dat) != 512) return false;
534
535         for ($i=0, $chks=0; $i<148; $i++)
536             $chks += ord($p_dat[$i]);
537
538         for ($i=156,$chks+=256; $i<512; $i++)
539             $chks += ord($p_dat[$i]);
540
541         $headers = @unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $p_dat);
542         if(!$headers) return false;
543
544         $return['checksum'] = OctDec(trim($headers['checksum']));
545         if ($return['checksum'] != $chks) return false;
546
547         $return['filename'] = trim($headers['filename']);
548         $return['mode'] = OctDec(trim($headers['mode']));
549         $return['uid'] = OctDec(trim($headers['uid']));
550         $return['gid'] = OctDec(trim($headers['gid']));
551         $return['size'] = OctDec(trim($headers['size']));
552         $return['mtime'] = OctDec(trim($headers['mtime']));
553         $return['typeflag'] = $headers['typeflag'];
554         $return['link'] = trim($headers['link']);
555         $return['uname'] = trim($headers['uname']);
556         $return['gname'] = trim($headers['gname']);
557
558         return $return;
559     }
560
561     function _fetchFilelist($p_filelist) {
562         if(!$p_filelist || (is_array($p_filelist) && !@count($p_filelist))) return false;
563
564         if(is_string($p_filelist)) {
565             $p_filelist = explode('|',$p_filelist);
566             if(!is_array($p_filelist)) $p_filelist = Array($p_filelist);
567         }
568
569         return $p_filelist;
570     }
571
572     function _addFileList($p_fl, $p_addir, $p_remdir) {
573         foreach($p_fl as $file) {
574             if(($file == $this->_nomf && $this->_nomf != TarLib::ARCHIVE_DYNAMIC) || !$file || (!file_exists($file) && !is_array($file)))
575                 continue;
576
577             if (!$this->_addFile($file, $p_addir, $p_remdir))
578                 continue;
579
580             if (@is_dir($file)) {
581                 $d = @opendir($file);
582
583                 if(!$d) continue;
584                 readdir($d);
585                 readdir($d);
586
587                 while($f = readdir($d)) {
588                     if($file != ".") $tmplist[0] = "$file/$f";
589                     else $tmplist[0] = $d;
590
591                     $this->_addFileList($tmplist, $p_addir, $p_remdir);
592                 }
593
594                 closedir($d);
595                 unset($tmplist,$f);
596             }
597         }
598         return true;
599     }
600
601     function _addFile($p_fn, $p_addir = '', $p_remdir = '') {
602         if(is_array($p_fn)) list($p_fn, $data) = $p_fn;
603         $sname = $p_fn;
604
605         if($p_remdir) {
606             if(substr($p_remdir,-1) != '/') $p_remdir .= "/";
607
608             if(substr($sname, 0, strlen($p_remdir)) == $p_remdir)
609                 $sname = substr($sname, strlen($p_remdir));
610         }
611
612         if($p_addir) $sname = $p_addir.(substr($p_addir,-1) == '/' ? '' : "/").$sname;
613
614         if(strlen($sname) > 99) return;
615
616         if(@is_dir($p_fn)) {
617             if(!$this->_writeFileHeader($p_fn, $sname)) return false;
618         } else {
619             if(!$data) {
620                 $fp = fopen($p_fn, 'rb');
621                 if(!$fp) return false;
622             }
623
624             if(!$this->_writeFileHeader($p_fn, $sname, ($data ? strlen($data) : false))) return false;
625
626             if(!$data) {
627                 while(!feof($fp)) {
628                     $packed = pack("a512", fread($fp,512));
629                     $this->_write($packed);
630                 }
631                 fclose($fp);
632             } else {
633                 $len = strlen($data);
634                 for($s = 0; $s < $len; $s += 512){
635                     $this->_write(pack("a512",substr($data,$s,512)));
636                 }
637             }
638         }
639
640         return true;
641     }
642
643     function _writeFileHeader($p_file, $p_sname, $p_data=false) {
644         if(!$p_data) {
645             if (!$p_sname) $p_sname = $p_file;
646             $p_sname = $this->_pathTrans($p_sname);
647
648             $h_info = stat($p_file);
649             $h[0] = sprintf("%6s ", DecOct($h_info[4]));
650             $h[] = sprintf("%6s ", DecOct($h_info[5]));
651             $h[] = sprintf("%6s ", DecOct(fileperms($p_file)));
652             clearstatcache();
653             $h[] = sprintf("%11s ", DecOct(filesize($p_file)));
654             $h[] = sprintf("%11s", DecOct(filemtime($p_file)));
655
656             $dir = @is_dir($p_file) ? '5' : '';
657         } else {
658             $dir = '';
659             $p_data = sprintf("%11s ", DecOct($p_data));
660             $time = sprintf("%11s ", DecOct(time()));
661             $h = Array("     0 ","     0 "," 40777 ",$p_data,$time);
662         }
663
664         $data_first = pack("a100a8a8a8a12A12", $p_sname, $h[2], $h[0], $h[1], $h[3], $h[4]);
665         $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $dir, '', '', '', '', '', '', '', '', "");
666
667         for ($i=0,$chks=0; $i<148; $i++)
668             $chks += ord($data_first[$i]);
669
670         for ($i=156, $chks+=256, $j=0; $i<512; $i++, $j++)
671             $chks += ord($data_last[$j]);
672
673         $this->_write($data_first);
674
675         $chks = pack("a8",sprintf("%6s ", DecOct($chks)));
676         $this->_write($chks.$data_last);
677
678         return true;
679     }
680
681     function _append($p_filelist, $p_addir="", $p_remdir="") {
682         if(!$this->_fp) if(!$this->_OpenWrite('a')) return -6;
683
684         if($this->_nomf == TarLib::ARCHIVE_DYNAMIC) {
685             $s = strlen($this->_memdat);
686             $this->_memdat = substr($this->_memdat,0,-512);
687         } else {
688             $s = filesize($this->_nomf);
689             $this->_seek($s-512);
690         }
691
692         $ok = $this->_addFileList($p_filelist, $p_addir, $p_remdir);
693         $this->_writeFooter();
694
695         return $ok;
696     }
697
698     function _pathTrans($p_dir) {
699         if ($p_dir) {
700             $subf = explode("/", $p_dir);
701             $r='';
702
703             for ($i=count($subf)-1; $i>=0; $i--) {
704                 if ($subf[$i] == ".") {
705                     # do nothing
706                 } elseif ($subf[$i] == "..") {
707                     $i--;
708                 } elseif (!$subf[$i] && $i!=count($subf)-1 && $i) {
709                     # do nothing
710                 } else {
711                     $r = $subf[$i].($i!=(count($subf)-1) ? "/".$r : "");
712                 }
713             }
714         }
715         return $r;
716     }
717
718     function _writeFooter() {
719         $this->_write(pack("a512", ""));
720     }
721
722     function _extractList($p_to, $p_files, $p_remdir, $p_mode = 0755) {
723         if (!$p_to || ($p_to[0]!="/"&&substr($p_to,0,3)!="../"&&substr($p_to,1,3)!=":\\"&&substr($p_to,1,2)!=":/")) /*" // <- PHP Coder bug */
724             $p_to = "./$p_to";
725
726         if ($p_remdir && substr($p_remdir,-1)!='/') $p_remdir .= '/';
727         $p_remdirs = strlen($p_remdir);
728         while($dat = $this->_read(512)) {
729             $headers = $this->_readHeader($dat);
730             if(!$headers['filename']) continue;
731
732             if($p_files == -1 || $p_files[0] == -1){
733                 $extract = true;
734             } else {
735                 $extract = false;
736
737                 foreach($p_files as $f) {
738                     if(substr($f,-1) == "/") {
739                         if((strlen($headers['filename']) > strlen($f)) && (substr($headers['filename'],0,strlen($f))==$f)) {
740                             $extract = true;
741                             break;
742                         }
743                     } elseif($f == $headers['filename']) {
744                         $extract = true;
745                         break;
746                     }
747                 }
748             }
749
750             if ($extract) {
751                 $det[] = $headers;
752                 if ($p_remdir && substr($headers['filename'],0,$p_remdirs)==$p_remdir)
753                     $headers['filename'] = substr($headers['filename'],$p_remdirs);
754
755                 if($headers['filename'].'/' == $p_remdir && $headers['typeflag']=='5') continue;
756
757                 if ($p_to != "./" && $p_to != "/") {
758                     while($p_to{-1}=="/") $p_to = substr($p_to,0,-1);
759
760                     if($headers['filename']{0} == "/")
761                         $headers['filename'] = $p_to.$headers['filename'];
762                     else
763                         $headers['filename'] = $p_to."/".$headers['filename'];
764                 }
765
766                 $ok = $this->_dirApp($headers['typeflag']=="5" ? $headers['filename'] : dirname($headers['filename']));
767                 if($ok < 0) return $ok;
768
769                 if (!$headers['typeflag']) {
770                     if (!$fp = @fopen($headers['filename'], "wb")) return -6;
771                     $n = floor($headers['size']/512);
772
773                     for ($i=0; $i<$n; $i++){
774                         fwrite($fp, $this->_read(512),512);
775                     }
776                     if (($headers['size'] % 512) != 0) fwrite($fp, $this->_read(512), $headers['size'] % 512);
777
778                     fclose($fp);
779                     touch($headers['filename'], $headers['mtime']);
780                     chmod($headers['filename'], $p_mode);
781                 } else {
782                     $this->_seek(ceil($headers['size']/512)*512,1);
783                 }
784             }else $this->_seek(ceil($headers['size']/512)*512,1);
785         }
786         return $det;
787     }
788
789     function _dirApp($d) {
790         //  map to dokuwiki function (its more robust)
791         return io_mkdir_p($d);
792     }
793
794 }
795