Project

General

Profile

Paste
Download (86.6 KB) Statistics
| Branch: | Revision:

root / drupal7 / modules / system / system.tar.inc @ c7768a53

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
 * @category  File_Formats
34
 * @package   Archive_Tar
35
 * @author    Vincent Blavet <vincent@phpconcept.net>
36
 * @copyright 1997-2010 The Authors
37
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
38
 * @version   CVS: $Id$
39
 * @link      http://pear.php.net/package/Archive_Tar
40
 */
41

    
42
 /**
43
 * Note on Drupal 7 porting.
44
 * This file origin is Tar.php, release 1.4.9 (stable) with some code
45
 * from PEAR.php, release 1.10.10 (stable) both at http://pear.php.net.
46
 * To simplify future porting from pear of this file, you should not
47
 * do cosmetic or other non significant changes to this file.
48
 * The following changes have been done:
49
 *  Removed require_once 'PEAR.php'.
50
 *  Added defintion of OS_WINDOWS taken from PEAR.php.
51
 *  Removed extends PEAR from class.
52
 *  Removed call parent:: __construct().
53
 *  Changed PEAR::loadExtension($extname) to this->loadExtension($extname).
54
 *  Added function loadExtension() taken from PEAR.php.
55
 *  Changed all calls of unlink() to drupal_unlink().
56
 *  Changed $this->error_object = &$this->raiseError($p_message)
57
 *  to throw new Exception($p_message).
58
 */
59

    
60
// Drupal removal require_once 'PEAR.php'.
61

    
62
// Drupal addition OS_WINDOWS as defined in PEAR.php.
63
if (substr(PHP_OS, 0, 3) == 'WIN') {
64
    define('OS_WINDOWS', true);
65
} else {
66
    define('OS_WINDOWS', false);
67
}
68

    
69
define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
70
define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
71

    
72
if (!function_exists('gzopen') && function_exists('gzopen64')) {
73
    function gzopen($filename, $mode, $use_include_path = 0)
74
    {
75
        return gzopen64($filename, $mode, $use_include_path);
76
    }
77
}
78

    
79
if (!function_exists('gztell') && function_exists('gztell64')) {
80
    function gztell($zp)
81
    {
82
        return gztell64($zp);
83
    }
84
}
85

    
86
if (!function_exists('gzseek') && function_exists('gzseek64')) {
87
    function gzseek($zp, $offset, $whence = SEEK_SET)
88
    {
89
        return gzseek64($zp, $offset, $whence);
90
    }
91
}
92

    
93
/**
94
 * Creates a (compressed) Tar archive
95
 *
96
 * @package Archive_Tar
97
 * @author  Vincent Blavet <vincent@phpconcept.net>
98
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
99
 * @version $Revision$
100
 */
101
// Drupal change class Archive_Tar extends PEAR.
102
class Archive_Tar
103
{
104
    /**
105
     * @var string Name of the Tar
106
     */
107
    public $_tarname = '';
108

    
109
    /**
110
     * @var boolean if true, the Tar file will be gzipped
111
     */
112
    public $_compress = false;
113

    
114
    /**
115
     * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
116
     */
117
    public $_compress_type = 'none';
118

    
119
    /**
120
     * @var string Explode separator
121
     */
122
    public $_separator = ' ';
123

    
124
    /**
125
     * @var file descriptor
126
     */
127
    public $_file = 0;
128

    
129
    /**
130
     * @var string Local Tar name of a remote Tar (http:// or ftp://)
131
     */
132
    public $_temp_tarname = '';
133

    
134
    /**
135
     * @var string regular expression for ignoring files or directories
136
     */
137
    public $_ignore_regexp = '';
138

    
139
    /**
140
     * @var object PEAR_Error object
141
     */
142
    public $error_object = null;
143

    
144
    /**
145
     * Format for data extraction
146
     *
147
     * @var string
148
     */
149
    public $_fmt = '';
150

    
151
    /**
152
     * @var int Length of the read buffer in bytes
153
     */
154
    protected $buffer_length;
155

    
156
    /**
157
     * Archive_Tar Class constructor. This flavour of the constructor only
158
     * declare a new Archive_Tar object, identifying it by the name of the
159
     * tar file.
160
     * If the compress argument is set the tar will be read or created as a
161
     * gzip or bz2 compressed TAR file.
162
     *
163
     * @param string $p_tarname The name of the tar archive to create
164
     * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
165
     *               parameter indicates if gzip, bz2 or lzma2 compression
166
     *               is required.  For compatibility reason the
167
     *               boolean value 'true' means 'gz'.
168
     * @param int $buffer_length Length of the read buffer in bytes
169
     *
170
     * @return bool
171
     */
172
    public function __construct($p_tarname, $p_compress = null, $buffer_length = 512)
173
    {
174
        // Drupal removal parent::__construct().
175

    
176
        $this->_compress = false;
177
        $this->_compress_type = 'none';
178
        if (($p_compress === null) || ($p_compress == '')) {
179
            if (@file_exists($p_tarname)) {
180
                if ($fp = @fopen($p_tarname, "rb")) {
181
                    // look for gzip magic cookie
182
                    $data = fread($fp, 2);
183
                    fclose($fp);
184
                    if ($data == "\37\213") {
185
                        $this->_compress = true;
186
                        $this->_compress_type = 'gz';
187
                        // No sure it's enought for a magic code ....
188
                    } elseif ($data == "BZ") {
189
                        $this->_compress = true;
190
                        $this->_compress_type = 'bz2';
191
                    } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
192
                        $this->_compress = true;
193
                        $this->_compress_type = 'lzma2';
194
                    }
195
                }
196
            } else {
197
                // probably a remote file or some file accessible
198
                // through a stream interface
199
                if (substr($p_tarname, -2) == 'gz') {
200
                    $this->_compress = true;
201
                    $this->_compress_type = 'gz';
202
                } elseif ((substr($p_tarname, -3) == 'bz2') ||
203
                    (substr($p_tarname, -2) == 'bz')
204
                ) {
205
                    $this->_compress = true;
206
                    $this->_compress_type = 'bz2';
207
                } else {
208
                    if (substr($p_tarname, -2) == 'xz') {
209
                        $this->_compress = true;
210
                        $this->_compress_type = 'lzma2';
211
                    }
212
                }
213
            }
214
        } else {
215
            if (($p_compress === true) || ($p_compress == 'gz')) {
216
                $this->_compress = true;
217
                $this->_compress_type = 'gz';
218
            } else {
219
                if ($p_compress == 'bz2') {
220
                    $this->_compress = true;
221
                    $this->_compress_type = 'bz2';
222
                } else {
223
                    if ($p_compress == 'lzma2') {
224
                        $this->_compress = true;
225
                        $this->_compress_type = 'lzma2';
226
                    } else {
227
                        $this->_error(
228
                            "Unsupported compression type '$p_compress'\n" .
229
                            "Supported types are 'gz', 'bz2' and 'lzma2'.\n"
230
                        );
231
                        return false;
232
                    }
233
                }
234
            }
235
        }
236
        $this->_tarname = $p_tarname;
237
        if ($this->_compress) { // assert zlib or bz2 or xz extension support
238
            if ($this->_compress_type == 'gz') {
239
                $extname = 'zlib';
240
            } else {
241
                if ($this->_compress_type == 'bz2') {
242
                    $extname = 'bz2';
243
                } else {
244
                    if ($this->_compress_type == 'lzma2') {
245
                        $extname = 'xz';
246
                    }
247
                }
248
            }
249

    
250
            if (!extension_loaded($extname)) {
251
                // Drupal change PEAR::loadExtension($extname).
252
                $this->loadExtension($extname);
253
            }
254
            if (!extension_loaded($extname)) {
255
                $this->_error(
256
                    "The extension '$extname' couldn't be found.\n" .
257
                    "Please make sure your version of PHP was built " .
258
                    "with '$extname' support.\n"
259
                );
260
                return false;
261
            }
262
        }
263

    
264

    
265
        if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
266
            $this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
267
                "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
268
                "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
269
        } else {
270
            $this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
271
                "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
272
                "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
273
        }
274

    
275

    
276
        $this->buffer_length = $buffer_length;
277
    }
278

    
279
    public function __destruct()
280
    {
281
        $this->_close();
282
        // ----- Look for a local copy to delete
283
        if ($this->_temp_tarname != '') {
284
            @drupal_unlink($this->_temp_tarname);
285
        }
286
    }
287

    
288
    // Drupal addition from PEAR.php.
289
    /**
290
    * OS independent PHP extension load. Remember to take care
291
    * on the correct extension name for case sensitive OSes.
292
    *
293
    * @param string $ext The extension name
294
    * @return bool Success or not on the dl() call
295
    */
296
    public static function loadExtension($ext)
297
    {
298
        if (extension_loaded($ext)) {
299
            return true;
300
        }
301

    
302
        // if either returns true dl() will produce a FATAL error, stop that
303
        if (
304
            function_exists('dl') === false ||
305
            ini_get('enable_dl') != 1
306
        ) {
307
            return false;
308
        }
309

    
310
        if (OS_WINDOWS) {
311
            $suffix = '.dll';
312
        } elseif (PHP_OS == 'HP-UX') {
313
            $suffix = '.sl';
314
        } elseif (PHP_OS == 'AIX') {
315
            $suffix = '.a';
316
        } elseif (PHP_OS == 'OSX') {
317
            $suffix = '.bundle';
318
        } else {
319
            $suffix = '.so';
320
        }
321

    
322
        return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
323
    }
324

    
325

    
326
    /**
327
     * This method creates the archive file and add the files / directories
328
     * that are listed in $p_filelist.
329
     * If a file with the same name exist and is writable, it is replaced
330
     * by the new tar.
331
     * The method return false and a PEAR error text.
332
     * The $p_filelist parameter can be an array of string, each string
333
     * representing a filename or a directory name with their path if
334
     * needed. It can also be a single string with names separated by a
335
     * single blank.
336
     * For each directory added in the archive, the files and
337
     * sub-directories are also added.
338
     * See also createModify() method for more details.
339
     *
340
     * @param array $p_filelist An array of filenames and directory names, or a
341
     *              single string with names separated by a single
342
     *              blank space.
343
     *
344
     * @return true on success, false on error.
345
     * @see    createModify()
346
     */
