Projet

Général

Profil

Paste
Télécharger (62,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / system / system.tar.inc @ 6a4d64c4

1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3

    
4
/**
5
 * File::CSV
6
 *
7
 * PHP versions 4 and 5
8
 *
9
 * Copyright (c) 1997-2008,
10
 * Vincent Blavet <vincent@phpconcept.net>
11
 * All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *
16
 *     * Redistributions of source code must retain the above copyright notice,
17
 *       this list of conditions and the following disclaimer.
18
 *     * Redistributions in binary form must reproduce the above copyright
19
 *       notice, this list of conditions and the following disclaimer in the
20
 *       documentation and/or other materials provided with the distribution.
21
 *
22
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
26
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
 *
33
 *
34
 * @category    File_Formats
35
 * @package     Archive_Tar
36
 * @author      Vincent Blavet <vincent@phpconcept.net>
37
 * @copyright   1997-2008 The Authors
38
 * @license     http://www.opensource.org/licenses/bsd-license.php New BSD License
39
 * @version     CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp
40
 * @link        http://pear.php.net/package/Archive_Tar
41
 */
42

    
43
//require_once 'PEAR.php';
44
//
45
//
46
define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
47
define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
48

    
49
/**
50
* Creates a (compressed) Tar archive
51
*
52
* @author   Vincent Blavet <vincent@phpconcept.net>
53
* @version  Revision: 1.43
54
* @license  http://www.opensource.org/licenses/bsd-license.php New BSD License
55
* @package  Archive_Tar
56
*/
57
class Archive_Tar // extends PEAR
58
{
59
    /**
60
    * @var string Name of the Tar
61
    */
62
    var $_tarname='';
63

    
64
    /**
65
    * @var boolean if true, the Tar file will be gzipped
66
    */
67
    var $_compress=false;
68

    
69
    /**
70
    * @var string Type of compression : 'none', 'gz' or 'bz2'
71
    */
72
    var $_compress_type='none';
73

    
74
    /**
75
    * @var string Explode separator
76
    */
77
    var $_separator=' ';
78

    
79
    /**
80
    * @var file descriptor
81
    */
82
    var $_file=0;
83

    
84
    /**
85
    * @var string Local Tar name of a remote Tar (http:// or ftp://)
86
    */
87
    var $_temp_tarname='';
88

    
89
    // {{{ constructor
90
    /**
91
    * Archive_Tar Class constructor. This flavour of the constructor only
92
    * declare a new Archive_Tar object, identifying it by the name of the
93
    * tar file.
94
    * If the compress argument is set the tar will be read or created as a
95
    * gzip or bz2 compressed TAR file.
96
    *
97
    * @param    string  $p_tarname  The name of the tar archive to create
98
    * @param    string  $p_compress can be null, 'gz' or 'bz2'. This
99
    *                   parameter indicates if gzip or bz2 compression
100
    *                   is required.  For compatibility reason the
101
    *                   boolean value 'true' means 'gz'.
102
    * @access public
103
    */
104
//    function Archive_Tar($p_tarname, $p_compress = null)
105
    function __construct($p_tarname, $p_compress = null)
106
    {
107
//        $this->PEAR();
108
        $this->_compress = false;
109
        $this->_compress_type = 'none';
110
        if (($p_compress === null) || ($p_compress == '')) {
111
            if (@file_exists($p_tarname)) {
112
                if ($fp = @fopen($p_tarname, "rb")) {
113
                    // look for gzip magic cookie
114
                    $data = fread($fp, 2);
115
                    fclose($fp);
116
                    if ($data == "\37\213") {
117
                        $this->_compress = true;
118
                        $this->_compress_type = 'gz';
119
                    // No sure it's enought for a magic code ....
120
                    } elseif ($data == "BZ") {
121
                        $this->_compress = true;
122
                        $this->_compress_type = 'bz2';
123
                    }
124
                }
125
            } else {
126
                // probably a remote file or some file accessible
127
                // through a stream interface
128
                if (substr($p_tarname, -2) == 'gz') {
129
                    $this->_compress = true;
130
                    $this->_compress_type = 'gz';
131
                } elseif ((substr($p_tarname, -3) == 'bz2') ||
132
                          (substr($p_tarname, -2) == 'bz')) {
133
                    $this->_compress = true;
134
                    $this->_compress_type = 'bz2';
135
                }
136
            }
137
        } else {
138
            if (($p_compress === true) || ($p_compress == 'gz')) {
139
                $this->_compress = true;
140
                $this->_compress_type = 'gz';
141
            } else if ($p_compress == 'bz2') {
142
                $this->_compress = true;
143
                $this->_compress_type = 'bz2';
144
            } else {
145
                die("Unsupported compression type '$p_compress'\n".
146
                    "Supported types are 'gz' and 'bz2'.\n");
147
                return false;
148
            }
149
        }
150
        $this->_tarname = $p_tarname;
151
        if ($this->_compress) { // assert zlib or bz2 extension support
152
            if ($this->_compress_type == 'gz')
153
                $extname = 'zlib';
154
            else if ($this->_compress_type == 'bz2')
155
                $extname = 'bz2';
156

    
157
            if (!extension_loaded($extname)) {
158
//                PEAR::loadExtension($extname);
159
                $this->loadExtension($extname);
160
            }
161
            if (!extension_loaded($extname)) {
162
                die("The extension '$extname' couldn't be found.\n".
163
                    "Please make sure your version of PHP was built ".
164
                    "with '$extname' support.\n");
165
                return false;
166
            }
167
        }
168
    }
169
    // }}}
170

    
171
    /**
172
    * OS independent PHP extension load. Remember to take care
173
    * on the correct extension name for case sensitive OSes.
174
    * The function is the copy of PEAR::loadExtension().
175
    *
176
    * @param string $ext The extension name
177
    * @return bool Success or not on the dl() call
178
    */
179
    function loadExtension($ext)
180
    {
181
        if (!extension_loaded($ext)) {
182
            // if either returns true dl() will produce a FATAL error, stop that
183
            if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
184
                return false;
185
            }
186

    
187
            if (OS_WINDOWS) {
188
                $suffix = '.dll';
189
            } elseif (PHP_OS == 'HP-UX') {
190
                $suffix = '.sl';
191
            } elseif (PHP_OS == 'AIX') {
192
                $suffix = '.a';
193
            } elseif (PHP_OS == 'OSX') {
194
                $suffix = '.bundle';
195
            } else {
196
                $suffix = '.so';
197
            }
198

    
199
            return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
200
        }
201

    
202
        return true;
203
    }
204

    
205

    
206
    // {{{ destructor
207
//    function _Archive_Tar()
208
    function __destruct()
209
    {
210
        $this->_close();
211
        // ----- Look for a local copy to delete
212
        if ($this->_temp_tarname != '')
213
            @drupal_unlink($this->_temp_tarname);
214
//        $this->_PEAR();
215
    }
216
    // }}}
217

    
218
    // {{{ create()
219
    /**
220
    * This method creates the archive file and add the files / directories
221
    * that are listed in $p_filelist.
222
    * If a file with the same name exist and is writable, it is replaced
223
    * by the new tar.
224
    * The method return false and a PEAR error text.
225
    * The $p_filelist parameter can be an array of string, each string
226
    * representing a filename or a directory name with their path if
227
    * needed. It can also be a single string with names separated by a
228
    * single blank.
229
    * For each directory added in the archive, the files and
230
    * sub-directories are also added.
231
    * See also createModify() method for more details.
232
    *
233
    * @param array  $p_filelist An array of filenames and directory names, or a
234
	*                           single string with names separated by a single
235
	*                           blank space.
236
    * @return                   true on success, false on error.
237
    * @see createModify()
238
    * @access public
239
    */
240
    function create($p_filelist)
241
    {
242
        return $this->createModify($p_filelist, '', '');
243
    }
244
    // }}}
245

    
246
    // {{{ add()
247
    /**
248
    * This method add the files / directories that are listed in $p_filelist in
249
    * the archive. If the archive does not exist it is created.
250
    * The method return false and a PEAR error text.
251
    * The files and directories listed are only added at the end of the archive,
252
    * even if a file with the same name is already archived.
253
    * See also createModify() method for more details.
254
    *
255
    * @param array  $p_filelist An array of filenames and directory names, or a
256
	*                           single string with names separated by a single
257
	*                           blank space.
258
    * @return                   true on success, false on error.
259
    * @see createModify()
260
    * @access public
261
    */
262
    function add($p_filelist)
263
    {
264
        return $this->addModify($p_filelist, '', '');
265
    }
266
    // }}}
267

    
268
    // {{{ extract()
269
    function extract($p_path='')
270
    {
271
        return $this->extractModify($p_path, '');
272
    }
273
    // }}}
274

    
275
    // {{{ listContent()
276
    function listContent()
277
    {
278
        $v_list_detail = array();
279

    
280
        if ($this->_openRead()) {
281
            if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
282
                unset($v_list_detail);
283
                $v_list_detail = 0;
284
            }
285
            $this->_close();
286
        }
287

    
288
        return $v_list_detail;
289
    }