347
    public function create($p_filelist)
348
    {
349
        return $this->createModify($p_filelist, '', '');
350
    }
351

    
352
    /**
353
     * This method add the files / directories that are listed in $p_filelist in
354
     * the archive. If the archive does not exist it is created.
355
     * The method return false and a PEAR error text.
356
     * The files and directories listed are only added at the end of the archive,
357
     * even if a file with the same name is already archived.
358
     * See also createModify() method for more details.
359
     *
360
     * @param array $p_filelist An array of filenames and directory names, or a
361
     *              single string with names separated by a single
362
     *              blank space.
363
     *
364
     * @return true on success, false on error.
365
     * @see    createModify()
366
     * @access public
367
     */
368
    public function add($p_filelist)
369
    {
370
        return $this->addModify($p_filelist, '', '');
371
    }
372

    
373
    /**
374
     * @param string $p_path
375
     * @param bool $p_preserve
376
     * @param bool $p_symlinks
377
     * @return bool
378
     */
379
    public function extract($p_path = '', $p_preserve = false, $p_symlinks = true)
380
    {
381
        return $this->extractModify($p_path, '', $p_preserve, $p_symlinks);
382
    }
383

    
384
    /**
385
     * @return array|int
386
     */
387
    public function listContent()
388
    {
389
        $v_list_detail = array();
390

    
391
        if ($this->_openRead()) {
392
            if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
393
                unset($v_list_detail);
394
                $v_list_detail = 0;
395
            }
396
            $this->_close();
397
        }
398

    
399
        return $v_list_detail;
400
    }
401

    
402
    /**
403
     * This method creates the archive file and add the files / directories
404
     * that are listed in $p_filelist.
405
     * If the file already exists and is writable, it is replaced by the
406
     * new tar. It is a create and not an add. If the file exists and is
407
     * read-only or is a directory it is not replaced. The method return
408
     * false and a PEAR error text.
409
     * The $p_filelist parameter can be an array of string, each string
410
     * representing a filename or a directory name with their path if
411
     * needed. It can also be a single string with names separated by a
412
     * single blank.
413
     * The path indicated in $p_remove_dir will be removed from the
414
     * memorized path of each file / directory listed when this path
415
     * exists. By default nothing is removed (empty path '')
416
     * The path indicated in $p_add_dir will be added at the beginning of
417
     * the memorized path of each file / directory listed. However it can
418
     * be set to empty ''. The adding of a path is done after the removing
419
     * of path.
420
     * The path add/remove ability enables the user to prepare an archive
421
     * for extraction in a different path than the origin files are.
422
     * See also addModify() method for file adding properties.
423
     *
424
     * @param array $p_filelist An array of filenames and directory names,
425
     *                             or a single string with names separated by
426
     *                             a single blank space.
427
     * @param string $p_add_dir A string which contains a path to be added
428
     *                             to the memorized path of each element in
429
     *                             the list.
430
     * @param string $p_remove_dir A string which contains a path to be
431
     *                             removed from the memorized path of each
432
     *                             element in the list, when relevant.
433
     *
434
     * @return boolean true on success, false on error.
435
     * @see addModify()
436
     */
437
    public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
438
    {
439
        $v_result = true;
440

    
441
        if (!$this->_openWrite()) {
442
            return false;
443
        }
444

    
445
        if ($p_filelist != '') {
446
            if (is_array($p_filelist)) {
447
                $v_list = $p_filelist;
448
            } elseif (is_string($p_filelist)) {
449
                $v_list = explode($this->_separator, $p_filelist);
450
            } else {
451
                $this->_cleanFile();
452
                $this->_error('Invalid file list');
453
                return false;
454
            }
455

    
456
            $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
457
        }
458

    
459
        if ($v_result) {
460
            $this->_writeFooter();
461
            $this->_close();
462
        } else {
463
            $this->_cleanFile();
464
        }
465

    
466
        return $v_result;
467
    }
468

    
469
    /**
470
     * This method add the files / directories listed in $p_filelist at the
471
     * end of the existing archive. If the archive does not yet exists it
472
     * is created.
473
     * The $p_filelist parameter can be an array of string, each string
474
     * representing a filename or a directory name with their path if
475
     * needed. It can also be a single string with names separated by a
476
     * single blank.
477
     * The path indicated in $p_remove_dir will be removed from the
478
     * memorized path of each file / directory listed when this path
479
     * exists. By default nothing is removed (empty path '')
480
     * The path indicated in $p_add_dir will be added at the beginning of
481
     * the memorized path of each file / directory listed. However it can
482
     * be set to empty ''. The adding of a path is done after the removing
483
     * of path.
484
     * The path add/remove ability enables the user to prepare an archive
485
     * for extraction in a different path than the origin files are.
486
     * If a file/dir is already in the archive it will only be added at the
487
     * end of the archive. There is no update of the existing archived
488
     * file/dir. However while extracting the archive, the last file will
489
     * replace the first one. This results in a none optimization of the
490
     * archive size.
491
     * If a file/dir does not exist the file/dir is ignored. However an
492
     * error text is send to PEAR error.
493
     * If a file/dir is not readable the file/dir is ignored. However an
494
     * error text is send to PEAR error.
495
     *
496
     * @param array $p_filelist An array of filenames and directory
497
     *                             names, or a single string with names
498
     *                             separated by a single blank space.
499
     * @param string $p_add_dir A string which contains a path to be
500
     *                             added to the memorized path of each
501
     *                             element in the list.
502
     * @param string $p_remove_dir A string which contains a path to be
503
     *                             removed from the memorized path of
504
     *                             each element in the list, when
505
     *                             relevant.
506
     *
507
     * @return true on success, false on error.
508
     */
509
    public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
510
    {
511
        $v_result = true;
512

    
513
        if (!$this->_isArchive()) {
514
            $v_result = $this->createModify(
515
                $p_filelist,
516
                $p_add_dir,
517
                $p_remove_dir
518
            );
519
        } else {
520
            if (is_array($p_filelist)) {
521
                $v_list = $p_filelist;
522
            } elseif (is_string($p_filelist)) {
523
                $v_list = explode($this->_separator, $p_filelist);
524
            } else {
525
                $this->_error('Invalid file list');
526
                return false;
527
            }
528

    
529
            $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
530
        }
531

    
532
        return $v_result;
533
    }
534

    
535
    /**
536
     * This method add a single string as a file at the
537
     * end of the existing archive. If the archive does not yet exists it
538
     * is created.
539
     *
540
     * @param string $p_filename A string which contains the full
541
     *                           filename path that will be associated
542
     *                           with the string.
543
     * @param string $p_string The content of the file added in
544
     *                           the archive.
545
     * @param bool|int $p_datetime A custom date/time (unix timestamp)
546
     *                           for the file (optional).
547
     * @param array $p_params An array of optional params:
548
     *                               stamp => the datetime (replaces
549
     *                                   datetime above if it exists)
550
     *                               mode => the permissions on the
551
     *                                   file (600 by default)
552
     *                               type => is this a link?  See the
553
     *                                   tar specification for details.
554
     *                                   (default = regular file)
555
     *                               uid => the user ID of the file
556
     *                                   (default = 0 = root)
557
     *                               gid => the group ID of the file
558
     *                                   (default = 0 = root)
559
     *
560
     * @return true on success, false on error.
561
     */
562
    public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
563
    {
564
        $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
565
        $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
566
        $p_type = @$p_params["type"] ? $p_params["type"] : "";
567
        $p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
568
        $p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
569
        $v_result = true;
570

    
571
        if (!$this->_isArchive()) {
572
            if (!$this->_openWrite()) {
573
                return false;
574
            }
575
            $this->_close();
576
        }
577

    
578
        if (!$this->_openAppend()) {
579
            return false;
580
        }
581

    
582
        // Need to check the get back to the temporary file ? ....
583
        $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
584

    
585
        $this->_writeFooter();
586

    
587
        $this->_close();
588

    
589
        return $v_result;
590
    }
591

    
592
    /**
593
     * This method extract all the content of the archive in the directory
594
     * indicated by $p_path. When relevant the memorized path of the
595
     * files/dir can be modified by removing the $p_remove_path path at the
596
     * beginning of the file/dir path.
597
     * While extracting a file, if the directory path does not exists it is
598
     * created.
599
     * While extracting a file, if the file already exists it is replaced
600
     * without looking for last modification date.
601
     * While extracting a file, if the file already exists and is write
602
     * protected, the extraction is aborted.
603
     * While extracting a file, if a directory with the same name already
604
     * exists, the extraction is aborted.
605
     * While extracting a directory, if a file with the same name already
606
     * exists, the extraction is aborted.
607
     * While extracting a file/directory if the destination directory exist
608
     * and is write protected, or does not exist but can not be created,
609
     * the extraction is aborted.
610
     * If after extraction an extracted file does not show the correct
611
     * stored file size, the extraction is aborted.
612
     * When the extraction is aborted, a PEAR error text is set and false
613
     * is returned. However the result can be a partial extraction that may
614
     * need to be manually cleaned.
615
     *
616
     * @param string $p_path The path of the directory where the
617
     *                               files/dir need to by extracted.
618
     * @param string $p_remove_path Part of the memorized path that can be
619
     *                               removed if present at the beginning of
620
     *                               the file/dir path.
621
     * @param boolean $p_preserve Preserve user/group ownership of files
622
     * @param boolean $p_symlinks Allow symlinks.
623
     *
624
     * @return boolean true on success, false on error.
625
     * @see    extractList()
626
     */
627
    public function extractModify($p_path, $p_remove_path, $p_preserve = false, $p_symlinks = true)
628
    {
629
        $v_result = true;
630
        $v_list_detail = array();
631

    
632
        if ($v_result = $this->_openRead()) {
633
            $v_result = $this->_extractList(
634
                $p_path,
635
                $v_list_detail,
636
                "complete",
637
                0,
638
                $p_remove_path,
639
                $p_preserve,
640
                $p_symlinks
641
            );
642
            $this->_close();
643
        }
644

    
645
        return $v_result;
646
    }
647

    
648
    /**
649
     * This method extract from the archive one file identified by $p_filename.
650
     * The return value is a string with the file content, or NULL on error.
651
     *
652
     * @param string $p_filename The path of the file to extract in a string.
653
     *
654
     * @return a string with the file content or NULL.
655
     */
656
    public function extractInString($p_filename)
657
    {
658
        if ($this->_openRead()) {
659
            $v_result = $this->_extractInString($p_filename);
660
            $this->_close();
661
        } else {
662
            $v_result = null;
663
        }
664

    
665
        return $v_result;
666
    }
667

    
668
    /**
669
     * This method extract from the archive only the files indicated in the
670
     * $p_filelist. These files are extracted in the current directory or
671
     * in the directory indicated by the optional $p_path parameter.
672
     * If indicated the $p_remove_path can be used in the same way as it is
673
     * used in extractModify() method.
674
     *
675
     * @param array $p_filelist An array of filenames and directory names,
676
     *                               or a single string with names separated
677
     *                               by a single blank space.
678
     * @param string $p_path The path of the directory where the
679
     *                               files/dir need to by extracted.
680
     * @param string $p_remove_path Part of the memorized path that can be
681
     *                               removed if present at the beginning of
682
     *                               the file/dir path.
683
     * @param boolean $p_preserve Preserve user/group ownership of files
684
     * @param boolean $p_symlinks Allow symlinks.
685
     *
686
     * @return true on success, false on error.
687
     * @see    extractModify()
688
     */
689
    public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false, $p_symlinks = true)
690
    {
691
        $v_result = true;
692
        $v_list_detail = array();
693

    
694
        if (is_array($p_filelist)) {
695
            $v_list = $p_filelist;
696
        } elseif (is_string($p_filelist)) {
697
            $v_list = explode($this->_separator, $p_filelist);
698
        } else {
699
            $this->_error('Invalid string list');
700
            return false;
701
        }
702

    
703
        if ($v_result = $this->_openRead()) {
704
            $v_result = $this->_extractList(
705
                $p_path,
706
                $v_list_detail,
707
                "partial",
708
                $v_list,
709
                $p_remove_path,
710
                $p_preserve,
711
                $p_symlinks
712
            );
713
            $this->_close();
714
        }
715

    
716
        return $v_result;
717
    }
718

    
719
    /**
720
     * This method set specific attributes of the archive. It uses a variable
721
     * list of parameters, in the format attribute code + attribute values :
722
     * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
723
     *
724
     * @return true on success, false on error.
725
     */
726
    public function setAttribute()
727
    {
728
        $v_result = true;
729

    
730
        // ----- Get the number of variable list of arguments
731
        if (($v_size = func_num_args()) == 0) {
732
            return true;
733
        }
734

    
735
        // ----- Get the arguments
736
        $v_att_list = func_get_args();
737

    
738
        // ----- Read the attributes
739
        $i = 0;
740
        while ($i < $v_size) {
741

    
742
            // ----- Look for next option
743
            switch ($v_att_list[$i]) {
744
                // ----- Look for options that request a string value
745
                case ARCHIVE_TAR_ATT_SEPARATOR :
746
                    // ----- Check the number of parameters
747
                    if (($i + 1) >= $v_size) {
748
                        $this->_error(
749
                            'Invalid number of parameters for '
750
                            . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
751
                        );
752
                        return false;
753
                    }
754

    
755
                    // ----- Get the value
756
                    $this->_separator = $v_att_list[$i + 1];
757
                    $i++;
758
                    break;
759

    
760
                default :
761
                    $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
762
                    return false;
763
            }
764

    
765
            // ----- Next attribute
766
            $i++;
767
        }
768

    
769
        return $v_result;
770
    }
771

    
772
    /**
773
     * This method sets the regular expression for ignoring files and directories
774
     * at import, for example:
775
     * $arch->setIgnoreRegexp("#CVS|\.svn#");
776
     *
777
     * @param string $regexp regular expression defining which files or directories to ignore
778
     */
779
    public function setIgnoreRegexp($regexp)
780
    {
781
        $this->_ignore_regexp = $regexp;
782
    }
783

    
784
    /**
785
     * This method sets the regular expression for ignoring all files and directories
786
     * matching the filenames in the array list at import, for example:
787
     * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
788
     *
789
     * @param array $list a list of file or directory names to ignore
790
     *
791
     * @access public
792
     */
793
    public function setIgnoreList($list)
794
    {
795
        $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
796
        $regexp = '#/' . join('$|/', $list) . '#';
797
        $this->setIgnoreRegexp($regexp);
798
    }
799

    
800
    /**
801
     * @param string $p_message
802
     */
803
    public function _error($p_message)
804
    {
805
        // Drupal change $this->error_object = $this->raiseError($p_message).
806
        throw new Exception($p_message);
807
    }
808

    
809
    /**
810
     * @param string $p_message
811
     */
812
    public function _warning($p_message)
813
    {
814
        // Drupal change $this->error_object = $this->raiseError($p_message).
815
        throw new Exception($p_message);
816
    }
817

    
818
    /**
819
     * @param string $p_filename
820
     * @return bool
821
     */
822
    public function _isArchive($p_filename = null)
823
    {
824
        if ($p_filename == null) {
825
            $p_filename = $this->_tarname;
826
        }
827
        clearstatcache();
828
        return @is_file($p_filename) && !@is_link($p_filename);
829
    }
830

    
831
    /**
832
     * @return bool
833
     */
834
    public function _openWrite()
835
    {
836
        if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
837
            $this->_file = @gzopen($this->_tarname, "wb9");
838
        } else {
839
            if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
840
                $this->_file = @bzopen($this->_tarname, "w");
841
            } else {
842
                if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
843
                    $this->_file = @xzopen($this->_tarname, 'w');
844
                } else {
845
                    if ($this->_compress_type == 'none') {
846
                        $this->_file = @fopen($this->_tarname, "wb");
847
                    } else {
848
                        $this->_error(
849
                            'Unknown or missing compression type ('
850
                            . $this->_compress_type . ')'
851
                        );
852
                        return false;
853
                    }
854
                }
855
            }
856
        }
857

    
858
        if ($this->_file == 0) {
859
            $this->_error(
860
                'Unable to open in write mode \''
861
                . $this->_tarname . '\''
862
            );
863
            return false;
864
        }
865

    
866
        return true;
867
    }
868

    
869
    /**
870
     * @return bool
871
     */
872
    public function _openRead()
873
    {
874
        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
875

    
876
            // ----- Look if a local copy need to be done
877
            if ($this->_temp_tarname == '') {
878
                $this->_temp_tarname = uniqid('tar') . '.tmp';
879
                if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
880
                    $this->_error(
881
                        'Unable to open in read mode \''
882
                        . $this->_tarname . '\''
883
                    );
884
                    $this->_temp_tarname = '';
885
                    return false;
886
                }
887
                if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
888
                    $this->_error(
889
                        'Unable to open in write mode \''
890
                        . $this->_temp_tarname . '\''
891
                    );
892
                    $this->_temp_tarname = '';
893
                    return false;
894
                }
895
                while ($v_data = @fread($v_file_from, 1024)) {
896
                    @fwrite($v_file_to, $v_data);
897
                }
898
                @fclose($v_file_from);
899
                @fclose($v_file_to);
900
            }
901

    
902
            // ----- File to open if the local copy
903
            $v_filename = $this->_temp_tarname;
904
        } else {
905
            // ----- File to open if the normal Tar file
906

    
907
            $v_filename = $this->_tarname;
908
        }
909

    
910
        if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
911
            $this->_file = @gzopen($v_filename, "rb");
912
        } else {
913
            if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
914
                $this->_file = @bzopen($v_filename, "r");
915
            } else {
916
                if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
917
                    $this->_file = @xzopen($v_filename, "r");
918
                } else {
919
                    if ($this->_compress_type == 'none') {
920
                        $this->_file = @fopen($v_filename, "rb");
921
                    } else {
922
                        $this->_error(
923
                            'Unknown or missing compression type ('
924
                            . $this->_compress_type . ')'
925
                        );
926
                        return false;
927
                    }
928
                }
929
            }
930
        }
931

    
932
        if ($this->_file == 0) {
933
            $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
934
            return false;
935
        }
936

    
937
        return true;
938
    }
939

    
940
    /**
941
     * @return bool
942
     */
943
    public function _openReadWrite()
944
    {
945
        if ($this->_compress_type == 'gz') {
946
            $this->_file = @gzopen($this->_tarname, "r+b");
947
        } else {
948
            if ($this->_compress_type == 'bz2') {
949
                $this->_error(
950
                    'Unable to open bz2 in read/write mode \''
951
                    . $this->_tarname . '\' (limitation of bz2 extension)'
952
                );
953
                return false;
954
            } else {
955
                if ($this->_compress_type == 'lzma2') {
956
                    $this->_error(
957
                        'Unable to open lzma2 in read/write mode \''
958
                        . $this->_tarname . '\' (limitation of lzma2 extension)'
959
                    );
960
                    return false;
961
                } else {
962
                    if ($this->_compress_type == 'none') {
963
                        $this->_file = @fopen($this->_tarname, "r+b");
964
                    } else {
965
                        $this->_error(
966
                            'Unknown or missing compression type ('
967
                            . $this->_compress_type . ')'
968
                        );
969
                        return false;
970
                    }
971
                }
972
            }
973
        }
974

    
975
        if ($this->_file == 0) {
976
            $this->_error(
977
                'Unable to open in read/write mode \''
978
                . $this->_tarname . '\''
979
            );
980
            return false;
981
        }
982

    
983
        return true;
984
    }
985

    
986
    /**
987
     * @return bool
988
     */
989
    public function _close()