290
    // }}}
291

    
292
    // {{{ createModify()
293
    /**
294
    * This method creates the archive file and add the files / directories
295
    * that are listed in $p_filelist.
296
    * If the file already exists and is writable, it is replaced by the
297
    * new tar. It is a create and not an add. If the file exists and is
298
    * read-only or is a directory it is not replaced. The method return
299
    * false and a PEAR error text.
300
    * The $p_filelist parameter can be an array of string, each string
301
    * representing a filename or a directory name with their path if
302
    * needed. It can also be a single string with names separated by a
303
    * single blank.
304
    * The path indicated in $p_remove_dir will be removed from the
305
    * memorized path of each file / directory listed when this path
306
    * exists. By default nothing is removed (empty path '')
307
    * The path indicated in $p_add_dir will be added at the beginning of
308
    * the memorized path of each file / directory listed. However it can
309
    * be set to empty ''. The adding of a path is done after the removing
310
    * of path.
311
    * The path add/remove ability enables the user to prepare an archive
312
    * for extraction in a different path than the origin files are.
313
    * See also addModify() method for file adding properties.
314
    *
315
    * @param array  $p_filelist     An array of filenames and directory names,
316
	*                               or a single string with names separated by
317
	*                               a single blank space.
318
    * @param string $p_add_dir      A string which contains a path to be added
319
	*                               to the memorized path of each element in
320
	*                               the list.
321
    * @param string $p_remove_dir   A string which contains a path to be
322
	*                               removed from the memorized path of each
323
	*                               element in the list, when relevant.
324
    * @return boolean               true on success, false on error.
325
    * @access public
326
    * @see addModify()
327
    */
328
    function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
329
    {
330
        $v_result = true;
331

    
332
        if (!$this->_openWrite())
333
            return false;
334

    
335
        if ($p_filelist != '') {
336
            if (is_array($p_filelist))
337
                $v_list = $p_filelist;
338
            elseif (is_string($p_filelist))
339
                $v_list = explode($this->_separator, $p_filelist);
340
            else {
341
                $this->_cleanFile();
342
                $this->_error('Invalid file list');
343
                return false;
344
            }
345

    
346
            $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
347
        }
348

    
349
        if ($v_result) {
350
            $this->_writeFooter();
351
            $this->_close();
352
        } else
353
            $this->_cleanFile();
354

    
355
        return $v_result;
356
    }
357
    // }}}
358

    
359
    // {{{ addModify()
360
    /**
361
    * This method add the files / directories listed in $p_filelist at the
362
    * end of the existing archive. If the archive does not yet exists it
363
    * is created.
364
    * The $p_filelist parameter can be an array of string, each string
365
    * representing a filename or a directory name with their path if
366
    * needed. It can also be a single string with names separated by a
367
    * single blank.
368
    * The path indicated in $p_remove_dir will be removed from the
369
    * memorized path of each file / directory listed when this path
370
    * exists. By default nothing is removed (empty path '')
371
    * The path indicated in $p_add_dir will be added at the beginning of
372
    * the memorized path of each file / directory listed. However it can
373
    * be set to empty ''. The adding of a path is done after the removing
374
    * of path.
375
    * The path add/remove ability enables the user to prepare an archive
376
    * for extraction in a different path than the origin files are.
377
    * If a file/dir is already in the archive it will only be added at the
378
    * end of the archive. There is no update of the existing archived
379
    * file/dir. However while extracting the archive, the last file will
380
    * replace the first one. This results in a none optimization of the
381
    * archive size.
382
    * If a file/dir does not exist the file/dir is ignored. However an
383
    * error text is send to PEAR error.
384
    * If a file/dir is not readable the file/dir is ignored. However an
385
    * error text is send to PEAR error.
386
    *
387
    * @param array      $p_filelist     An array of filenames and directory
388
	*                                   names, or a single string with names
389
	*                                   separated by a single blank space.
390
    * @param string     $p_add_dir      A string which contains a path to be
391
	*                                   added to the memorized path of each
392
	*                                   element in the list.
393
    * @param string     $p_remove_dir   A string which contains a path to be
394
	*                                   removed from the memorized path of
395
	*                                   each element in the list, when
396
    *                                   relevant.
397
    * @return                           true on success, false on error.
398
    * @access public
399
    */
400
    function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
401
    {
402
        $v_result = true;
403

    
404
        if (!$this->_isArchive())
405
            $v_result = $this->createModify($p_filelist, $p_add_dir,
406
			                                $p_remove_dir);
407
        else {
408
            if (is_array($p_filelist))
409
                $v_list = $p_filelist;
410
            elseif (is_string($p_filelist))
411
                $v_list = explode($this->_separator, $p_filelist);
412
            else {
413
                $this->_error('Invalid file list');
414
                return false;
415
            }
416

    
417
            $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
418
        }
419

    
420
        return $v_result;
421
    }
422
    // }}}
423

    
424
    // {{{ addString()
425
    /**
426
    * This method add a single string as a file at the
427
    * end of the existing archive. If the archive does not yet exists it
428
    * is created.
429
    *
430
    * @param string     $p_filename     A string which contains the full
431
	*                                   filename path that will be associated
432
	*                                   with the string.
433
    * @param string     $p_string       The content of the file added in
434
	*                                   the archive.
435
    * @return                           true on success, false on error.
436
    * @access public
437
    */
438
    function addString($p_filename, $p_string)
439
    {
440
        $v_result = true;
441

    
442
        if (!$this->_isArchive()) {
443
            if (!$this->_openWrite()) {
444
                return false;
445
            }
446
            $this->_close();
447
        }
448

    
449
        if (!$this->_openAppend())
450
            return false;
451

    
452
        // Need to check the get back to the temporary file ? ....
453
        $v_result = $this->_addString($p_filename, $p_string);
454

    
455
        $this->_writeFooter();
456

    
457
        $this->_close();
458

    
459
        return $v_result;
460
    }
461
    // }}}
462

    
463
    // {{{ extractModify()
464
    /**
465
    * This method extract all the content of the archive in the directory
466
    * indicated by $p_path. When relevant the memorized path of the
467
    * files/dir can be modified by removing the $p_remove_path path at the
468
    * beginning of the file/dir path.
469
    * While extracting a file, if the directory path does not exists it is
470
    * created.
471
    * While extracting a file, if the file already exists it is replaced
472
    * without looking for last modification date.
473
    * While extracting a file, if the file already exists and is write
474
    * protected, the extraction is aborted.
475
    * While extracting a file, if a directory with the same name already
476
    * exists, the extraction is aborted.
477
    * While extracting a directory, if a file with the same name already
478
    * exists, the extraction is aborted.
479
    * While extracting a file/directory if the destination directory exist
480
    * and is write protected, or does not exist but can not be created,
481
    * the extraction is aborted.
482
    * If after extraction an extracted file does not show the correct
483
    * stored file size, the extraction is aborted.
484
    * When the extraction is aborted, a PEAR error text is set and false
485
    * is returned. However the result can be a partial extraction that may
486
    * need to be manually cleaned.
487
    *
488
    * @param string $p_path         The path of the directory where the
489
	*                               files/dir need to by extracted.
490
    * @param string $p_remove_path  Part of the memorized path that can be
491
	*                               removed if present at the beginning of
492
	*                               the file/dir path.
493
    * @return boolean               true on success, false on error.
494
    * @access public
495
    * @see extractList()
496
    */