990
    {
991
        //if (isset($this->_file)) {
992
        if (is_resource($this->_file)) {
993
            if ($this->_compress_type == 'gz') {
994
                @gzclose($this->_file);
995
            } else {
996
                if ($this->_compress_type == 'bz2') {
997
                    @bzclose($this->_file);
998
                } else {
999
                    if ($this->_compress_type == 'lzma2') {
1000
                        @xzclose($this->_file);
1001
                    } else {
1002
                        if ($this->_compress_type == 'none') {
1003
                            @fclose($this->_file);
1004
                        } else {
1005
                            $this->_error(
1006
                                'Unknown or missing compression type ('
1007
                                . $this->_compress_type . ')'
1008
                            );
1009
                        }
1010
                    }
1011
                }
1012
            }
1013

    
1014
            $this->_file = 0;
1015
        }
1016

    
1017
        // ----- Look if a local copy need to be erase
1018
        // Note that it might be interesting to keep the url for a time : ToDo
1019
        if ($this->_temp_tarname != '') {
1020
            @drupal_unlink($this->_temp_tarname);
1021
            $this->_temp_tarname = '';
1022
        }
1023

    
1024
        return true;
1025
    }
1026

    
1027
    /**
1028
     * @return bool
1029
     */
1030
    public function _cleanFile()
1031
    {
1032
        $this->_close();
1033

    
1034
        // ----- Look for a local copy
1035
        if ($this->_temp_tarname != '') {
1036
            // ----- Remove the local copy but not the remote tarname
1037
            @drupal_unlink($this->_temp_tarname);
1038
            $this->_temp_tarname = '';
1039
        } else {
1040
            // ----- Remove the local tarname file
1041
            @drupal_unlink($this->_tarname);
1042
        }
1043
        $this->_tarname = '';
1044

    
1045
        return true;
1046
    }
1047

    
1048
    /**
1049
     * @param mixed $p_binary_data
1050
     * @param integer $p_len
1051
     * @return bool
1052
     */
1053
    public function _writeBlock($p_binary_data, $p_len = null)
1054
    {
1055
        if (is_resource($this->_file)) {
1056
            if ($p_len === null) {
1057
                if ($this->_compress_type == 'gz') {
1058
                    @gzputs($this->_file, $p_binary_data);
1059
                } else {
1060
                    if ($this->_compress_type == 'bz2') {
1061
                        @bzwrite($this->_file, $p_binary_data);
1062
                    } else {
1063
                        if ($this->_compress_type == 'lzma2') {
1064
                            @xzwrite($this->_file, $p_binary_data);
1065
                        } else {
1066
                            if ($this->_compress_type == 'none') {
1067
                                @fputs($this->_file, $p_binary_data);
1068
                            } else {
1069
                                $this->_error(
1070
                                    'Unknown or missing compression type ('
1071
                                    . $this->_compress_type . ')'
1072
                                );
1073
                            }
1074
                        }
1075
                    }
1076
                }
1077
            } else {
1078
                if ($this->_compress_type == 'gz') {
1079
                    @gzputs($this->_file, $p_binary_data, $p_len);
1080
                } else {
1081
                    if ($this->_compress_type == 'bz2') {
1082
                        @bzwrite($this->_file, $p_binary_data, $p_len);
1083
                    } else {
1084
                        if ($this->_compress_type == 'lzma2') {
1085
                            @xzwrite($this->_file, $p_binary_data, $p_len);
1086
                        } else {
1087
                            if ($this->_compress_type == 'none') {
1088
                                @fputs($this->_file, $p_binary_data, $p_len);
1089
                            } else {
1090
                                $this->_error(
1091
                                    'Unknown or missing compression type ('
1092
                                    . $this->_compress_type . ')'
1093
                                );
1094
                            }
1095
                        }
1096
                    }
1097
                }
1098
            }
1099
        }
1100
        return true;
1101
    }
1102

    
1103
    /**
1104
     * @return null|string
1105
     */
1106
    public function _readBlock()
1107
    {
1108
        $v_block = null;
1109
        if (is_resource($this->_file)) {
1110
            if ($this->_compress_type == 'gz') {
1111
                $v_block = @gzread($this->_file, 512);
1112
            } else {
1113
                if ($this->_compress_type == 'bz2') {
1114
                    $v_block = @bzread($this->_file, 512);
1115
                } else {
1116
                    if ($this->_compress_type == 'lzma2') {
1117
                        $v_block = @xzread($this->_file, 512);
1118
                    } else {
1119
                        if ($this->_compress_type == 'none') {
1120
                            $v_block = @fread($this->_file, 512);
1121
                        } else {
1122
                            $this->_error(
1123
                                'Unknown or missing compression type ('
1124
                                . $this->_compress_type . ')'
1125
                            );
1126
                        }
1127
                    }
1128
                }
1129
            }
1130
        }
1131
        return $v_block;
1132
    }
1133

    
1134
    /**
1135
     * @param null $p_len
1136
     * @return bool
1137
     */
1138
    public function _jumpBlock($p_len = null)
1139
    {
1140
        if (is_resource($this->_file)) {
1141
            if ($p_len === null) {
1142
                $p_len = 1;
1143
            }
1144

    
1145
            if ($this->_compress_type == 'gz') {
1146
                @gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
1147
            } else {
1148
                if ($this->_compress_type == 'bz2') {
1149
                    // ----- Replace missing bztell() and bzseek()
1150
                    for ($i = 0; $i < $p_len; $i++) {
1151
                        $this->_readBlock();
1152
                    }
1153
                } else {
1154
                    if ($this->_compress_type == 'lzma2') {
1155
                        // ----- Replace missing xztell() and xzseek()
1156
                        for ($i = 0; $i < $p_len; $i++) {
1157
                            $this->_readBlock();
1158
                        }
1159
                    } else {
1160
                        if ($this->_compress_type == 'none') {
1161
                            @fseek($this->_file, $p_len * 512, SEEK_CUR);
1162
                        } else {
1163
                            $this->_error(
1164
                                'Unknown or missing compression type ('
1165
                                . $this->_compress_type . ')'
1166
                            );
1167
                        }
1168
                    }
1169
                }
1170
            }
1171
        }
1172
        return true;
1173
    }
1174

    
1175
    /**
1176
     * @return bool
1177
     */
1178
    public function _writeFooter()
1179
    {
1180
        if (is_resource($this->_file)) {
1181
            // ----- Write the last 0 filled block for end of archive
1182
            $v_binary_data = pack('a1024', '');
1183
            $this->_writeBlock($v_binary_data);
1184
        }
1185
        return true;
1186
    }
1187

    
1188
    /**
1189
     * @param array $p_list
1190
     * @param string $p_add_dir
1191
     * @param string $p_remove_dir
1192
     * @return bool
1193
     */
1194
    public function _addList($p_list, $p_add_dir, $p_remove_dir)
1195
    {
1196
        $v_result = true;
1197
        $v_header = array();
1198

    
1199
        // ----- Remove potential windows directory separator
1200
        $p_add_dir = $this->_translateWinPath($p_add_dir);
1201
        $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
1202

    
1203
        if (!$this->_file) {
1204
            $this->_error('Invalid file descriptor');
1205
            return false;
1206
        }
1207

    
1208
        if (sizeof($p_list) == 0) {
1209
            return true;
1210
        }
1211

    
1212
        foreach ($p_list as $v_filename) {
1213
            if (!$v_result) {
1214
                break;
1215
            }
1216

    
1217
            // ----- Skip the current tar name
1218
            if ($v_filename == $this->_tarname) {
1219
                continue;
1220
            }
1221

    
1222
            if ($v_filename == '') {
1223
                continue;
1224
            }
1225

    
1226
            // ----- ignore files and directories matching the ignore regular expression
1227
            if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
1228
                $this->_warning("File '$v_filename' ignored");
1229
                continue;
1230
            }
1231

    
1232
            if (!file_exists($v_filename) && !is_link($v_filename)) {
1233
                $this->_warning("File '$v_filename' does not exist");
1234
                continue;
1235
            }
1236

    
1237
            // ----- Add the file or directory header
1238
            if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
1239
                return false;
1240
            }
1241

    
1242
            if (@is_dir($v_filename) && !@is_link($v_filename)) {
1243
                if (!($p_hdir = opendir($v_filename))) {
1244
                    $this->_warning("Directory '$v_filename' can not be read");
1245
                    continue;
1246
                }
1247
                while (false !== ($p_hitem = readdir($p_hdir))) {
1248
                    if (($p_hitem != '.') && ($p_hitem != '..')) {
1249
                        if ($v_filename != ".") {
1250
                            $p_temp_list[0] = $v_filename . '/' . $p_hitem;
1251
                        } else {
1252
                            $p_temp_list[0] = $p_hitem;
1253
                        }
1254

    
1255
                        $v_result = $this->_addList(
1256
                            $p_temp_list,
1257
                            $p_add_dir,
1258
                            $p_remove_dir
1259
                        );
1260
                    }
1261
                }
1262

    
1263
                unset($p_temp_list);
1264
                unset($p_hdir);
1265
                unset($p_hitem);
1266
            }
1267
        }
1268

    
1269
        return $v_result;
1270
    }
1271

    
1272
    /**
1273
     * @param string $p_filename
1274
     * @param mixed $p_header
1275
     * @param string $p_add_dir
1276
     * @param string $p_remove_dir
1277
     * @param null $v_stored_filename
1278
     * @return bool
1279
     */
1280
    public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
1281
    {
1282
        if (!$this->_file) {
1283
            $this->_error('Invalid file descriptor');
1284
            return false;
1285
        }
1286

    
1287
        if ($p_filename == '') {
1288
            $this->_error('Invalid file name');
1289
            return false;
1290
        }
1291

    
1292
        if (is_null($v_stored_filename)) {
1293
            // ----- Calculate the stored filename
1294
            $p_filename = $this->_translateWinPath($p_filename, false);
1295
            $v_stored_filename = $p_filename;
1296

    
1297
            if (strcmp($p_filename, $p_remove_dir) == 0) {
1298
                return true;
1299
            }
1300

    
1301
            if ($p_remove_dir != '') {
1302
                if (substr($p_remove_dir, -1) != '/') {
1303
                    $p_remove_dir .= '/';
1304
                }
1305

    
1306
                if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
1307
                    $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1308
                }
1309
            }
1310

    
1311
            $v_stored_filename = $this->_translateWinPath($v_stored_filename);
1312
            if ($p_add_dir != '') {
1313
                if (substr($p_add_dir, -1) == '/') {
1314
                    $v_stored_filename = $p_add_dir . $v_stored_filename;
1315
                } else {
1316
                    $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
1317
                }
1318
            }
1319

    
1320
            $v_stored_filename = $this->_pathReduction($v_stored_filename);
1321
        }
1322

    
1323
        if ($this->_isArchive($p_filename)) {
1324
            if (($v_file = @fopen($p_filename, "rb")) == 0) {
1325
                $this->_warning(
1326
                    "Unable to open file '" . $p_filename
1327
                    . "' in binary read mode"
1328
                );
1329
                return true;
1330
            }
1331

    
1332
            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1333
                return false;
1334
            }
1335

    
1336
            while (($v_buffer = fread($v_file, $this->buffer_length)) != '') {
1337
                $buffer_length = strlen("$v_buffer");
1338
                if ($buffer_length != $this->buffer_length) {
1339
                    $pack_size = ((int)($buffer_length / 512) + 1) * 512;
1340
                    $pack_format = sprintf('a%d', $pack_size);
1341
                } else {
1342
                    $pack_format = sprintf('a%d', $this->buffer_length);
1343
                }
1344
                $v_binary_data = pack($pack_format, "$v_buffer");
1345
                $this->_writeBlock($v_binary_data);
1346
            }
1347

    
1348
            fclose($v_file);
1349
        } else {
1350
            // ----- Only header for dir
1351
            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1352
                return false;
1353
            }
1354
        }
1355

    
1356
        return true;
1357
    }
1358

    
1359
    /**
1360
     * @param string $p_filename
1361
     * @param string $p_string
1362
     * @param bool $p_datetime
1363
     * @param array $p_params
1364
     * @return bool
1365
     */
1366
    public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
1367
    {
1368
        $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
1369
        $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
1370
        $p_type = @$p_params["type"] ? $p_params["type"] : "";
1371
        $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
1372
        $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
1373
        if (!$this->_file) {
1374
            $this->_error('Invalid file descriptor');
1375
            return false;
1376
        }
1377

    
1378
        if ($p_filename == '') {
1379
            $this->_error('Invalid file name');
1380
            return false;
1381
        }
1382

    
1383
        // ----- Calculate the stored filename
1384
        $p_filename = $this->_translateWinPath($p_filename, false);
1385

    
1386
        // ----- If datetime is not specified, set current time
1387
        if ($p_datetime === false) {
1388
            $p_datetime = time();
1389
        }
1390

    
1391
        if (!$this->_writeHeaderBlock(
1392
            $p_filename,
1393
            strlen($p_string),
1394
            $p_stamp,
1395
            $p_mode,
1396
            $p_type,
1397
            $p_uid,
1398
            $p_gid
1399
        )
1400
        ) {
1401
            return false;
1402
        }
1403

    
1404
        $i = 0;
1405
        while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
1406
            $v_binary_data = pack("a512", $v_buffer);
1407
            $this->_writeBlock($v_binary_data);
1408
        }
1409

    
1410
        return true;
1411
    }
1412

    
1413
    /**
1414
     * @param string $p_filename
1415
     * @param string $p_stored_filename
1416
     * @return bool
1417
     */
1418
    public function _writeHeader($p_filename, $p_stored_filename)
1419
    {
1420
        if ($p_stored_filename == '') {
1421
            $p_stored_filename = $p_filename;
1422
        }
1423

    
1424
        $v_reduced_filename = $this->_pathReduction($p_stored_filename);
1425

    
1426
        if (strlen($v_reduced_filename) > 99) {
1427
            if (!$this->_writeLongHeader($v_reduced_filename, false)) {
1428
                return false;
1429
            }
1430
        }
1431

    
1432
        $v_linkname = '';
1433
        if (@is_link($p_filename)) {
1434
            $v_linkname = readlink($p_filename);
1435
        }
1436

    
1437
        if (strlen($v_linkname) > 99) {
1438
            if (!$this->_writeLongHeader($v_linkname, true)) {
1439
                return false;
1440
            }
1441
        }
1442

    
1443
        $v_info = lstat($p_filename);
1444
        $v_uid = sprintf("%07s", DecOct($v_info[4]));
1445
        $v_gid = sprintf("%07s", DecOct($v_info[5]));
1446
        $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
1447
        $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1448

    
1449
        if (@is_link($p_filename)) {
1450
            $v_typeflag = '2';
1451
            $v_size = sprintf("%011s", DecOct(0));
1452
        } elseif (@is_dir($p_filename)) {
1453
            $v_typeflag = "5";
1454
            $v_size = sprintf("%011s", DecOct(0));
1455
        } else {
1456
            $v_typeflag = '0';
1457
            clearstatcache();
1458
            $v_size = sprintf("%011s", DecOct($v_info['size']));
1459
        }
1460

    
1461
        $v_magic = 'ustar ';
1462
        $v_version = ' ';
1463

    
1464
        if (function_exists('posix_getpwuid')) {
1465
            $userinfo = posix_getpwuid($v_info[4]);
1466
            $groupinfo = posix_getgrgid($v_info[5]);
1467

    
1468
            $v_uname = $userinfo['name'];
1469
            $v_gname = $groupinfo['name'];
1470
        } else {
1471
            $v_uname = '';
1472
            $v_gname = '';
1473
        }
1474

    
1475
        $v_devmajor = '';
1476
        $v_devminor = '';
1477
        $v_prefix = '';
1478

    
1479
        $v_binary_data_first = pack(
1480
            "a100a8a8a8a12a12",
1481
            $v_reduced_filename,
1482
            $v_perms,
1483
            $v_uid,
1484
            $v_gid,
1485
            $v_size,
1486
            $v_mtime
1487
        );
1488
        $v_binary_data_last = pack(
1489
            "a1a100a6a2a32a32a8a8a155a12",
1490
            $v_typeflag,
1491
            $v_linkname,
1492
            $v_magic,
1493
            $v_version,
1494
            $v_uname,
1495
            $v_gname,
1496
            $v_devmajor,
1497
            $v_devminor,
1498
            $v_prefix,
1499
            ''
1500
        );
1501

    
1502
        // ----- Calculate the checksum
1503
        $v_checksum = 0;
1504
        // ..... First part of the header
1505
        for ($i = 0; $i < 148; $i++) {
1506
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1507
        }
1508
        // ..... Ignore the checksum value and replace it by ' ' (space)
1509
        for ($i = 148; $i < 156; $i++) {
1510
            $v_checksum += ord(' ');
1511
        }
1512
        // ..... Last part of the header
1513
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1514
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1515
        }
1516

    
1517
        // ----- Write the first 148 bytes of the header in the archive
1518
        $this->_writeBlock($v_binary_data_first, 148);
1519

    
1520
        // ----- Write the calculated checksum
1521
        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1522
        $v_binary_data = pack("a8", $v_checksum);
1523
        $this->_writeBlock($v_binary_data, 8);
1524

    
1525
        // ----- Write the last 356 bytes of the header in the archive
1526
        $this->_writeBlock($v_binary_data_last, 356);
1527

    
1528
        return true;
1529
    }
1530

    
1531
    /**
1532
     * @param string $p_filename
1533
     * @param int $p_size
1534
     * @param int $p_mtime
1535
     * @param int $p_perms
1536
     * @param string $p_type
1537
     * @param int $p_uid
1538
     * @param int $p_gid
1539
     * @return bool
1540
     */
1541
    public function _writeHeaderBlock(
1542
        $p_filename,
1543
        $p_size,
1544
        $p_mtime = 0,
1545
        $p_perms = 0,
1546
        $p_type = '',
1547
        $p_uid = 0,
1548
        $p_gid = 0
1549
    )
1550
    {
1551
        $p_filename = $this->_pathReduction($p_filename);
1552

    
1553
        if (strlen($p_filename) > 99) {
1554
            if (!$this->_writeLongHeader($p_filename, false)) {
1555
                return false;
1556
            }
1557
        }
1558

    
1559
        if ($p_type == "5") {
1560
            $v_size = sprintf("%011s", DecOct(0));
1561
        } else {
1562
            $v_size = sprintf("%011s", DecOct($p_size));
1563
        }
1564

    
1565
        $v_uid = sprintf("%07s", DecOct($p_uid));
1566
        $v_gid = sprintf("%07s", DecOct($p_gid));
1567
        $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1568

    
1569
        $v_mtime = sprintf("%11s", DecOct($p_mtime));
1570

    
1571
        $v_linkname = '';
1572

    
1573
        $v_magic = 'ustar ';
1574

    
1575
        $v_version = ' ';
1576

    
1577
        if (function_exists('posix_getpwuid')) {
1578
            $userinfo = posix_getpwuid($p_uid);
1579
            $groupinfo = posix_getgrgid($p_gid);
1580

    
1581
            $v_uname = $userinfo['name'];
1582
            $v_gname = $groupinfo['name'];
1583
        } else {
1584
            $v_uname = '';
1585
            $v_gname = '';
1586
        }
1587

    
1588
        $v_devmajor = '';
1589

    
1590
        $v_devminor = '';
1591

    
1592
        $v_prefix = '';
1593

    
1594
        $v_binary_data_first = pack(
1595
            "a100a8a8a8a12A12",
1596
            $p_filename,
1597
            $v_perms,
1598
            $v_uid,
1599
            $v_gid,
1600
            $v_size,
1601
            $v_mtime
1602
        );
1603
        $v_binary_data_last = pack(
1604
            "a1a100a6a2a32a32a8a8a155a12",
1605
            $p_type,
1606
            $v_linkname,
1607
            $v_magic,
1608
            $v_version,
1609
            $v_uname,
1610
            $v_gname,
1611
            $v_devmajor,
1612
            $v_devminor,
1613
            $v_prefix,
1614
            ''
1615
        );
1616

    
1617
        // ----- Calculate the checksum
1618
        $v_checksum = 0;
1619
        // ..... First part of the header
1620
        for ($i = 0; $i < 148; $i++) {
1621
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1622
        }
1623
        // ..... Ignore the checksum value and replace it by ' ' (space)
1624
        for ($i = 148; $i < 156; $i++) {
1625
            $v_checksum += ord(' ');
1626
        }
1627
        // ..... Last part of the header
1628
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1629
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1630
        }