497
    function extractModify($p_path, $p_remove_path)
498
    {
499
        $v_result = true;
500
        $v_list_detail = array();
501

    
502
        if ($v_result = $this->_openRead()) {
503
            $v_result = $this->_extractList($p_path, $v_list_detail,
504
			                                "complete", 0, $p_remove_path);
505
            $this->_close();
506
        }
507

    
508
        return $v_result;
509
    }
510
    // }}}
511

    
512
    // {{{ extractInString()
513
    /**
514
    * This method extract from the archive one file identified by $p_filename.
515
    * The return value is a string with the file content, or NULL on error.
516
    * @param string $p_filename     The path of the file to extract in a string.
517
    * @return                       a string with the file content or NULL.
518
    * @access public
519
    */
520
    function extractInString($p_filename)
521
    {
522
        if ($this->_openRead()) {
523
            $v_result = $this->_extractInString($p_filename);
524
            $this->_close();
525
        } else {
526
            $v_result = NULL;
527
        }
528

    
529
        return $v_result;
530
    }
531
    // }}}
532

    
533
    // {{{ extractList()
534
    /**
535
    * This method extract from the archive only the files indicated in the
536
    * $p_filelist. These files are extracted in the current directory or
537
    * in the directory indicated by the optional $p_path parameter.
538
    * If indicated the $p_remove_path can be used in the same way as it is
539
    * used in extractModify() method.
540
    * @param array  $p_filelist     An array of filenames and directory names,
541
	*                               or a single string with names separated
542
	*                               by a single blank space.
543
    * @param string $p_path         The path of the directory where the
544
	*                               files/dir need to by extracted.
545
    * @param string $p_remove_path  Part of the memorized path that can be
546
	*                               removed if present at the beginning of
547
	*                               the file/dir path.
548
    * @return                       true on success, false on error.
549
    * @access public
550
    * @see extractModify()
551
    */
552
    function extractList($p_filelist, $p_path='', $p_remove_path='')
553
    {
554
        $v_result = true;
555
        $v_list_detail = array();
556

    
557
        if (is_array($p_filelist))
558
            $v_list = $p_filelist;
559
        elseif (is_string($p_filelist))
560
            $v_list = explode($this->_separator, $p_filelist);
561
        else {
562
            $this->_error('Invalid string list');
563
            return false;
564
        }
565

    
566
        if ($v_result = $this->_openRead()) {
567
            $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
568
			                                $v_list, $p_remove_path);
569
            $this->_close();
570
        }
571

    
572
        return $v_result;
573
    }
574
    // }}}
575

    
576
    // {{{ setAttribute()
577
    /**
578
    * This method set specific attributes of the archive. It uses a variable
579
    * list of parameters, in the format attribute code + attribute values :
580
    * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
581
    * @param mixed $argv            variable list of attributes and values
582
    * @return                       true on success, false on error.
583
    * @access public
584
    */
585
    function setAttribute()
586
    {
587
        $v_result = true;
588

    
589
        // ----- Get the number of variable list of arguments
590
        if (($v_size = func_num_args()) == 0) {
591
            return true;
592
        }
593

    
594
        // ----- Get the arguments
595
        $v_att_list = &func_get_args();
596

    
597
        // ----- Read the attributes
598
        $i=0;
599
        while ($i<$v_size) {
600

    
601
            // ----- Look for next option
602
            switch ($v_att_list[$i]) {
603
                // ----- Look for options that request a string value
604
                case ARCHIVE_TAR_ATT_SEPARATOR :
605
                    // ----- Check the number of parameters
606
                    if (($i+1) >= $v_size) {
607
                        $this->_error('Invalid number of parameters for '
608
						              .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
609
                        return false;
610
                    }
611

    
612
                    // ----- Get the value
613
                    $this->_separator = $v_att_list[$i+1];
614
                    $i++;
615
                break;
616

    
617
                default :
618
                    $this->_error('Unknow attribute code '.$v_att_list[$i].'');
619
                    return false;
620
            }
621

    
622
            // ----- Next attribute
623
            $i++;
624
        }
625

    
626
        return $v_result;
627
    }
628
    // }}}
629

    
630
    // {{{ _error()
631
    function _error($p_message)
632
    {
633
        // ----- To be completed
634
//        $this->raiseError($p_message);
635
        throw new Exception($p_message);
636
    }
637
    // }}}
638

    
639
    // {{{ _warning()
640
    function _warning($p_message)
641
    {
642
        // ----- To be completed
643
//        $this->raiseError($p_message);
644
        throw new Exception($p_message);
645
    }
646
    // }}}
647

    
648
    // {{{ _isArchive()
649
    function _isArchive($p_filename=NULL)
650
    {
651
        if ($p_filename == NULL) {
652
            $p_filename = $this->_tarname;
653
        }
654
        clearstatcache();
655
        return @is_file($p_filename) && !@is_link($p_filename);
656
    }
657
    // }}}
658

    
659
    // {{{ _openWrite()
660
    function _openWrite()
661
    {
662
        if ($this->_compress_type == 'gz')
663
            $this->_file = @gzopen($this->_tarname, "wb9");
664
        else if ($this->_compress_type == 'bz2')
665
            $this->_file = @bzopen($this->_tarname, "w");
666
        else if ($this->_compress_type == 'none')
667
            $this->_file = @fopen($this->_tarname, "wb");
668
        else
669
            $this->_error('Unknown or missing compression type ('
670
			              .$this->_compress_type.')');
671

    
672
        if ($this->_file == 0) {
673
            $this->_error('Unable to open in write mode \''
674
			              .$this->_tarname.'\'');
675
            return false;
676
        }
677

    
678
        return true;
679
    }
680
    // }}}
681

    
682
    // {{{ _openRead()
683
    function _openRead()
684
    {
685
        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
686

    
687
          // ----- Look if a local copy need to be done
688
          if ($this->_temp_tarname == '') {
689
              $this->_temp_tarname = uniqid('tar').'.tmp';
690
              if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
691
                $this->_error('Unable to open in read mode \''
692
				              .$this->_tarname.'\'');
693
                $this->_temp_tarname = '';
694
                return false;
695
              }
696
              if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
697
                $this->_error('Unable to open in write mode \''
698
				              .$this->_temp_tarname.'\'');
699
                $this->_temp_tarname = '';
700
                return false;
701
              }
702
              while ($v_data = @fread($v_file_from, 1024))
703
                  @fwrite($v_file_to, $v_data);
704
              @fclose($v_file_from);
705
              @fclose($v_file_to);
706
          }
707

    
708
          // ----- File to open if the local copy
709
          $v_filename = $this->_temp_tarname;
710

    
711
        } else
712
          // ----- File to open if the normal Tar file
713
          $v_filename = $this->_tarname;
714

    
715
        if ($this->_compress_type == 'gz')
716
            $this->_file = @gzopen($v_filename, "rb");
717
        else if ($this->_compress_type == 'bz2')
718
            $this->_file = @bzopen($v_filename, "r");
719
        else if ($this->_compress_type == 'none')
720
            $this->_file = @fopen($v_filename, "rb");
721
        else
722
            $this->_error('Unknown or missing compression type ('
723
			              .$this->_compress_type.')');
724

    
725
        if ($this->_file == 0) {
726
            $this->_error('Unable to open in read mode \''.$v_filename.'\'');
727
            return false;
728
        }
729

    
730
        return true;
731
    }
732
    // }}}
733

    
734
    // {{{ _openReadWrite()
735
    function _openReadWrite()
736
    {
737
        if ($this->_compress_type == 'gz')
738
            $this->_file = @gzopen($this->_tarname, "r+b");
739
        else if ($this->_compress_type == 'bz2') {
740
            $this->_error('Unable to open bz2 in read/write mode \''
741
			              .$this->_tarname.'\' (limitation of bz2 extension)');
742
            return false;
743
        } else if ($this->_compress_type == 'none')
744
            $this->_file = @fopen($this->_tarname, "r+b");
745
        else
746
            $this->_error('Unknown or missing compression type ('
747
			              .$this->_compress_type.')');
748

    
749
        if ($this->_file == 0) {
750
            $this->_error('Unable to open in read/write mode \''
751
			              .$this->_tarname.'\'');
752
            return false;
753
        }
754

    
755
        return true;
756
    }