1631

    
1632
        // ----- Write the first 148 bytes of the header in the archive
1633
        $this->_writeBlock($v_binary_data_first, 148);
1634

    
1635
        // ----- Write the calculated checksum
1636
        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1637
        $v_binary_data = pack("a8", $v_checksum);
1638
        $this->_writeBlock($v_binary_data, 8);
1639

    
1640
        // ----- Write the last 356 bytes of the header in the archive
1641
        $this->_writeBlock($v_binary_data_last, 356);
1642

    
1643
        return true;
1644
    }
1645

    
1646
    /**
1647
     * @param string $p_filename
1648
     * @return bool
1649
     */
1650
    public function _writeLongHeader($p_filename, $is_link = false)
1651
    {
1652
        $v_uid = sprintf("%07s", 0);
1653
        $v_gid = sprintf("%07s", 0);
1654
        $v_perms = sprintf("%07s", 0);
1655
        $v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
1656
        $v_mtime = sprintf("%011s", 0);
1657
        $v_typeflag = ($is_link ? 'K' : 'L');
1658
        $v_linkname = '';
1659
        $v_magic = 'ustar ';
1660
        $v_version = ' ';
1661
        $v_uname = '';
1662
        $v_gname = '';
1663
        $v_devmajor = '';
1664
        $v_devminor = '';
1665
        $v_prefix = '';
1666

    
1667
        $v_binary_data_first = pack(
1668
            "a100a8a8a8a12a12",
1669
            '././@LongLink',
1670
            $v_perms,
1671
            $v_uid,
1672
            $v_gid,
1673
            $v_size,
1674
            $v_mtime
1675
        );
1676
        $v_binary_data_last = pack(
1677
            "a1a100a6a2a32a32a8a8a155a12",
1678
            $v_typeflag,
1679
            $v_linkname,
1680
            $v_magic,
1681
            $v_version,
1682
            $v_uname,
1683
            $v_gname,
1684
            $v_devmajor,
1685
            $v_devminor,
1686
            $v_prefix,
1687
            ''
1688
        );
1689

    
1690
        // ----- Calculate the checksum
1691
        $v_checksum = 0;
1692
        // ..... First part of the header
1693
        for ($i = 0; $i < 148; $i++) {
1694
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1695
        }
1696
        // ..... Ignore the checksum value and replace it by ' ' (space)
1697
        for ($i = 148; $i < 156; $i++) {
1698
            $v_checksum += ord(' ');
1699
        }
1700
        // ..... Last part of the header
1701
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1702
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1703
        }
1704

    
1705
        // ----- Write the first 148 bytes of the header in the archive
1706
        $this->_writeBlock($v_binary_data_first, 148);
1707

    
1708
        // ----- Write the calculated checksum
1709
        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1710
        $v_binary_data = pack("a8", $v_checksum);
1711
        $this->_writeBlock($v_binary_data, 8);
1712

    
1713
        // ----- Write the last 356 bytes of the header in the archive
1714
        $this->_writeBlock($v_binary_data_last, 356);
1715

    
1716
        // ----- Write the filename as content of the block
1717
        $i = 0;
1718
        while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1719
            $v_binary_data = pack("a512", "$v_buffer");
1720
            $this->_writeBlock($v_binary_data);
1721
        }
1722

    
1723
        return true;
1724
    }
1725

    
1726
    /**
1727
     * @param mixed $v_binary_data
1728
     * @param mixed $v_header
1729
     * @return bool
1730
     */
1731
    public function _readHeader($v_binary_data, &$v_header)
1732
    {
1733
        if (strlen($v_binary_data) == 0) {
1734
            $v_header['filename'] = '';
1735
            return true;
1736
        }
1737

    
1738
        if (strlen($v_binary_data) != 512) {
1739
            $v_header['filename'] = '';
1740
            $this->_error('Invalid block size : ' . strlen($v_binary_data));
1741
            return false;
1742
        }
1743

    
1744
        if (!is_array($v_header)) {
1745
            $v_header = array();
1746
        }
1747
        // ----- Calculate the checksum
1748
        $v_checksum = 0;
1749
        // ..... First part of the header
1750
        $v_binary_split = str_split($v_binary_data);
1751
        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
1752
        $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
1753
        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
1754

    
1755

    
1756
        $v_data = unpack($this->_fmt, $v_binary_data);
1757

    
1758
        if (strlen($v_data["prefix"]) > 0) {
1759
            $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1760
        }
1761

    
1762
        // ----- Extract the checksum
1763
        $v_data_checksum = trim($v_data['checksum']);
1764
        if (!preg_match('/^[0-7]*$/', $v_data_checksum)) {
1765
            $this->_error(
1766
                'Invalid checksum for file "' . $v_data['filename']
1767
                . '" : ' . $v_data_checksum . ' extracted'
1768
            );
1769
            return false;
1770
        }
1771

    
1772
        $v_header['checksum'] = OctDec($v_data_checksum);
1773
        if ($v_header['checksum'] != $v_checksum) {
1774
            $v_header['filename'] = '';
1775

    
1776
            // ----- Look for last block (empty block)
1777
            if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1778
                return true;
1779
            }
1780

    
1781
            $this->_error(
1782
                'Invalid checksum for file "' . $v_data['filename']
1783
                . '" : ' . $v_checksum . ' calculated, '
1784
                . $v_header['checksum'] . ' expected'
1785
            );
1786
            return false;
1787
        }
1788

    
1789
        // ----- Extract the properties
1790
        $v_header['filename'] = rtrim($v_data['filename'], "\0");
1791
        if ($this->_isMaliciousFilename($v_header['filename'])) {
1792
            $this->_error(
1793
                'Malicious .tar detected, file "' . $v_header['filename'] .
1794
                '" will not install in desired directory tree'
1795
            );
1796
            return false;
1797
        }
1798
        $v_header['mode'] = OctDec(trim($v_data['mode']));
1799
        $v_header['uid'] = OctDec(trim($v_data['uid']));
1800
        $v_header['gid'] = OctDec(trim($v_data['gid']));
1801
        $v_header['size'] = $this->_tarRecToSize($v_data['size']);
1802
        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1803
        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1804
            $v_header['size'] = 0;
1805
        }
1806
        $v_header['link'] = trim($v_data['link']);
1807
        /* ----- All these fields are removed form the header because
1808
        they do not carry interesting info
1809
        $v_header[magic] = trim($v_data[magic]);
1810
        $v_header[version] = trim($v_data[version]);
1811
        $v_header[uname] = trim($v_data[uname]);
1812
        $v_header[gname] = trim($v_data[gname]);
1813
        $v_header[devmajor] = trim($v_data[devmajor]);
1814
        $v_header[devminor] = trim($v_data[devminor]);
1815
        */
1816

    
1817
        return true;
1818
    }
1819

    
1820
    /**
1821
     * Convert Tar record size to actual size
1822
     *
1823
     * @param string $tar_size
1824
     * @return size of tar record in bytes
1825
     */
1826
    private function _tarRecToSize($tar_size)
1827
    {
1828
        /*
1829
         * First byte of size has a special meaning if bit 7 is set.
1830
         *
1831
         * Bit 7 indicates base-256 encoding if set.
1832
         * Bit 6 is the sign bit.
1833
         * Bits 5:0 are most significant value bits.
1834
         */
1835
        $ch = ord($tar_size[0]);
1836
        if ($ch & 0x80) {
1837
            // Full 12-bytes record is required.
1838
            $rec_str = $tar_size . "\x00";
1839

    
1840
            $size = ($ch & 0x40) ? -1 : 0;
1841
            $size = ($size << 6) | ($ch & 0x3f);
1842

    
1843
            for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
1844
                $size = ($size * 256) + ord($rec_str[$num_ch]);
1845
            }
1846

    
1847
            return $size;
1848

    
1849
        } else {
1850
            return OctDec(trim($tar_size));
1851
        }
1852
    }
1853

    
1854
    /**
1855
     * Detect and report a malicious file name
1856
     *
1857
     * @param string $file
1858
     *
1859
     * @return bool
1860
     */
1861
    private function _isMaliciousFilename($file)
1862
    {
1863
        if (strpos($file, '://') !== false) {
1864
            return true;
1865
        }
1866
        if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) {
1867
            return true;
1868
        }
1869
        return false;
1870
    }
1871

    
1872
    /**
1873
     * @param $v_header
1874
     * @return bool
1875
     */
1876
    public function _readLongHeader(&$v_header)
1877
    {
1878
        $v_filename = '';
1879
        $v_filesize = $v_header['size'];
1880
        $n = floor($v_header['size'] / 512);
1881
        for ($i = 0; $i < $n; $i++) {
1882
            $v_content = $this->_readBlock();
1883
            $v_filename .= $v_content;
1884
        }
1885
        if (($v_header['size'] % 512) != 0) {
1886
            $v_content = $this->_readBlock();
1887
            $v_filename .= $v_content;
1888
        }
1889

    
1890
        // ----- Read the next header
1891
        $v_binary_data = $this->_readBlock();
1892

    
1893
        if (!$this->_readHeader($v_binary_data, $v_header)) {
1894
            return false;
1895
        }
1896

    
1897
        $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1898
        $v_header['filename'] = $v_filename;
1899
        if ($this->_isMaliciousFilename($v_filename)) {
1900
            $this->_error(
1901
                'Malicious .tar detected, file "' . $v_filename .
1902
                '" will not install in desired directory tree'
1903
            );
1904
            return false;
1905
        }
1906

    
1907
        return true;
1908
    }
1909

    
1910
    /**
1911
     * This method extract from the archive one file identified by $p_filename.
1912
     * The return value is a string with the file content, or null on error.
1913
     *
1914
     * @param string $p_filename The path of the file to extract in a string.
1915
     *
1916
     * @return a string with the file content or null.
1917
     */
1918
    private function _extractInString($p_filename)