757
    // }}}
758

    
759
    // {{{ _close()
760
    function _close()
761
    {
762
        //if (isset($this->_file)) {
763
        if (is_resource($this->_file)) {
764
            if ($this->_compress_type == 'gz')
765
                @gzclose($this->_file);
766
            else if ($this->_compress_type == 'bz2')
767
                @bzclose($this->_file);
768
            else if ($this->_compress_type == 'none')
769
                @fclose($this->_file);
770
            else
771
                $this->_error('Unknown or missing compression type ('
772
				              .$this->_compress_type.')');
773

    
774
            $this->_file = 0;
775
        }
776

    
777
        // ----- Look if a local copy need to be erase
778
        // Note that it might be interesting to keep the url for a time : ToDo
779
        if ($this->_temp_tarname != '') {
780
            @drupal_unlink($this->_temp_tarname);
781
            $this->_temp_tarname = '';
782
        }
783

    
784
        return true;
785
    }
786
    // }}}
787

    
788
    // {{{ _cleanFile()
789
    function _cleanFile()
790
    {
791
        $this->_close();
792

    
793
        // ----- Look for a local copy
794
        if ($this->_temp_tarname != '') {
795
            // ----- Remove the local copy but not the remote tarname
796
            @drupal_unlink($this->_temp_tarname);
797
            $this->_temp_tarname = '';
798
        } else {
799
            // ----- Remove the local tarname file
800
            @drupal_unlink($this->_tarname);
801
        }
802
        $this->_tarname = '';
803

    
804
        return true;
805
    }
806
    // }}}
807

    
808
    // {{{ _writeBlock()
809
    function _writeBlock($p_binary_data, $p_len=null)
810
    {
811
      if (is_resource($this->_file)) {
812
          if ($p_len === null) {
813
              if ($this->_compress_type == 'gz')
814
                  @gzputs($this->_file, $p_binary_data);
815
              else if ($this->_compress_type == 'bz2')
816
                  @bzwrite($this->_file, $p_binary_data);
817
              else if ($this->_compress_type == 'none')
818
                  @fputs($this->_file, $p_binary_data);
819
              else
820
                  $this->_error('Unknown or missing compression type ('
821
				                .$this->_compress_type.')');
822
          } else {
823
              if ($this->_compress_type == 'gz')
824
                  @gzputs($this->_file, $p_binary_data, $p_len);
825
              else if ($this->_compress_type == 'bz2')
826
                  @bzwrite($this->_file, $p_binary_data, $p_len);
827
              else if ($this->_compress_type == 'none')
828
                  @fputs($this->_file, $p_binary_data, $p_len);
829
              else
830
                  $this->_error('Unknown or missing compression type ('
831
				                .$this->_compress_type.')');
832

    
833
          }
834
      }
835
      return true;
836
    }
837
    // }}}
838

    
839
    // {{{ _readBlock()
840
    function _readBlock()
841
    {
842
      $v_block = null;
843
      if (is_resource($this->_file)) {
844
          if ($this->_compress_type == 'gz')
845
              $v_block = @gzread($this->_file, 512);
846
          else if ($this->_compress_type == 'bz2')
847
              $v_block = @bzread($this->_file, 512);
848
          else if ($this->_compress_type == 'none')
849
              $v_block = @fread($this->_file, 512);
850
          else
851
              $this->_error('Unknown or missing compression type ('
852
			                .$this->_compress_type.')');
853
      }
854
      return $v_block;
855
    }
856
    // }}}
857

    
858
    // {{{ _jumpBlock()
859
    function _jumpBlock($p_len=null)
860
    {
861
      if (is_resource($this->_file)) {
862
          if ($p_len === null)
863
              $p_len = 1;
864

    
865
          if ($this->_compress_type == 'gz') {
866
              @gzseek($this->_file, gztell($this->_file)+($p_len*512));
867
          }
868
          else if ($this->_compress_type == 'bz2') {
869
              // ----- Replace missing bztell() and bzseek()
870
              for ($i=0; $i<$p_len; $i++)
871
                  $this->_readBlock();
872
          } else if ($this->_compress_type == 'none')
873
              @fseek($this->_file, ftell($this->_file)+($p_len*512));
874
          else
875
              $this->_error('Unknown or missing compression type ('
876
			                .$this->_compress_type.')');
877

    
878
      }
879
      return true;
880
    }
881
    // }}}
882

    
883
    // {{{ _writeFooter()
884
    function _writeFooter()
885
    {
886
      if (is_resource($this->_file)) {
887
          // ----- Write the last 0 filled block for end of archive
888
          $v_binary_data = pack('a1024', '');
889
          $this->_writeBlock($v_binary_data);
890
      }
891
      return true;
892
    }
893
    // }}}
894

    
895
    // {{{ _addList()
896
    function _addList($p_list, $p_add_dir, $p_remove_dir)
897
    {
898
      $v_result=true;
899
      $v_header = array();
900

    
901
      // ----- Remove potential windows directory separator
902
      $p_add_dir = $this->_translateWinPath($p_add_dir);
903
      $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
904

    
905
      if (!$this->_file) {
906
          $this->_error('Invalid file descriptor');
907
          return false;
908
      }
909

    
910
      if (sizeof($p_list) == 0)
911
          return true;
912

    
913
      foreach ($p_list as $v_filename) {
914
          if (!$v_result) {
915
              break;
916
          }
917

    
918
        // ----- Skip the current tar name
919
        if ($v_filename == $this->_tarname)
920
            continue;
921

    
922
        if ($v_filename == '')
923
            continue;
924

    
925
        if (!file_exists($v_filename)) {
926
            $this->_warning("File '$v_filename' does not exist");
927
            continue;
928
        }
929

    
930
        // ----- Add the file or directory header
931
        if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
932
            return false;
933

    
934
        if (@is_dir($v_filename) && !@is_link($v_filename)) {
935
            if (!($p_hdir = opendir($v_filename))) {
936
                $this->_warning("Directory '$v_filename' can not be read");
937
                continue;
938
            }
939
            while (false !== ($p_hitem = readdir($p_hdir))) {
940
                if (($p_hitem != '.') && ($p_hitem != '..')) {
941
                    if ($v_filename != ".")
942
                        $p_temp_list[0] = $v_filename.'/'.$p_hitem;
943
                    else
944
                        $p_temp_list[0] = $p_hitem;
945

    
946
                    $v_result = $this->_addList($p_temp_list,
947
					                            $p_add_dir,
948
												$p_remove_dir);
949
                }
950
            }
951

    
952
            unset($p_temp_list);
953
            unset($p_hdir);
954
            unset($p_hitem);
955
        }
956
      }
957

    
958
      return $v_result;
959
    }
960
    // }}}
961

    
962
    // {{{ _addFile()
963
    function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
964
    {
965
      if (!$this->_file) {
966
          $this->_error('Invalid file descriptor');
967
          return false;
968
      }
969

    
970
      if ($p_filename == '') {
971
          $this->_error('Invalid file name');
972
          return false;
973
      }
974

    
975
      // ----- Calculate the stored filename
976
      $p_filename = $this->_translateWinPath($p_filename, false);;
977
      $v_stored_filename = $p_filename;
978
      if (strcmp($p_filename, $p_remove_dir) == 0) {
979
          return true;
980
      }
981
      if ($p_remove_dir != '') {
982
          if (substr($p_remove_dir, -1) != '/')
983
              $p_remove_dir .= '/';
984

    
985
          if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
986
              $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
987
      }
988
      $v_stored_filename = $this->_translateWinPath($v_stored_filename);
989
      if ($p_add_dir != '') {
990
          if (substr($p_add_dir, -1) == '/')
991
              $v_stored_filename = $p_add_dir.$v_stored_filename;
992
          else
993
              $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
994
      }
995

    
996
      $v_stored_filename = $this->_pathReduction($v_stored_filename);
997

    
998
      if ($this->_isArchive($p_filename)) {
999
          if (($v_file = @fopen($p_filename, "rb")) == 0) {
1000
              $this->_warning("Unable to open file '".$p_filename
1001
			                  ."' in binary read mode");
1002
              return true;
1003
          }
1004

    
1005
          if (!$this->_writeHeader($p_filename, $v_stored_filename))
1006
              return false;
1007

    
1008
          while (($v_buffer = fread($v_file, 512)) != '') {
1009
              $v_binary_data = pack("a512", "$v_buffer");
1010
              $this->_writeBlock($v_binary_data);
1011
          }
1012

    
1013
          fclose($v_file);
1014

    
1015
      } else {
1016
          // ----- Only header for dir
1017
          if (!$this->_writeHeader($p_filename, $v_stored_filename))
1018
              return false;
1019
      }
1020

    
1021
      return true;
1022
    }