1919
    {
1920
        $v_result_str = "";
1921

    
1922
        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1923
            if (!$this->_readHeader($v_binary_data, $v_header)) {
1924
                return null;
1925
            }
1926

    
1927
            if ($v_header['filename'] == '') {
1928
                continue;
1929
            }
1930

    
1931
            switch ($v_header['typeflag']) {
1932
                case 'L':
1933
                    {
1934
                        if (!$this->_readLongHeader($v_header)) {
1935
                            return null;
1936
                        }
1937
                    }
1938
                    break;
1939

    
1940
                case 'K':
1941
                    {
1942
                        $v_link_header = $v_header;
1943
                        if (!$this->_readLongHeader($v_link_header)) {
1944
                            return null;
1945
                        }
1946
                        $v_header['link'] = $v_link_header['filename'];
1947
                    }
1948
                    break;
1949
            }
1950

    
1951
            if ($v_header['filename'] == $p_filename) {
1952
                if ($v_header['typeflag'] == "5") {
1953
                    $this->_error(
1954
                        'Unable to extract in string a directory '
1955
                        . 'entry {' . $v_header['filename'] . '}'
1956
                    );
1957
                    return null;
1958
                } else {
1959
                    $n = floor($v_header['size'] / 512);
1960
                    for ($i = 0; $i < $n; $i++) {
1961
                        $v_result_str .= $this->_readBlock();
1962
                    }
1963
                    if (($v_header['size'] % 512) != 0) {
1964
                        $v_content = $this->_readBlock();
1965
                        $v_result_str .= substr(
1966
                            $v_content,
1967
                            0,
1968
                            ($v_header['size'] % 512)
1969
                        );
1970
                    }
1971
                    return $v_result_str;
1972
                }
1973
            } else {
1974
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1975
            }
1976
        }
1977

    
1978
        return null;
1979
    }
1980

    
1981
    /**
1982
     * @param string $p_path
1983
     * @param string $p_list_detail
1984
     * @param string $p_mode
1985
     * @param string $p_file_list
1986
     * @param string $p_remove_path
1987
     * @param bool $p_preserve
1988
     * @param bool $p_symlinks
1989
     * @return bool
1990
     */
1991
    public function _extractList(
1992
        $p_path,
1993
        &$p_list_detail,
1994
        $p_mode,
1995
        $p_file_list,
1996
        $p_remove_path,
1997
        $p_preserve = false,
1998
        $p_symlinks = true
1999
    )
2000
    {
2001
        $v_result = true;
2002
        $v_nb = 0;
2003
        $v_extract_all = true;
2004
        $v_listing = false;
2005

    
2006
        $p_path = $this->_translateWinPath($p_path, false);
2007
        if ($p_path == '' || (substr($p_path, 0, 1) != '/'
2008
                && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
2009
        ) {
2010
            $p_path = "./" . $p_path;
2011
        }
2012
        $p_remove_path = $this->_translateWinPath($p_remove_path);
2013

    
2014
        // ----- Look for path to remove format (should end by /)
2015
        if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
2016
            $p_remove_path .= '/';
2017
        }
2018
        $p_remove_path_size = strlen($p_remove_path);
2019

    
2020
        switch ($p_mode) {
2021
            case "complete" :
2022
                $v_extract_all = true;
2023
                $v_listing = false;
2024
                break;
2025
            case "partial" :
2026
                $v_extract_all = false;
2027
                $v_listing = false;
2028
                break;
2029
            case "list" :
2030
                $v_extract_all = false;
2031
                $v_listing = true;
2032
                break;
2033
            default :
2034
                $this->_error('Invalid extract mode (' . $p_mode . ')');
2035
                return false;
2036
        }
2037

    
2038
        clearstatcache();
2039

    
2040
        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
2041
            $v_extract_file = false;
2042
            $v_extraction_stopped = 0;
2043

    
2044
            if (!$this->_readHeader($v_binary_data, $v_header)) {
2045
                return false;
2046
            }
2047

    
2048
            if ($v_header['filename'] == '') {
2049
                continue;
2050
            }
2051

    
2052
            switch ($v_header['typeflag']) {
2053
                case 'L':
2054
                    {
2055
                        if (!$this->_readLongHeader($v_header)) {
2056
                            return null;
2057
                        }
2058
                    }
2059
                    break;
2060

    
2061
                case 'K':
2062
                    {
2063
                        $v_link_header = $v_header;
2064
                        if (!$this->_readLongHeader($v_link_header)) {
2065
                            return null;
2066
                        }
2067
                        $v_header['link'] = $v_link_header['filename'];
2068
                    }
2069
                    break;
2070
            }
2071

    
2072
            // ignore extended / pax headers
2073
            if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
2074
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2075
                continue;
2076
            }
2077

    
2078
            if ((!$v_extract_all) && (is_array($p_file_list))) {
2079
                // ----- By default no unzip if the file is not found
2080
                $v_extract_file = false;
2081

    
2082
                for ($i = 0; $i < sizeof($p_file_list); $i++) {
2083
                    // ----- Look if it is a directory
2084
                    if (substr($p_file_list[$i], -1) == '/') {
2085
                        // ----- Look if the directory is in the filename path
2086
                        if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
2087
                            && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
2088
                                == $p_file_list[$i])
2089
                        ) {
2090
                            $v_extract_file = true;
2091
                            break;
2092
                        }
2093
                    } // ----- It is a file, so compare the file names
2094
                    elseif ($p_file_list[$i] == $v_header['filename']) {
2095
                        $v_extract_file = true;
2096
                        break;
2097
                    }
2098
                }
2099
            } else {
2100
                $v_extract_file = true;
2101
            }
2102

    
2103
            // ----- Look if this file need to be extracted
2104
            if (($v_extract_file) && (!$v_listing)) {
2105
                if (($p_remove_path != '')
2106
                    && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
2107
                        == $p_remove_path)
2108
                ) {
2109
                    $v_header['filename'] = substr(
2110
                        $v_header['filename'],
2111
                        $p_remove_path_size
2112
                    );
2113
                    if ($v_header['filename'] == '') {
2114
                        continue;
2115
                    }
2116
                }
2117
                if (($p_path != './') && ($p_path != '/')) {
2118
                    while (substr($p_path, -1) == '/') {
2119
                        $p_path = substr($p_path, 0, strlen($p_path) - 1);
2120
                    }
2121

    
2122
                    if (substr($v_header['filename'], 0, 1) == '/') {
2123
                        $v_header['filename'] = $p_path . $v_header['filename'];
2124
                    } else {
2125
                        $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2126
                    }
2127
                }
2128
                if (file_exists($v_header['filename'])) {
2129
                    if ((@is_dir($v_header['filename']))
2130
                        && ($v_header['typeflag'] == '')
2131
                    ) {
2132
                        $this->_error(
2133
                            'File ' . $v_header['filename']
2134
                            . ' already exists as a directory'
2135
                        );
2136
                        return false;
2137
                    }
2138
                    if (($this->_isArchive($v_header['filename']))
2139
                        && ($v_header['typeflag'] == "5")
2140
                    ) {
2141
                        $this->_error(
2142
                            'Directory ' . $v_header['filename']
2143
                            . ' already exists as a file'
2144
                        );
2145
                        return false;
2146
                    }
2147
                    if (!is_writeable($v_header['filename'])) {
2148
                        $this->_error(
2149
                            'File ' . $v_header['filename']
2150
                            . ' already exists and is write protected'
2151
                        );
2152
                        return false;
2153
                    }
2154
                    if (filemtime($v_header['filename']) > $v_header['mtime']) {
2155
                        // To be completed : An error or silent no replace ?
2156
                    }
2157
                } // ----- Check the directory availability and create it if necessary
2158
                elseif (($v_result
2159
                        = $this->_dirCheck(
2160
                        ($v_header['typeflag'] == "5"
2161
                            ? $v_header['filename']
2162
                            : dirname($v_header['filename']))
2163
                    )) != 1
2164
                ) {
2165
                    $this->_error('Unable to create path for ' . $v_header['filename']);
2166
                    return false;
2167
                }
2168

    
2169
                if ($v_extract_file) {
2170
                    if ($v_header['typeflag'] == "5") {
2171
                        if (!@file_exists($v_header['filename'])) {
2172
                            if (!@mkdir($v_header['filename'], 0777)) {
2173
                                $this->_error(
2174
                                    'Unable to create directory {'
2175
                                    . $v_header['filename'] . '}'
2176
                                );
2177
                                return false;
2178
                            }
2179
                        }
2180
                    } elseif ($v_header['typeflag'] == "2") {
2181
                        if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) {
2182
                            $this->_error(
2183
                                'Out-of-path file extraction {'
2184
                                . $v_header['filename'] . ' --> ' .
2185
                                $v_header['link'] . '}'
2186
                            );
2187
                            return false;
2188
                        }
2189
                        if (!$p_symlinks) {
2190
                            $this->_warning('Symbolic links are not allowed. '
2191
                                . 'Unable to extract {'
2192
                                . $v_header['filename'] . '}'
2193
                            );
2194
                            return false;
2195
                        }
2196
                        if (@file_exists($v_header['filename'])) {
2197
                            @drupal_unlink($v_header['filename']);
2198
                        }
2199
                        if (!@symlink($v_header['link'], $v_header['filename'])) {
2200
                            $this->_error(
2201
                                'Unable to extract symbolic link {'
2202
                                . $v_header['filename'] . '}'
2203
                            );
2204
                            return false;
2205
                        }
2206
                    } else {
2207
                        if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2208
                            $this->_error(
2209
                                'Error while opening {' . $v_header['filename']
2210
                                . '} in write binary mode'
2211
                            );
2212
                            return false;
2213
                        } else {
2214
                            $n = floor($v_header['size'] / 512);
2215
                            for ($i = 0; $i < $n; $i++) {
2216
                                $v_content = $this->_readBlock();
2217
                                fwrite($v_dest_file, $v_content, 512);
2218
                            }
2219
                            if (($v_header['size'] % 512) != 0) {
2220
                                $v_content = $this->_readBlock();
2221
                                fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2222
                            }
2223

    
2224
                            @fclose($v_dest_file);
2225

    
2226
                            if ($p_preserve) {
2227
                                @chown($v_header['filename'], $v_header['uid']);
2228
                                @chgrp($v_header['filename'], $v_header['gid']);
2229
                            }
2230

    
2231
                            // ----- Change the file mode, mtime
2232
                            @touch($v_header['filename'], $v_header['mtime']);
2233
                            if ($v_header['mode'] & 0111) {
2234
                                // make file executable, obey umask
2235
                                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
2236
                                @chmod($v_header['filename'], $mode);
2237
                            }
2238
                        }