1023
    // }}}
1024

    
1025
    // {{{ _addString()
1026
    function _addString($p_filename, $p_string)
1027
    {
1028
      if (!$this->_file) {
1029
          $this->_error('Invalid file descriptor');
1030
          return false;
1031
      }
1032

    
1033
      if ($p_filename == '') {
1034
          $this->_error('Invalid file name');
1035
          return false;
1036
      }
1037

    
1038
      // ----- Calculate the stored filename
1039
      $p_filename = $this->_translateWinPath($p_filename, false);;
1040

    
1041
      if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
1042
	                                  time(), 384, "", 0, 0))
1043
          return false;
1044

    
1045
      $i=0;
1046
      while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
1047
          $v_binary_data = pack("a512", $v_buffer);
1048
          $this->_writeBlock($v_binary_data);
1049
      }
1050

    
1051
      return true;
1052
    }
1053
    // }}}
1054

    
1055
    // {{{ _writeHeader()
1056
    function _writeHeader($p_filename, $p_stored_filename)
1057
    {
1058
        if ($p_stored_filename == '')
1059
            $p_stored_filename = $p_filename;
1060
        $v_reduce_filename = $this->_pathReduction($p_stored_filename);
1061

    
1062
        if (strlen($v_reduce_filename) > 99) {
1063
          if (!$this->_writeLongHeader($v_reduce_filename))
1064
            return false;
1065
        }
1066

    
1067
        $v_info = lstat($p_filename);
1068
        $v_uid = sprintf("%6s ", DecOct($v_info[4]));
1069
        $v_gid = sprintf("%6s ", DecOct($v_info[5]));
1070
        $v_perms = sprintf("%6s ", DecOct($v_info['mode']));
1071

    
1072
        $v_mtime = sprintf("%11s", DecOct($v_info['mode']));
1073

    
1074
        $v_linkname = '';
1075

    
1076
        if (@is_link($p_filename)) {
1077
          $v_typeflag = '2';
1078
          $v_linkname = readlink($p_filename);
1079
          $v_size = sprintf("%11s ", DecOct(0));
1080
        } elseif (@is_dir($p_filename)) {
1081
          $v_typeflag = "5";
1082
          $v_size = sprintf("%11s ", DecOct(0));
1083
        } else {
1084
          $v_typeflag = '';
1085
          clearstatcache();
1086
          $v_size = sprintf("%11s ", DecOct($v_info['size']));
1087
        }
1088

    
1089
        $v_magic = '';
1090

    
1091
        $v_version = '';
1092

    
1093
        $v_uname = '';
1094

    
1095
        $v_gname = '';
1096

    
1097
        $v_devmajor = '';
1098

    
1099
        $v_devminor = '';
1100

    
1101
        $v_prefix = '';
1102

    
1103
        $v_binary_data_first = pack("a100a8a8a8a12A12",
1104
		                            $v_reduce_filename, $v_perms, $v_uid,
1105
									$v_gid, $v_size, $v_mtime);
1106
        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1107
		                           $v_typeflag, $v_linkname, $v_magic,
1108
								   $v_version, $v_uname, $v_gname,
1109
								   $v_devmajor, $v_devminor, $v_prefix, '');
1110

    
1111
        // ----- Calculate the checksum
1112
        $v_checksum = 0;
1113
        // ..... First part of the header
1114
        for ($i=0; $i<148; $i++)
1115
            $v_checksum += ord(substr($v_binary_data_first,$i,1));
1116
        // ..... Ignore the checksum value and replace it by ' ' (space)
1117
        for ($i=148; $i<156; $i++)
1118
            $v_checksum += ord(' ');
1119
        // ..... Last part of the header
1120
        for ($i=156, $j=0; $i<512; $i++, $j++)
1121
            $v_checksum += ord(substr($v_binary_data_last,$j,1));
1122

    
1123
        // ----- Write the first 148 bytes of the header in the archive
1124
        $this->_writeBlock($v_binary_data_first, 148);
1125

    
1126
        // ----- Write the calculated checksum
1127
        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
1128
        $v_binary_data = pack("a8", $v_checksum);
1129
        $this->_writeBlock($v_binary_data, 8);
1130

    
1131
        // ----- Write the last 356 bytes of the header in the archive
1132
        $this->_writeBlock($v_binary_data_last, 356);
1133

    
1134
        return true;
1135
    }
1136
    // }}}
1137

    
1138
    // {{{ _writeHeaderBlock()
1139
    function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
1140
	                           $p_type='', $p_uid=0, $p_gid=0)
1141
    {
1142
        $p_filename = $this->_pathReduction($p_filename);
1143

    
1144
        if (strlen($p_filename) > 99) {
1145
          if (!$this->_writeLongHeader($p_filename))
1146
            return false;
1147
        }
1148

    
1149
        if ($p_type == "5") {
1150
          $v_size = sprintf("%11s ", DecOct(0));
1151
        } else {
1152
          $v_size = sprintf("%11s ", DecOct($p_size));
1153
        }
1154

    
1155
        $v_uid = sprintf("%6s ", DecOct($p_uid));
1156
        $v_gid = sprintf("%6s ", DecOct($p_gid));
1157
        $v_perms = sprintf("%6s ", DecOct($p_perms));
1158

    
1159
        $v_mtime = sprintf("%11s", DecOct($p_mtime));
1160

    
1161
        $v_linkname = '';
1162

    
1163
        $v_magic = '';
1164

    
1165
        $v_version = '';
1166

    
1167
        $v_uname = '';
1168

    
1169
        $v_gname = '';
1170

    
1171
        $v_devmajor = '';
1172

    
1173
        $v_devminor = '';
1174

    
1175
        $v_prefix = '';
1176

    
1177
        $v_binary_data_first = pack("a100a8a8a8a12A12",
1178
		                            $p_filename, $v_perms, $v_uid, $v_gid,
1179
									$v_size, $v_mtime);
1180
        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1181
		                           $p_type, $v_linkname, $v_magic,
1182
								   $v_version, $v_uname, $v_gname,
1183
								   $v_devmajor, $v_devminor, $v_prefix, '');
1184

    
1185
        // ----- Calculate the checksum
1186
        $v_checksum = 0;
1187
        // ..... First part of the header
1188
        for ($i=0; $i<148; $i++)
1189
            $v_checksum += ord(substr($v_binary_data_first,$i,1));
1190
        // ..... Ignore the checksum value and replace it by ' ' (space)
1191
        for ($i=148; $i<156; $i++)
1192
            $v_checksum += ord(' ');
1193
        // ..... Last part of the header
1194
        for ($i=156, $j=0; $i<512; $i++, $j++)
1195
            $v_checksum += ord(substr($v_binary_data_last,$j,1));
1196

    
1197
        // ----- Write the first 148 bytes of the header in the archive
1198
        $this->_writeBlock($v_binary_data_first, 148);
1199

    
1200
        // ----- Write the calculated checksum
1201
        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
1202
        $v_binary_data = pack("a8", $v_checksum);
1203
        $this->_writeBlock($v_binary_data, 8);
1204

    
1205
        // ----- Write the last 356 bytes of the header in the archive
1206
        $this->_writeBlock($v_binary_data_last, 356);