2239

    
2240
                        // ----- Check the file size
2241
                        clearstatcache();
2242
                        if (!is_file($v_header['filename'])) {
2243
                            $this->_error(
2244
                                'Extracted file ' . $v_header['filename']
2245
                                . 'does not exist. Archive may be corrupted.'
2246
                            );
2247
                            return false;
2248
                        }
2249

    
2250
                        $filesize = filesize($v_header['filename']);
2251
                        if ($filesize != $v_header['size']) {
2252
                            $this->_error(
2253
                                'Extracted file ' . $v_header['filename']
2254
                                . ' does not have the correct file size \''
2255
                                . $filesize
2256
                                . '\' (' . $v_header['size']
2257
                                . ' expected). Archive may be corrupted.'
2258
                            );
2259
                            return false;
2260
                        }
2261
                    }
2262
                } else {
2263
                    $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2264
                }
2265
            } else {
2266
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2267
            }
2268

    
2269
            /* TBC : Seems to be unused ...
2270
            if ($this->_compress)
2271
              $v_end_of_file = @gzeof($this->_file);
2272
            else
2273
              $v_end_of_file = @feof($this->_file);
2274
              */
2275

    
2276
            if ($v_listing || $v_extract_file || $v_extraction_stopped) {
2277
                // ----- Log extracted files
2278
                if (($v_file_dir = dirname($v_header['filename']))
2279
                    == $v_header['filename']
2280
                ) {
2281
                    $v_file_dir = '';
2282
                }
2283
                if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2284
                    $v_file_dir = '/';
2285
                }
2286

    
2287
                $p_list_detail[$v_nb++] = $v_header;
2288
                if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2289
                    return true;
2290
                }
2291
            }
2292
        }
2293

    
2294
        return true;
2295
    }
2296

    
2297
    /**
2298
     * @return bool
2299
     */
2300
    public function _openAppend()
2301
    {
2302
        if (filesize($this->_tarname) == 0) {
2303
            return $this->_openWrite();
2304
        }
2305

    
2306
        if ($this->_compress) {
2307
            $this->_close();
2308

    
2309
            if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2310
                $this->_error(
2311
                    'Error while renaming \'' . $this->_tarname
2312
                    . '\' to temporary file \'' . $this->_tarname
2313
                    . '.tmp\''
2314
                );
2315
                return false;
2316
            }
2317

    
2318
            if ($this->_compress_type == 'gz') {
2319
                $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
2320
            } elseif ($this->_compress_type == 'bz2') {
2321
                $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
2322
            } elseif ($this->_compress_type == 'lzma2') {
2323
                $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
2324
            }
2325

    
2326

    
2327
            if ($v_temp_tar == 0) {
2328
                $this->_error(
2329
                    'Unable to open file \'' . $this->_tarname
2330
                    . '.tmp\' in binary read mode'
2331
                );
2332
                @rename($this->_tarname . ".tmp", $this->_tarname);
2333
                return false;
2334
            }
2335

    
2336
            if (!$this->_openWrite()) {
2337
                @rename($this->_tarname . ".tmp", $this->_tarname);
2338
                return false;
2339
            }
2340

    
2341
            if ($this->_compress_type == 'gz') {
2342
                $end_blocks = 0;
2343

    
2344
                while (!@gzeof($v_temp_tar)) {
2345
                    $v_buffer = @gzread($v_temp_tar, 512);
2346
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2347
                        $end_blocks++;
2348
                        // do not copy end blocks, we will re-make them
2349
                        // after appending
2350
                        continue;
2351
                    } elseif ($end_blocks > 0) {
2352
                        for ($i = 0; $i < $end_blocks; $i++) {
2353
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2354
                        }
2355
                        $end_blocks = 0;
2356
                    }
2357
                    $v_binary_data = pack("a512", $v_buffer);
2358
                    $this->_writeBlock($v_binary_data);
2359
                }
2360

    
2361
                @gzclose($v_temp_tar);
2362
            } elseif ($this->_compress_type == 'bz2') {
2363
                $end_blocks = 0;
2364

    
2365
                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2366
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2367
                        $end_blocks++;
2368
                        // do not copy end blocks, we will re-make them
2369
                        // after appending
2370
                        continue;
2371
                    } elseif ($end_blocks > 0) {
2372
                        for ($i = 0; $i < $end_blocks; $i++) {
2373
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2374
                        }
2375
                        $end_blocks = 0;
2376
                    }
2377
                    $v_binary_data = pack("a512", $v_buffer);
2378
                    $this->_writeBlock($v_binary_data);
2379
                }
2380

    
2381
                @bzclose($v_temp_tar);
2382
            } elseif ($this->_compress_type == 'lzma2') {
2383
                $end_blocks = 0;
2384

    
2385
                while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2386
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2387
                        $end_blocks++;
2388
                        // do not copy end blocks, we will re-make them
2389
                        // after appending
2390
                        continue;
2391
                    } elseif ($end_blocks > 0) {
2392
                        for ($i = 0; $i < $end_blocks; $i++) {
2393
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2394
                        }
2395
                        $end_blocks = 0;
2396
                    }
2397
                    $v_binary_data = pack("a512", $v_buffer);
2398
                    $this->_writeBlock($v_binary_data);
2399
                }
2400

    
2401
                @xzclose($v_temp_tar);
2402
            }
2403

    
2404
            if (!@drupal_unlink($this->_tarname . ".tmp")) {
2405
                $this->_error(
2406
                    'Error while deleting temporary file \''
2407
                    . $this->_tarname . '.tmp\''
2408
                );
2409
            }
2410
        } else {
2411
            // ----- For not compressed tar, just add files before the last
2412
            //       one or two 512 bytes block
2413
            if (!$this->_openReadWrite()) {
2414
                return false;
2415
            }
2416

    
2417
            clearstatcache();
2418
            $v_size = filesize($this->_tarname);
2419

    
2420
            // We might have zero, one or two end blocks.
2421
            // The standard is two, but we should try to handle
2422
            // other cases.
2423
            fseek($this->_file, $v_size - 1024);
2424
            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2425
                fseek($this->_file, $v_size - 1024);
2426
            } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2427
                fseek($this->_file, $v_size - 512);
2428
            }
2429
        }
2430

    
2431
        return true;
2432
    }
2433

    
2434
    /**
2435
     * @param $p_filelist
2436
     * @param string $p_add_dir
2437
     * @param string $p_remove_dir
2438
     * @return bool
2439
     */
2440
    public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2441
    {
2442
        if (!$this->_openAppend()) {
2443
            return false;
2444
        }
2445

    
2446
        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2447
            $this->_writeFooter();
2448
        }
2449

    
2450
        $this->_close();
2451

    
2452
        return true;
2453
    }
2454

    
2455
    /**
2456
     * Check if a directory exists and create it (including parent
2457
     * dirs) if not.
2458
     *
2459
     * @param string $p_dir directory to check
2460
     *
2461
     * @return bool true if the directory exists or was created
2462
     */
2463
    public function _dirCheck($p_dir)
2464
    {
2465
        clearstatcache();
2466
        if ((@is_dir($p_dir)) || ($p_dir == '')) {
2467
            return true;
2468
        }
2469

    
2470
        $p_parent_dir = dirname($p_dir);
2471

    
2472
        if (($p_parent_dir != $p_dir) &&
2473
            ($p_parent_dir != '') &&
2474
            (!$this->_dirCheck($p_parent_dir))
2475
        ) {
2476
            return false;
2477
        }
2478

    
2479
        if (!@mkdir($p_dir, 0777)) {
2480
            $this->_error("Unable to create directory '$p_dir'");
2481
            return false;
2482
        }
2483

    
2484
        return true;
2485
    }
2486

    
2487
    /**
2488
     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2489
     * rand emove double slashes.
2490
     *
2491
     * @param string $p_dir path to reduce
2492
     *
2493
     * @return string reduced path
2494
     */
2495
    private function _pathReduction($p_dir)
2496
    {
2497
        $v_result = '';
2498

    
2499
        // ----- Look for not empty path
2500
        if ($p_dir != '') {
2501
            // ----- Explode path by directory names
2502
            $v_list = explode('/', $p_dir);
2503

    
2504
            // ----- Study directories from last to first
2505
            for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
2506
                // ----- Look for current path
2507
                if ($v_list[$i] == ".") {
2508
                    // ----- Ignore this directory
2509
                    // Should be the first $i=0, but no check is done
2510
                } else {
2511
                    if ($v_list[$i] == "..") {
2512
                        // ----- Ignore it and ignore the $i-1
2513
                        $i--;
2514
                    } else {
2515
                        if (($v_list[$i] == '')
2516
                            && ($i != (sizeof($v_list) - 1))
2517
                            && ($i != 0)
2518
                        ) {
2519
                            // ----- Ignore only the double '//' in path,
2520
                            // but not the first and last /
2521
                        } else {
2522
                            $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2523
                                    . $v_result : '');
2524
                        }
2525
                    }
2526
                }
2527
            }
2528
        }
2529

    
2530
        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2531
            $v_result = strtr($v_result, '\\', '/');
2532
        }
2533

    
2534
        return $v_result;
2535
    }
2536

    
2537
    /**
2538
     * @param $p_path
2539
     * @param bool $p_remove_disk_letter
2540
     * @return string
2541
     */
2542
    public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2543
    {
2544
        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2545
            // ----- Look for potential disk letter
2546
            if (($p_remove_disk_letter)
2547
                && (($v_position = strpos($p_path, ':')) != false)
2548
            ) {
2549
                $p_path = substr($p_path, $v_position + 1);
2550
            }
2551
            // ----- Change potential windows directory separator
2552
            if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2553
                $p_path = strtr($p_path, '\\', '/');
2554
            }
2555
        }
2556
        return $p_path;
2557
    }
2558
}