1207

    
1208
        return true;
1209
    }
1210
    // }}}
1211

    
1212
    // {{{ _writeLongHeader()
1213
    function _writeLongHeader($p_filename)
1214
    {
1215
        $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
1216

    
1217
        $v_typeflag = 'L';
1218

    
1219
        $v_linkname = '';
1220

    
1221
        $v_magic = '';
1222

    
1223
        $v_version = '';
1224

    
1225
        $v_uname = '';
1226

    
1227
        $v_gname = '';
1228

    
1229
        $v_devmajor = '';
1230

    
1231
        $v_devminor = '';
1232

    
1233
        $v_prefix = '';
1234

    
1235
        $v_binary_data_first = pack("a100a8a8a8a12A12",
1236
		                            '././@LongLink', 0, 0, 0, $v_size, 0);
1237
        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1238
		                           $v_typeflag, $v_linkname, $v_magic,
1239
								   $v_version, $v_uname, $v_gname,
1240
								   $v_devmajor, $v_devminor, $v_prefix, '');
1241

    
1242
        // ----- Calculate the checksum
1243
        $v_checksum = 0;
1244
        // ..... First part of the header
1245
        for ($i=0; $i<148; $i++)
1246
            $v_checksum += ord(substr($v_binary_data_first,$i,1));
1247
        // ..... Ignore the checksum value and replace it by ' ' (space)
1248
        for ($i=148; $i<156; $i++)
1249
            $v_checksum += ord(' ');
1250
        // ..... Last part of the header
1251
        for ($i=156, $j=0; $i<512; $i++, $j++)
1252
            $v_checksum += ord(substr($v_binary_data_last,$j,1));
1253

    
1254
        // ----- Write the first 148 bytes of the header in the archive
1255
        $this->_writeBlock($v_binary_data_first, 148);
1256

    
1257
        // ----- Write the calculated checksum
1258
        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
1259
        $v_binary_data = pack("a8", $v_checksum);
1260
        $this->_writeBlock($v_binary_data, 8);
1261

    
1262
        // ----- Write the last 356 bytes of the header in the archive
1263
        $this->_writeBlock($v_binary_data_last, 356);
1264

    
1265
        // ----- Write the filename as content of the block
1266
        $i=0;
1267
        while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
1268
            $v_binary_data = pack("a512", "$v_buffer");
1269
            $this->_writeBlock($v_binary_data);
1270
        }
1271

    
1272
        return true;
1273
    }
1274
    // }}}
1275

    
1276
    // {{{ _readHeader()
1277
    function _readHeader($v_binary_data, &$v_header)
1278
    {
1279
        if (strlen($v_binary_data)==0) {
1280
            $v_header['filename'] = '';
1281
            return true;
1282
        }
1283

    
1284
        if (strlen($v_binary_data) != 512) {
1285
            $v_header['filename'] = '';
1286
            $this->_error('Invalid block size : '.strlen($v_binary_data));
1287
            return false;
1288
        }
1289

    
1290
        if (!is_array($v_header)) {
1291
            $v_header = array();
1292
        }
1293
        // ----- Calculate the checksum
1294
        $v_checksum = 0;
1295
        // ..... First part of the header
1296
        for ($i=0; $i<148; $i++)
1297
            $v_checksum+=ord(substr($v_binary_data,$i,1));
1298
        // ..... Ignore the checksum value and replace it by ' ' (space)
1299
        for ($i=148; $i<156; $i++)
1300
            $v_checksum += ord(' ');
1301
        // ..... Last part of the header
1302
        for ($i=156; $i<512; $i++)
1303
           $v_checksum+=ord(substr($v_binary_data,$i,1));
1304

    
1305
        $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
1306
		                 ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
1307
						 ."a32uname/a32gname/a8devmajor/a8devminor",
1308
						 $v_binary_data);
1309

    
1310
        // ----- Extract the checksum
1311
        $v_header['checksum'] = OctDec(trim($v_data['checksum']));
1312
        if ($v_header['checksum'] != $v_checksum) {
1313
            $v_header['filename'] = '';
1314

    
1315
            // ----- Look for last block (empty block)
1316
            if (($v_checksum == 256) && ($v_header['checksum'] == 0))
1317
                return true;
1318

    
1319
            $this->_error('Invalid checksum for file "'.$v_data['filename']
1320
			              .'" : '.$v_checksum.' calculated, '
1321
						  .$v_header['checksum'].' expected');
1322
            return false;
1323
        }
1324

    
1325
        // ----- Extract the properties
1326
        $v_header['filename'] = trim($v_data['filename']);
1327
        if ($this->_maliciousFilename($v_header['filename'])) {
1328
            $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
1329
                '" will not install in desired directory tree');
1330
            return false;
1331
        }
1332
        $v_header['mode'] = OctDec(trim($v_data['mode']));
1333
        $v_header['uid'] = OctDec(trim($v_data['uid']));
1334
        $v_header['gid'] = OctDec(trim($v_data['gid']));
1335
        $v_header['size'] = OctDec(trim($v_data['size']));
1336
        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1337
        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1338
          $v_header['size'] = 0;
1339
        }
1340
        $v_header['link'] = trim($v_data['link']);
1341
        /* ----- All these fields are removed form the header because
1342
		they do not carry interesting info
1343
        $v_header[magic] = trim($v_data[magic]);
1344
        $v_header[version] = trim($v_data[version]);
1345
        $v_header[uname] = trim($v_data[uname]);
1346
        $v_header[gname] = trim($v_data[gname]);
1347
        $v_header[devmajor] = trim($v_data[devmajor]);
1348
        $v_header[devminor] = trim($v_data[devminor]);
1349
        */
1350

    
1351
        return true;
1352
    }
1353
    // }}}
1354

    
1355
    // {{{ _maliciousFilename()
1356
    /**
1357
     * Detect and report a malicious file name
1358
     *
1359
     * @param string $file
1360
     * @return bool
1361
     * @access private
1362
     */
1363
    function _maliciousFilename($file)
1364
    {
1365
        if (strpos($file, '/../') !== false) {
1366
            return true;
1367
        }
1368
        if (strpos($file, '../') === 0) {
1369
            return true;
1370
        }
1371
        return false;
1372
    }
1373
    // }}}
1374

    
1375
    // {{{ _readLongHeader()
1376
    function _readLongHeader(&$v_header)
1377
    {
1378
      $v_filename = '';
1379
      $n = floor($v_header['size']/512);
1380
      for ($i=0; $i<$n; $i++) {
1381
        $v_content = $this->_readBlock();
1382
        $v_filename .= $v_content;
1383
      }
1384
      if (($v_header['size'] % 512) != 0) {
1385
        $v_content = $this->_readBlock();
1386
        $v_filename .= $v_content;
1387
      }
1388

    
1389
      // ----- Read the next header
1390
      $v_binary_data = $this->_readBlock();
1391

    
1392
      if (!$this->_readHeader($v_binary_data, $v_header))
1393
        return false;
1394

    
1395
      $v_filename = trim($v_filename);
1396
      $v_header['filename'] = $v_filename;
1397
        if ($this->_maliciousFilename($v_filename)) {
1398
            $this->_error('Malicious .tar detected, file "' . $v_filename .
1399
                '" will not install in desired directory tree');
1400
            return false;
1401
      }
1402

    
1403
      return true;
1404
    }
1405
    // }}}
1406

    
1407
    // {{{ _extractInString()
1408
    /**
1409
    * This method extract from the archive one file identified by $p_filename.
1410
    * The return value is a string with the file content, or NULL on error.
1411
    * @param string $p_filename     The path of the file to extract in a string.
1412
    * @return                       a string with the file content or NULL.
1413
    * @access private
1414
    */
1415
    function _extractInString($p_filename)
1416
    {
1417
        $v_result_str = "";
1418

    
1419
        While (strlen($v_binary_data = $this->_readBlock()) != 0)
1420
        {
1421
          if (!$this->_readHeader($v_binary_data, $v_header))
1422
            return NULL;
1423

    
1424
          if ($v_header['filename'] == '')
1425
            continue;
1426

    
1427
          // ----- Look for long filename
1428
          if ($v_header['typeflag'] == 'L') {
1429
            if (!$this->_readLongHeader($v_header))
1430
              return NULL;
1431
          }
1432

    
1433
          if ($v_header['filename'] == $p_filename) {
1434
              if ($v_header['typeflag'] == "5") {
1435
                  $this->_error('Unable to extract in string a directory '
1436
				                .'entry {'.$v_header['filename'].'}');
1437
                  return NULL;
1438
              } else {
1439
                  $n = floor($v_header['size']/512);
1440
                  for ($i=0; $i<$n; $i++) {
1441
                      $v_result_str .= $this->_readBlock();
1442
                  }
1443
                  if (($v_header['size'] % 512) != 0) {
1444
                      $v_content = $this->_readBlock();
1445
                      $v_result_str .= substr($v_content, 0,
1446
					                          ($v_header['size'] % 512));
1447
                  }
1448
                  return $v_result_str;
1449
              }
1450
          } else {
1451
              $this->_jumpBlock(ceil(($v_header['size']/512)));
1452
          }
1453
        }
1454

    
1455
        return NULL;
1456
    }
1457
    // }}}
1458

    
1459
    // {{{ _extractList()
1460
    function _extractList($p_path, &$p_list_detail, $p_mode,
1461
	                      $p_file_list, $p_remove_path)
1462
    {
1463
    $v_result=true;
1464
    $v_nb = 0;
1465
    $v_extract_all = true;
1466
    $v_listing = false;
1467

    
1468
    $p_path = $this->_translateWinPath($p_path, false);
1469
    if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1470
	    && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
1471
      $p_path = "./".$p_path;
1472
    }
1473
    $p_remove_path = $this->_translateWinPath($p_remove_path);
1474

    
1475
    // ----- Look for path to remove format (should end by /)
1476
    if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
1477
      $p_remove_path .= '/';
1478
    $p_remove_path_size = strlen($p_remove_path);
1479

    
1480
    switch ($p_mode) {
1481
      case "complete" :
1482
        $v_extract_all = TRUE;
1483
        $v_listing = FALSE;
1484
      break;
1485
      case "partial" :
1486
          $v_extract_all = FALSE;
1487
          $v_listing = FALSE;
1488
      break;
1489
      case "list" :
1490
          $v_extract_all = FALSE;
1491
          $v_listing = TRUE;
1492
      break;
1493
      default :
1494
        $this->_error('Invalid extract mode ('.$p_mode.')');
1495
        return false;
1496
    }
1497

    
1498
    clearstatcache();
1499

    
1500
    while (strlen($v_binary_data = $this->_readBlock()) != 0)
1501
    {
1502
      $v_extract_file = FALSE;
1503
      $v_extraction_stopped = 0;
1504

    
1505
      if (!$this->_readHeader($v_binary_data, $v_header))
1506
        return false;
1507

    
1508
      if ($v_header['filename'] == '') {
1509
        continue;
1510
      }
1511

    
1512
      // ----- Look for long filename
1513
      if ($v_header['typeflag'] == 'L') {
1514
        if (!$this->_readLongHeader($v_header))
1515
          return false;
1516
      }
1517

    
1518
      if ((!$v_extract_all) && (is_array($p_file_list))) {
1519
        // ----- By default no unzip if the file is not found
1520
        $v_extract_file = false;
1521

    
1522
        for ($i=0; $i<sizeof($p_file_list); $i++) {
1523
          // ----- Look if it is a directory
1524
          if (substr($p_file_list[$i], -1) == '/') {
1525
            // ----- Look if the directory is in the filename path
1526
            if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
1527
			    && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
1528
				    == $p_file_list[$i])) {
1529
              $v_extract_file = TRUE;
1530
              break;
1531
            }
1532
          }
1533

    
1534
          // ----- It is a file, so compare the file names
1535
          elseif ($p_file_list[$i] == $v_header['filename']) {
1536
            $v_extract_file = TRUE;
1537
            break;
1538
          }
1539
        }
1540
      } else {
1541
        $v_extract_file = TRUE;
1542
      }
1543

    
1544
      // ----- Look if this file need to be extracted
1545
      if (($v_extract_file) && (!$v_listing))
1546
      {
1547
        if (($p_remove_path != '')
1548
            && (substr($v_header['filename'], 0, $p_remove_path_size)
1549
			    == $p_remove_path))
1550
          $v_header['filename'] = substr($v_header['filename'],
1551
		                                 $p_remove_path_size);
1552
        if (($p_path != './') && ($p_path != '/')) {
1553
          while (substr($p_path, -1) == '/')
1554
            $p_path = substr($p_path, 0, strlen($p_path)-1);
1555

    
1556
          if (substr($v_header['filename'], 0, 1) == '/')
1557
              $v_header['filename'] = $p_path.$v_header['filename'];
1558
          else
1559
            $v_header['filename'] = $p_path.'/'.$v_header['filename'];
1560
        }
1561
        if (file_exists($v_header['filename'])) {
1562
          if (   (@is_dir($v_header['filename']))
1563
		      && ($v_header['typeflag'] == '')) {
1564
            $this->_error('File '.$v_header['filename']
1565
			              .' already exists as a directory');
1566
            return false;
1567
          }
1568
          if (   ($this->_isArchive($v_header['filename']))
1569
		      && ($v_header['typeflag'] == "5")) {
1570
            $this->_error('Directory '.$v_header['filename']
1571
			              .' already exists as a file');
1572
            return false;
1573
          }
1574
          if (!is_writeable($v_header['filename'])) {
1575
            $this->_error('File '.$v_header['filename']
1576
			              .' already exists and is write protected');
1577
            return false;
1578
          }
1579
          if (filemtime($v_header['filename']) > $v_header['mtime']) {
1580
            // To be completed : An error or silent no replace ?
1581
          }
1582
        }
1583

    
1584
        // ----- Check the directory availability and create it if necessary
1585
        elseif (($v_result
1586
		         = $this->_dirCheck(($v_header['typeflag'] == "5"
1587
				                    ?$v_header['filename']
1588
									:dirname($v_header['filename'])))) != 1) {
1589
            $this->_error('Unable to create path for '.$v_header['filename']);
1590
            return false;
1591
        }
1592

    
1593
        if ($v_extract_file) {
1594
          if ($v_header['typeflag'] == "5") {
1595
            if (!@file_exists($v_header['filename'])) {
1596
                // Drupal integration.
1597
                // Changed the code to use drupal_mkdir() instead of mkdir().
1598
                if (!@drupal_mkdir($v_header['filename'], 0777)) {
1599
                    $this->_error('Unable to create directory {'
1600
					              .$v_header['filename'].'}');
1601
                    return false;
1602
                }
1603
            }
1604
          } elseif ($v_header['typeflag'] == "2") {
1605
              if (@file_exists($v_header['filename'])) {
1606
                  @drupal_unlink($v_header['filename']);
1607
              }
1608
              if (!@symlink($v_header['link'], $v_header['filename'])) {
1609
                  $this->_error('Unable to extract symbolic link {'
1610
                                .$v_header['filename'].'}');
1611
                  return false;
1612
              }
1613
          } else {
1614
              if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
1615
                  $this->_error('Error while opening {'.$v_header['filename']
1616
				                .'} in write binary mode');
1617
                  return false;
1618
              } else {
1619
                  $n = floor($v_header['size']/512);
1620
                  for ($i=0; $i<$n; $i++) {
1621
                      $v_content = $this->_readBlock();
1622
                      fwrite($v_dest_file, $v_content, 512);
1623
                  }
1624
            if (($v_header['size'] % 512) != 0) {
1625
              $v_content = $this->_readBlock();
1626
              fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
1627
            }
1628

    
1629
            @fclose($v_dest_file);
1630

    
1631
            // ----- Change the file mode, mtime
1632
            @touch($v_header['filename'], $v_header['mtime']);
1633
            if ($v_header['mode'] & 0111) {
1634
                // make file executable, obey umask
1635
                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
1636
                @chmod($v_header['filename'], $mode);
1637
            }
1638
          }
1639

    
1640
          // ----- Check the file size
1641
          clearstatcache();
1642
          if (filesize($v_header['filename']) != $v_header['size']) {
1643
              $this->_error('Extracted file '.$v_header['filename']
1644
			                .' does not have the correct file size \''
1645
							.filesize($v_header['filename'])
1646
							.'\' ('.$v_header['size']
1647
							.' expected). Archive may be corrupted.');
1648
              return false;
1649
          }
1650
          }
1651
        } else {
1652
          $this->_jumpBlock(ceil(($v_header['size']/512)));
1653
        }
1654
      } else {
1655
          $this->_jumpBlock(ceil(($v_header['size']/512)));
1656
      }
1657

    
1658
      /* TBC : Seems to be unused ...
1659
      if ($this->_compress)
1660
        $v_end_of_file = @gzeof($this->_file);
1661
      else
1662
        $v_end_of_file = @feof($this->_file);
1663
        */
1664

    
1665
      if ($v_listing || $v_extract_file || $v_extraction_stopped) {
1666
        // ----- Log extracted files
1667
        if (($v_file_dir = dirname($v_header['filename']))
1668
		    == $v_header['filename'])
1669
          $v_file_dir = '';
1670
        if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
1671
          $v_file_dir = '/';
1672

    
1673
        $p_list_detail[$v_nb++] = $v_header;
1674
        if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
1675
            return true;
1676
        }
1677
      }
1678
    }
1679

    
1680
        return true;
1681
    }
1682
    // }}}
1683

    
1684
    // {{{ _openAppend()
1685
    function _openAppend()
1686
    {
1687
        if (filesize($this->_tarname) == 0)
1688
          return $this->_openWrite();
1689

    
1690
        if ($this->_compress) {
1691
            $this->_close();
1692

    
1693
            if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
1694
                $this->_error('Error while renaming \''.$this->_tarname
1695
				              .'\' to temporary file \''.$this->_tarname
1696
							  .'.tmp\'');
1697
                return false;
1698
            }
1699

    
1700
            if ($this->_compress_type == 'gz')
1701
                $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
1702
            elseif ($this->_compress_type == 'bz2')
1703
                $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
1704

    
1705
            if ($v_temp_tar == 0) {
1706
                $this->_error('Unable to open file \''.$this->_tarname
1707
				              .'.tmp\' in binary read mode');
1708
                @rename($this->_tarname.".tmp", $this->_tarname);
1709
                return false;
1710
            }
1711

    
1712
            if (!$this->_openWrite()) {
1713
                @rename($this->_tarname.".tmp", $this->_tarname);
1714
                return false;
1715
            }
1716

    
1717
            if ($this->_compress_type == 'gz') {
1718
                while (!@gzeof($v_temp_tar)) {
1719
                    $v_buffer = @gzread($v_temp_tar, 512);
1720
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
1721
                        // do not copy end blocks, we will re-make them
1722
                        // after appending
1723
                        continue;
1724
                    }
1725
                    $v_binary_data = pack("a512", $v_buffer);
1726
                    $this->_writeBlock($v_binary_data);
1727
                }
1728

    
1729
                @gzclose($v_temp_tar);
1730
            }
1731
            elseif ($this->_compress_type == 'bz2') {
1732
                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
1733
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
1734
                        continue;
1735
                    }
1736
                    $v_binary_data = pack("a512", $v_buffer);
1737
                    $this->_writeBlock($v_binary_data);
1738
                }
1739

    
1740
                @bzclose($v_temp_tar);
1741
            }
1742

    
1743
            if (!@drupal_unlink($this->_tarname.".tmp")) {
1744
                $this->_error('Error while deleting temporary file \''
1745
				              .$this->_tarname.'.tmp\'');
1746
            }
1747

    
1748
        } else {
1749
            // ----- For not compressed tar, just add files before the last
1750
			//       one or two 512 bytes block
1751
            if (!$this->_openReadWrite())
1752
               return false;
1753

    
1754
            clearstatcache();
1755
            $v_size = filesize($this->_tarname);
1756

    
1757
            // We might have zero, one or two end blocks.
1758
            // The standard is two, but we should try to handle
1759
            // other cases.
1760
            fseek($this->_file, $v_size - 1024);
1761
            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
1762
                fseek($this->_file, $v_size - 1024);
1763
            }
1764
            elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
1765
                fseek($this->_file, $v_size - 512);
1766
            }
1767
        }
1768

    
1769
        return true;
1770
    }
1771
    // }}}
1772

    
1773
    // {{{ _append()
1774
    function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
1775
    {
1776
        if (!$this->_openAppend())
1777
            return false;
1778

    
1779
        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
1780
           $this->_writeFooter();
1781

    
1782
        $this->_close();
1783

    
1784
        return true;
1785
    }
1786
    // }}}
1787

    
1788
    // {{{ _dirCheck()
1789

    
1790
    /**
1791
     * Check if a directory exists and create it (including parent
1792
     * dirs) if not.
1793
     *
1794
     * @param string $p_dir directory to check
1795
     *
1796
     * @return bool TRUE if the directory exists or was created
1797
     */
1798
    function _dirCheck($p_dir)
1799
    {
1800
        clearstatcache();
1801
        if ((@is_dir($p_dir)) || ($p_dir == ''))
1802
            return true;
1803

    
1804
        $p_parent_dir = dirname($p_dir);
1805

    
1806
        if (($p_parent_dir != $p_dir) &&
1807
            ($p_parent_dir != '') &&
1808
            (!$this->_dirCheck($p_parent_dir)))
1809
             return false;
1810

    
1811
        // Drupal integration.
1812
        // Changed the code to use drupal_mkdir() instead of mkdir().
1813
        if (!@drupal_mkdir($p_dir, 0777)) {
1814
            $this->_error("Unable to create directory '$p_dir'");
1815
            return false;
1816
        }
1817

    
1818
        return true;
1819
    }
1820

    
1821
    // }}}
1822

    
1823
    // {{{ _pathReduction()
1824

    
1825
    /**
1826
     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
1827
     * rand emove double slashes.
1828
     *
1829
     * @param string $p_dir path to reduce
1830
     *
1831
     * @return string reduced path
1832
     *
1833
     * @access private
1834
     *
1835
     */
1836
    function _pathReduction($p_dir)
1837
    {
1838
        $v_result = '';
1839

    
1840
        // ----- Look for not empty path
1841
        if ($p_dir != '') {
1842
            // ----- Explode path by directory names
1843
            $v_list = explode('/', $p_dir);
1844

    
1845
            // ----- Study directories from last to first
1846
            for ($i=sizeof($v_list)-1; $i>=0; $i--) {
1847
                // ----- Look for current path
1848
                if ($v_list[$i] == ".") {
1849
                    // ----- Ignore this directory
1850
                    // Should be the first $i=0, but no check is done
1851
                }
1852
                else if ($v_list[$i] == "..") {
1853
                    // ----- Ignore it and ignore the $i-1
1854
                    $i--;
1855
                }
1856
                else if (   ($v_list[$i] == '')
1857
				         && ($i!=(sizeof($v_list)-1))
1858
						 && ($i!=0)) {
1859
                    // ----- Ignore only the double '//' in path,
1860
                    // but not the first and last /
1861
                } else {
1862
                    $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
1863
					            .$v_result:'');
1864
                }
1865
            }
1866
        }
1867
        $v_result = strtr($v_result, '\\', '/');
1868
        return $v_result;
1869
    }
1870

    
1871
    // }}}
1872

    
1873
    // {{{ _translateWinPath()
1874
    function _translateWinPath($p_path, $p_remove_disk_letter=true)
1875
    {
1876
      if (defined('OS_WINDOWS') && OS_WINDOWS) {
1877
          // ----- Look for potential disk letter
1878
          if (   ($p_remove_disk_letter)
1879
		      && (($v_position = strpos($p_path, ':')) != false)) {
1880
              $p_path = substr($p_path, $v_position+1);
1881
          }
1882
          // ----- Change potential windows directory separator
1883
          if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
1884
              $p_path = strtr($p_path, '\\', '/');
1885
          }
1886
      }
1887
      return $p_path;
1888
    }
1889
    // }}}
1890

    
1891
}
1892
?>