Projet

Général

Profil

Paste
Télécharger (84,8 ko) Statistiques
| Branche: | Révision:

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

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 8 porting.
44
 * This file origin is Tar.php, release 1.4.5 (stable) with some code
45
 * from PEAR.php, release 1.10.5 (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
 *  Added namespace Drupal\Core\Archiver.
50
 *  Removed require_once 'PEAR.php'.
51
 *  Added defintion of OS_WINDOWS taken from PEAR.php.
52
 *  Renamed class to ArchiveTar.
53
 *  Removed extends PEAR from class.
54
 *  Removed call parent:: __construct().
55
 *  Changed PEAR::loadExtension($extname) to this->loadExtension($extname).
56
 *  Added function loadExtension() taken from PEAR.php.
57
 *  Changed all calls of unlink() to drupal_unlink().
58
 *  Changed $this->error_object = &$this->raiseError($p_message)
59
 *  to throw new \Exception($p_message).
60
 */
61

    
62
 /**
63
 * Note on Drupal 7 backporting from Drupal 8.
64
 * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8.
65
 * The following changes have been done:
66
 *  Removed namespace Drupal\Core\Archiver.
67
 *  Renamed class to Archive_Tar.
68
 *  Changed \Exception to Exception.
69
 */
70

    
71

    
72
// Drupal removal require_once 'PEAR.php'.
73

    
74
// Drupal addition OS_WINDOWS as defined in PEAR.php.
75
if (substr(PHP_OS, 0, 3) == 'WIN') {
76
    define('OS_WINDOWS', true);
77
} else {
78
    define('OS_WINDOWS', false);
79
}
80

    
81
define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
82
define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
83

    
84
if (!function_exists('gzopen') && function_exists('gzopen64')) {
85
    function gzopen($filename, $mode, $use_include_path = 0)
86
    {
87
        return gzopen64($filename, $mode, $use_include_path);
88
    }
89
}
90

    
91
if (!function_exists('gztell') && function_exists('gztell64')) {
92
    function gztell($zp)
93
    {
94
        return gztell64($zp);
95
    }
96
}
97

    
98
if (!function_exists('gzseek') && function_exists('gzseek64')) {
99
    function gzseek($zp, $offset, $whence = SEEK_SET)
100
    {
101
        return gzseek64($zp, $offset, $whence);
102
    }
103
}
104

    
105
/**
106
 * Creates a (compressed) Tar archive
107
 *
108
 * @package Archive_Tar
109
 * @author  Vincent Blavet <vincent@phpconcept.net>
110
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
111
 * @version $Revision$
112
 */
113
// Drupal change class Archive_Tar extends PEAR.
114
class Archive_Tar
115
{
116
    /**
117
     * @var string Name of the Tar
118
     */
119
    public $_tarname = '';
120

    
121
    /**
122
     * @var boolean if true, the Tar file will be gzipped
123
     */
124
    public $_compress = false;
125

    
126
    /**
127
     * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
128
     */
129
    public $_compress_type = 'none';
130

    
131
    /**
132
     * @var string Explode separator
133
     */
134
    public $_separator = ' ';
135

    
136
    /**
137
     * @var file descriptor
138
     */
139
    public $_file = 0;
140

    
141
    /**
142
     * @var string Local Tar name of a remote Tar (http:// or ftp://)
143
     */
144
    public $_temp_tarname = '';
145

    
146
    /**
147
     * @var string regular expression for ignoring files or directories
148
     */
149
    public $_ignore_regexp = '';
150

    
151
    /**
152
     * @var object PEAR_Error object
153
     */
154
    public $error_object = null;
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
     *
169
     * @return bool
170
     */
171
    public function __construct($p_tarname, $p_compress = null)
172
    {
173
        // Drupal removal parent::__construct().
174

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

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

    
263

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

    
274

    
275
    }
276

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

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

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

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

    
320
        return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
321
    }
322

    
323

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

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

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

    
381
    /**
382
     * @return array|int
383
     */
384
    public function listContent()
385
    {
386
        $v_list_detail = array();
387

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

    
396
        return $v_list_detail;
397
    }
398

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

    
438
        if (!$this->_openWrite()) {
439
            return false;
440
        }
441

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

    
453
            $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
454
        }
455

    
456
        if ($v_result) {
457
            $this->_writeFooter();
458
            $this->_close();
459
        } else {
460
            $this->_cleanFile();
461
        }
462

    
463
        return $v_result;
464
    }
465

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

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

    
526
            $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
527
        }
528

    
529
        return $v_result;
530
    }
531

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

    
568
        if (!$this->_isArchive()) {
569
            if (!$this->_openWrite()) {
570
                return false;
571
            }
572
            $this->_close();
573
        }
574

    
575
        if (!$this->_openAppend()) {
576
            return false;
577
        }
578

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

    
582
        $this->_writeFooter();
583

    
584
        $this->_close();
585

    
586
        return $v_result;
587
    }
588

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

    
628
        if ($v_result = $this->_openRead()) {
629
            $v_result = $this->_extractList(
630
                $p_path,
631
                $v_list_detail,
632
                "complete",
633
                0,
634
                $p_remove_path,
635
                $p_preserve
636
            );
637
            $this->_close();
638
        }
639

    
640
        return $v_result;
641
    }
642

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

    
660
        return $v_result;
661
    }
662

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

    
688
        if (is_array($p_filelist)) {
689
            $v_list = $p_filelist;
690
        } elseif (is_string($p_filelist)) {
691
            $v_list = explode($this->_separator, $p_filelist);
692
        } else {
693
            $this->_error('Invalid string list');
694
            return false;
695
        }
696

    
697
        if ($v_result = $this->_openRead()) {
698
            $v_result = $this->_extractList(
699
                $p_path,
700
                $v_list_detail,
701
                "partial",
702
                $v_list,
703
                $p_remove_path,
704
                $p_preserve
705
            );
706
            $this->_close();
707
        }
708

    
709
        return $v_result;
710
    }
711

    
712
    /**
713
     * This method set specific attributes of the archive. It uses a variable
714
     * list of parameters, in the format attribute code + attribute values :
715
     * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
716
     *
717
     * @return true on success, false on error.
718
     */
719
    public function setAttribute()
720
    {
721
        $v_result = true;
722

    
723
        // ----- Get the number of variable list of arguments
724
        if (($v_size = func_num_args()) == 0) {
725
            return true;
726
        }
727

    
728
        // ----- Get the arguments
729
        $v_att_list = func_get_args();
730

    
731
        // ----- Read the attributes
732
        $i = 0;
733
        while ($i < $v_size) {
734

    
735
            // ----- Look for next option
736
            switch ($v_att_list[$i]) {
737
                // ----- Look for options that request a string value
738
                case ARCHIVE_TAR_ATT_SEPARATOR :
739
                    // ----- Check the number of parameters
740
                    if (($i + 1) >= $v_size) {
741
                        $this->_error(
742
                            'Invalid number of parameters for '
743
                            . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
744
                        );
745
                        return false;
746
                    }
747

    
748
                    // ----- Get the value
749
                    $this->_separator = $v_att_list[$i + 1];
750
                    $i++;
751
                    break;
752

    
753
                default :
754
                    $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
755
                    return false;
756
            }
757

    
758
            // ----- Next attribute
759
            $i++;
760
        }
761

    
762
        return $v_result;
763
    }
764

    
765
    /**
766
     * This method sets the regular expression for ignoring files and directories
767
     * at import, for example:
768
     * $arch->setIgnoreRegexp("#CVS|\.svn#");
769
     *
770
     * @param string $regexp regular expression defining which files or directories to ignore
771
     */
772
    public function setIgnoreRegexp($regexp)
773
    {
774
        $this->_ignore_regexp = $regexp;
775
    }
776

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

    
793
    /**
794
     * @param string $p_message
795
     */
796
    public function _error($p_message)
797
    {
798
        // Drupal change $this->error_object = $this->raiseError($p_message).
799
        throw new Exception($p_message);
800
    }
801

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

    
811
    /**
812
     * @param string $p_filename
813
     * @return bool
814
     */
815
    public function _isArchive($p_filename = null)
816
    {
817
        if ($p_filename == null) {
818
            $p_filename = $this->_tarname;
819
        }
820
        clearstatcache();
821
        return @is_file($p_filename) && !@is_link($p_filename);
822
    }
823

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

    
851
        if ($this->_file == 0) {
852
            $this->_error(
853
                'Unable to open in write mode \''
854
                . $this->_tarname . '\''
855
            );
856
            return false;
857
        }
858

    
859
        return true;
860
    }
861

    
862
    /**
863
     * @return bool
864
     */
865
    public function _openRead()
866
    {
867
        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
868

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

    
895
            // ----- File to open if the local copy
896
            $v_filename = $this->_temp_tarname;
897
        } else {
898
            // ----- File to open if the normal Tar file
899

    
900
            $v_filename = $this->_tarname;
901
        }
902

    
903
        if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
904
            $this->_file = @gzopen($v_filename, "rb");
905
        } else {
906
            if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
907
                $this->_file = @bzopen($v_filename, "r");
908
            } else {
909
                if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
910
                    $this->_file = @xzopen($v_filename, "r");
911
                } else {
912
                    if ($this->_compress_type == 'none') {
913
                        $this->_file = @fopen($v_filename, "rb");
914
                    } else {
915
                        $this->_error(
916
                            'Unknown or missing compression type ('
917
                            . $this->_compress_type . ')'
918
                        );
919
                        return false;
920
                    }
921
                }
922
            }
923
        }
924

    
925
        if ($this->_file == 0) {
926
            $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
927
            return false;
928
        }
929

    
930
        return true;
931
    }
932

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

    
968
        if ($this->_file == 0) {
969
            $this->_error(
970
                'Unable to open in read/write mode \''
971
                . $this->_tarname . '\''
972
            );
973
            return false;
974
        }
975

    
976
        return true;
977
    }
978

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

    
1007
            $this->_file = 0;
1008
        }
1009

    
1010
        // ----- Look if a local copy need to be erase
1011
        // Note that it might be interesting to keep the url for a time : ToDo
1012
        if ($this->_temp_tarname != '') {
1013
            @drupal_unlink($this->_temp_tarname);
1014
            $this->_temp_tarname = '';
1015
        }
1016

    
1017
        return true;
1018
    }
1019

    
1020
    /**
1021
     * @return bool
1022
     */
1023
    public function _cleanFile()
1024
    {
1025
        $this->_close();
1026

    
1027
        // ----- Look for a local copy
1028
        if ($this->_temp_tarname != '') {
1029
            // ----- Remove the local copy but not the remote tarname
1030
            @drupal_unlink($this->_temp_tarname);
1031
            $this->_temp_tarname = '';
1032
        } else {
1033
            // ----- Remove the local tarname file
1034
            @drupal_unlink($this->_tarname);
1035
        }
1036
        $this->_tarname = '';
1037

    
1038
        return true;
1039
    }
1040

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

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

    
1127
    /**
1128
     * @param null $p_len
1129
     * @return bool
1130
     */
1131
    public function _jumpBlock($p_len = null)
1132
    {
1133
        if (is_resource($this->_file)) {
1134
            if ($p_len === null) {
1135
                $p_len = 1;
1136
            }
1137

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

    
1168
    /**
1169
     * @return bool
1170
     */
1171
    public function _writeFooter()
1172
    {
1173
        if (is_resource($this->_file)) {
1174
            // ----- Write the last 0 filled block for end of archive
1175
            $v_binary_data = pack('a1024', '');
1176
            $this->_writeBlock($v_binary_data);
1177
        }
1178
        return true;
1179
    }
1180

    
1181
    /**
1182
     * @param array $p_list
1183
     * @param string $p_add_dir
1184
     * @param string $p_remove_dir
1185
     * @return bool
1186
     */
1187
    public function _addList($p_list, $p_add_dir, $p_remove_dir)
1188
    {
1189
        $v_result = true;
1190
        $v_header = array();
1191

    
1192
        // ----- Remove potential windows directory separator
1193
        $p_add_dir = $this->_translateWinPath($p_add_dir);
1194
        $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
1195

    
1196
        if (!$this->_file) {
1197
            $this->_error('Invalid file descriptor');
1198
            return false;
1199
        }
1200

    
1201
        if (sizeof($p_list) == 0) {
1202
            return true;
1203
        }
1204

    
1205
        foreach ($p_list as $v_filename) {
1206
            if (!$v_result) {
1207
                break;
1208
            }
1209

    
1210
            // ----- Skip the current tar name
1211
            if ($v_filename == $this->_tarname) {
1212
                continue;
1213
            }
1214

    
1215
            if ($v_filename == '') {
1216
                continue;
1217
            }
1218

    
1219
            // ----- ignore files and directories matching the ignore regular expression
1220
            if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
1221
                $this->_warning("File '$v_filename' ignored");
1222
                continue;
1223
            }
1224

    
1225
            if (!file_exists($v_filename) && !is_link($v_filename)) {
1226
                $this->_warning("File '$v_filename' does not exist");
1227
                continue;
1228
            }
1229

    
1230
            // ----- Add the file or directory header
1231
            if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
1232
                return false;
1233
            }
1234

    
1235
            if (@is_dir($v_filename) && !@is_link($v_filename)) {
1236
                if (!($p_hdir = opendir($v_filename))) {
1237
                    $this->_warning("Directory '$v_filename' can not be read");
1238
                    continue;
1239
                }
1240
                while (false !== ($p_hitem = readdir($p_hdir))) {
1241
                    if (($p_hitem != '.') && ($p_hitem != '..')) {
1242
                        if ($v_filename != ".") {
1243
                            $p_temp_list[0] = $v_filename . '/' . $p_hitem;
1244
                        } else {
1245
                            $p_temp_list[0] = $p_hitem;
1246
                        }
1247

    
1248
                        $v_result = $this->_addList(
1249
                            $p_temp_list,
1250
                            $p_add_dir,
1251
                            $p_remove_dir
1252
                        );
1253
                    }
1254
                }
1255

    
1256
                unset($p_temp_list);
1257
                unset($p_hdir);
1258
                unset($p_hitem);
1259
            }
1260
        }
1261

    
1262
        return $v_result;
1263
    }
1264

    
1265
    /**
1266
     * @param string $p_filename
1267
     * @param mixed $p_header
1268
     * @param string $p_add_dir
1269
     * @param string $p_remove_dir
1270
     * @param null $v_stored_filename
1271
     * @return bool
1272
     */
1273
    public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
1274
    {
1275
        if (!$this->_file) {
1276
            $this->_error('Invalid file descriptor');
1277
            return false;
1278
        }
1279

    
1280
        if ($p_filename == '') {
1281
            $this->_error('Invalid file name');
1282
            return false;
1283
        }
1284

    
1285
        if (is_null($v_stored_filename)) {
1286
            // ----- Calculate the stored filename
1287
            $p_filename = $this->_translateWinPath($p_filename, false);
1288
            $v_stored_filename = $p_filename;
1289

    
1290
            if (strcmp($p_filename, $p_remove_dir) == 0) {
1291
                return true;
1292
            }
1293

    
1294
            if ($p_remove_dir != '') {
1295
                if (substr($p_remove_dir, -1) != '/') {
1296
                    $p_remove_dir .= '/';
1297
                }
1298

    
1299
                if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
1300
                    $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1301
                }
1302
            }
1303

    
1304
            $v_stored_filename = $this->_translateWinPath($v_stored_filename);
1305
            if ($p_add_dir != '') {
1306
                if (substr($p_add_dir, -1) == '/') {
1307
                    $v_stored_filename = $p_add_dir . $v_stored_filename;
1308
                } else {
1309
                    $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
1310
                }
1311
            }
1312

    
1313
            $v_stored_filename = $this->_pathReduction($v_stored_filename);
1314
        }
1315

    
1316
        if ($this->_isArchive($p_filename)) {
1317
            if (($v_file = @fopen($p_filename, "rb")) == 0) {
1318
                $this->_warning(
1319
                    "Unable to open file '" . $p_filename
1320
                    . "' in binary read mode"
1321
                );
1322
                return true;
1323
            }
1324

    
1325
            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1326
                return false;
1327
            }
1328

    
1329
            while (($v_buffer = fread($v_file, 512)) != '') {
1330
                $v_binary_data = pack("a512", "$v_buffer");
1331
                $this->_writeBlock($v_binary_data);
1332
            }
1333

    
1334
            fclose($v_file);
1335
        } else {
1336
            // ----- Only header for dir
1337
            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1338
                return false;
1339
            }
1340
        }
1341

    
1342
        return true;
1343
    }
1344

    
1345
    /**
1346
     * @param string $p_filename
1347
     * @param string $p_string
1348
     * @param bool $p_datetime
1349
     * @param array $p_params
1350
     * @return bool
1351
     */
1352
    public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
1353
    {
1354
        $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
1355
        $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
1356
        $p_type = @$p_params["type"] ? $p_params["type"] : "";
1357
        $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
1358
        $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
1359
        if (!$this->_file) {
1360
            $this->_error('Invalid file descriptor');
1361
            return false;
1362
        }
1363

    
1364
        if ($p_filename == '') {
1365
            $this->_error('Invalid file name');
1366
            return false;
1367
        }
1368

    
1369
        // ----- Calculate the stored filename
1370
        $p_filename = $this->_translateWinPath($p_filename, false);
1371

    
1372
        // ----- If datetime is not specified, set current time
1373
        if ($p_datetime === false) {
1374
            $p_datetime = time();
1375
        }
1376

    
1377
        if (!$this->_writeHeaderBlock(
1378
            $p_filename,
1379
            strlen($p_string),
1380
            $p_stamp,
1381
            $p_mode,
1382
            $p_type,
1383
            $p_uid,
1384
            $p_gid
1385
        )
1386
        ) {
1387
            return false;
1388
        }
1389

    
1390
        $i = 0;
1391
        while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
1392
            $v_binary_data = pack("a512", $v_buffer);
1393
            $this->_writeBlock($v_binary_data);
1394
        }
1395

    
1396
        return true;
1397
    }
1398

    
1399
    /**
1400
     * @param string $p_filename
1401
     * @param string $p_stored_filename
1402
     * @return bool
1403
     */
1404
    public function _writeHeader($p_filename, $p_stored_filename)
1405
    {
1406
        if ($p_stored_filename == '') {
1407
            $p_stored_filename = $p_filename;
1408
        }
1409

    
1410
        $v_reduced_filename = $this->_pathReduction($p_stored_filename);
1411

    
1412
        if (strlen($v_reduced_filename) > 99) {
1413
            if (!$this->_writeLongHeader($v_reduced_filename, false)) {
1414
                return false;
1415
            }
1416
        }
1417

    
1418
        $v_linkname = '';
1419
        if (@is_link($p_filename)) {
1420
            $v_linkname = readlink($p_filename);
1421
        }
1422

    
1423
        if (strlen($v_linkname) > 99) {
1424
            if (!$this->_writeLongHeader($v_linkname, true)) {
1425
                return false;
1426
            }
1427
        }
1428

    
1429
        $v_info = lstat($p_filename);
1430
        $v_uid = sprintf("%07s", DecOct($v_info[4]));
1431
        $v_gid = sprintf("%07s", DecOct($v_info[5]));
1432
        $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
1433
        $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1434

    
1435
        if (@is_link($p_filename)) {
1436
            $v_typeflag = '2';
1437
            $v_size = sprintf("%011s", DecOct(0));
1438
        } elseif (@is_dir($p_filename)) {
1439
            $v_typeflag = "5";
1440
            $v_size = sprintf("%011s", DecOct(0));
1441
        } else {
1442
            $v_typeflag = '0';
1443
            clearstatcache();
1444
            $v_size = sprintf("%011s", DecOct($v_info['size']));
1445
        }
1446

    
1447
        $v_magic = 'ustar ';
1448
        $v_version = ' ';
1449

    
1450
        if (function_exists('posix_getpwuid')) {
1451
            $userinfo = posix_getpwuid($v_info[4]);
1452
            $groupinfo = posix_getgrgid($v_info[5]);
1453

    
1454
            $v_uname = $userinfo['name'];
1455
            $v_gname = $groupinfo['name'];
1456
        } else {
1457
            $v_uname = '';
1458
            $v_gname = '';
1459
        }
1460

    
1461
        $v_devmajor = '';
1462
        $v_devminor = '';
1463
        $v_prefix = '';
1464

    
1465
        $v_binary_data_first = pack(
1466
            "a100a8a8a8a12a12",
1467
            $v_reduced_filename,
1468
            $v_perms,
1469
            $v_uid,
1470
            $v_gid,
1471
            $v_size,
1472
            $v_mtime
1473
        );
1474
        $v_binary_data_last = pack(
1475
            "a1a100a6a2a32a32a8a8a155a12",
1476
            $v_typeflag,
1477
            $v_linkname,
1478
            $v_magic,
1479
            $v_version,
1480
            $v_uname,
1481
            $v_gname,
1482
            $v_devmajor,
1483
            $v_devminor,
1484
            $v_prefix,
1485
            ''
1486
        );
1487

    
1488
        // ----- Calculate the checksum
1489
        $v_checksum = 0;
1490
        // ..... First part of the header
1491
        for ($i = 0; $i < 148; $i++) {
1492
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1493
        }
1494
        // ..... Ignore the checksum value and replace it by ' ' (space)
1495
        for ($i = 148; $i < 156; $i++) {
1496
            $v_checksum += ord(' ');
1497
        }
1498
        // ..... Last part of the header
1499
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1500
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1501
        }
1502

    
1503
        // ----- Write the first 148 bytes of the header in the archive
1504
        $this->_writeBlock($v_binary_data_first, 148);
1505

    
1506
        // ----- Write the calculated checksum
1507
        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1508
        $v_binary_data = pack("a8", $v_checksum);
1509
        $this->_writeBlock($v_binary_data, 8);
1510

    
1511
        // ----- Write the last 356 bytes of the header in the archive
1512
        $this->_writeBlock($v_binary_data_last, 356);
1513

    
1514
        return true;
1515
    }
1516

    
1517
    /**
1518
     * @param string $p_filename
1519
     * @param int $p_size
1520
     * @param int $p_mtime
1521
     * @param int $p_perms
1522
     * @param string $p_type
1523
     * @param int $p_uid
1524
     * @param int $p_gid
1525
     * @return bool
1526
     */
1527
    public function _writeHeaderBlock(
1528
        $p_filename,
1529
        $p_size,
1530
        $p_mtime = 0,
1531
        $p_perms = 0,
1532
        $p_type = '',
1533
        $p_uid = 0,
1534
        $p_gid = 0
1535
    ) {
1536
        $p_filename = $this->_pathReduction($p_filename);
1537

    
1538
        if (strlen($p_filename) > 99) {
1539
            if (!$this->_writeLongHeader($p_filename, false)) {
1540
                return false;
1541
            }
1542
        }
1543

    
1544
        if ($p_type == "5") {
1545
            $v_size = sprintf("%011s", DecOct(0));
1546
        } else {
1547
            $v_size = sprintf("%011s", DecOct($p_size));
1548
        }
1549

    
1550
        $v_uid = sprintf("%07s", DecOct($p_uid));
1551
        $v_gid = sprintf("%07s", DecOct($p_gid));
1552
        $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1553

    
1554
        $v_mtime = sprintf("%11s", DecOct($p_mtime));
1555

    
1556
        $v_linkname = '';
1557

    
1558
        $v_magic = 'ustar ';
1559

    
1560
        $v_version = ' ';
1561

    
1562
        if (function_exists('posix_getpwuid')) {
1563
            $userinfo = posix_getpwuid($p_uid);
1564
            $groupinfo = posix_getgrgid($p_gid);
1565

    
1566
            $v_uname = $userinfo['name'];
1567
            $v_gname = $groupinfo['name'];
1568
        } else {
1569
            $v_uname = '';
1570
            $v_gname = '';
1571
        }
1572

    
1573
        $v_devmajor = '';
1574

    
1575
        $v_devminor = '';
1576

    
1577
        $v_prefix = '';
1578

    
1579
        $v_binary_data_first = pack(
1580
            "a100a8a8a8a12A12",
1581
            $p_filename,
1582
            $v_perms,
1583
            $v_uid,
1584
            $v_gid,
1585
            $v_size,
1586
            $v_mtime
1587
        );
1588
        $v_binary_data_last = pack(
1589
            "a1a100a6a2a32a32a8a8a155a12",
1590
            $p_type,
1591
            $v_linkname,
1592
            $v_magic,
1593
            $v_version,
1594
            $v_uname,
1595
            $v_gname,
1596
            $v_devmajor,
1597
            $v_devminor,
1598
            $v_prefix,
1599
            ''
1600
        );
1601

    
1602
        // ----- Calculate the checksum
1603
        $v_checksum = 0;
1604
        // ..... First part of the header
1605
        for ($i = 0; $i < 148; $i++) {
1606
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1607
        }
1608
        // ..... Ignore the checksum value and replace it by ' ' (space)
1609
        for ($i = 148; $i < 156; $i++) {
1610
            $v_checksum += ord(' ');
1611
        }
1612
        // ..... Last part of the header
1613
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1614
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1615
        }
1616

    
1617
        // ----- Write the first 148 bytes of the header in the archive
1618
        $this->_writeBlock($v_binary_data_first, 148);
1619

    
1620
        // ----- Write the calculated checksum
1621
        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1622
        $v_binary_data = pack("a8", $v_checksum);
1623
        $this->_writeBlock($v_binary_data, 8);
1624

    
1625
        // ----- Write the last 356 bytes of the header in the archive
1626
        $this->_writeBlock($v_binary_data_last, 356);
1627

    
1628
        return true;
1629
    }
1630

    
1631
    /**
1632
     * @param string $p_filename
1633
     * @return bool
1634
     */
1635
    public function _writeLongHeader($p_filename, $is_link = false)
1636
    {
1637
        $v_uid = sprintf("%07s", 0);
1638
        $v_gid = sprintf("%07s", 0);
1639
        $v_perms = sprintf("%07s", 0);
1640
        $v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
1641
        $v_mtime = sprintf("%011s", 0);
1642
        $v_typeflag = ($is_link ? 'K' : 'L');
1643
        $v_linkname = '';
1644
        $v_magic = 'ustar ';
1645
        $v_version = ' ';
1646
        $v_uname = '';
1647
        $v_gname = '';
1648
        $v_devmajor = '';
1649
        $v_devminor = '';
1650
        $v_prefix = '';
1651

    
1652
        $v_binary_data_first = pack(
1653
            "a100a8a8a8a12a12",
1654
            '././@LongLink',
1655
            $v_perms,
1656
            $v_uid,
1657
            $v_gid,
1658
            $v_size,
1659
            $v_mtime
1660
        );
1661
        $v_binary_data_last = pack(
1662
            "a1a100a6a2a32a32a8a8a155a12",
1663
            $v_typeflag,
1664
            $v_linkname,
1665
            $v_magic,
1666
            $v_version,
1667
            $v_uname,
1668
            $v_gname,
1669
            $v_devmajor,
1670
            $v_devminor,
1671
            $v_prefix,
1672
            ''
1673
        );
1674

    
1675
        // ----- Calculate the checksum
1676
        $v_checksum = 0;
1677
        // ..... First part of the header
1678
        for ($i = 0; $i < 148; $i++) {
1679
            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1680
        }
1681
        // ..... Ignore the checksum value and replace it by ' ' (space)
1682
        for ($i = 148; $i < 156; $i++) {
1683
            $v_checksum += ord(' ');
1684
        }
1685
        // ..... Last part of the header
1686
        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1687
            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1688
        }
1689

    
1690
        // ----- Write the first 148 bytes of the header in the archive
1691
        $this->_writeBlock($v_binary_data_first, 148);
1692

    
1693
        // ----- Write the calculated checksum
1694
        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1695
        $v_binary_data = pack("a8", $v_checksum);
1696
        $this->_writeBlock($v_binary_data, 8);
1697

    
1698
        // ----- Write the last 356 bytes of the header in the archive
1699
        $this->_writeBlock($v_binary_data_last, 356);
1700

    
1701
        // ----- Write the filename as content of the block
1702
        $i = 0;
1703
        while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1704
            $v_binary_data = pack("a512", "$v_buffer");
1705
            $this->_writeBlock($v_binary_data);
1706
        }
1707

    
1708
        return true;
1709
    }
1710

    
1711
    /**
1712
     * @param mixed $v_binary_data
1713
     * @param mixed $v_header
1714
     * @return bool
1715
     */
1716
    public function _readHeader($v_binary_data, &$v_header)
1717
    {
1718
        if (strlen($v_binary_data) == 0) {
1719
            $v_header['filename'] = '';
1720
            return true;
1721
        }
1722

    
1723
        if (strlen($v_binary_data) != 512) {
1724
            $v_header['filename'] = '';
1725
            $this->_error('Invalid block size : ' . strlen($v_binary_data));
1726
            return false;
1727
        }
1728

    
1729
        if (!is_array($v_header)) {
1730
            $v_header = array();
1731
        }
1732
        // ----- Calculate the checksum
1733
        $v_checksum = 0;
1734
        // ..... First part of the header
1735
        $v_binary_split = str_split($v_binary_data);
1736
        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
1737
        $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
1738
        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
1739

    
1740

    
1741
        $v_data = unpack($this->_fmt, $v_binary_data);
1742

    
1743
        if (strlen($v_data["prefix"]) > 0) {
1744
            $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1745
        }
1746

    
1747
        // ----- Extract the checksum
1748
        $v_header['checksum'] = OctDec(trim($v_data['checksum']));
1749
        if ($v_header['checksum'] != $v_checksum) {
1750
            $v_header['filename'] = '';
1751

    
1752
            // ----- Look for last block (empty block)
1753
            if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1754
                return true;
1755
            }
1756

    
1757
            $this->_error(
1758
                'Invalid checksum for file "' . $v_data['filename']
1759
                . '" : ' . $v_checksum . ' calculated, '
1760
                . $v_header['checksum'] . ' expected'
1761
            );
1762
            return false;
1763
        }
1764

    
1765
        // ----- Extract the properties
1766
        $v_header['filename'] = rtrim($v_data['filename'], "\0");
1767
        if ($this->_maliciousFilename($v_header['filename'])) {
1768
            $this->_error(
1769
                'Malicious .tar detected, file "' . $v_header['filename'] .
1770
                '" will not install in desired directory tree'
1771
            );
1772
            return false;
1773
        }
1774
        $v_header['mode'] = OctDec(trim($v_data['mode']));
1775
        $v_header['uid'] = OctDec(trim($v_data['uid']));
1776
        $v_header['gid'] = OctDec(trim($v_data['gid']));
1777
        $v_header['size'] = $this->_tarRecToSize($v_data['size']);
1778
        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1779
        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1780
            $v_header['size'] = 0;
1781
        }
1782
        $v_header['link'] = trim($v_data['link']);
1783
        /* ----- All these fields are removed form the header because
1784
        they do not carry interesting info
1785
        $v_header[magic] = trim($v_data[magic]);
1786
        $v_header[version] = trim($v_data[version]);
1787
        $v_header[uname] = trim($v_data[uname]);
1788
        $v_header[gname] = trim($v_data[gname]);
1789
        $v_header[devmajor] = trim($v_data[devmajor]);
1790
        $v_header[devminor] = trim($v_data[devminor]);
1791
        */
1792

    
1793
        return true;
1794
    }
1795

    
1796
    /**
1797
     * Convert Tar record size to actual size
1798
     *
1799
     * @param string $tar_size
1800
     * @return size of tar record in bytes
1801
     */
1802
    private function _tarRecToSize($tar_size)
1803
    {
1804
        /*
1805
         * First byte of size has a special meaning if bit 7 is set.
1806
         *
1807
         * Bit 7 indicates base-256 encoding if set.
1808
         * Bit 6 is the sign bit.
1809
         * Bits 5:0 are most significant value bits.
1810
         */
1811
        $ch = ord($tar_size[0]);
1812
        if ($ch & 0x80) {
1813
            // Full 12-bytes record is required.
1814
            $rec_str = $tar_size . "\x00";
1815

    
1816
            $size = ($ch & 0x40) ? -1 : 0;
1817
            $size = ($size << 6) | ($ch & 0x3f);
1818

    
1819
            for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
1820
                $size = ($size * 256) + ord($rec_str[$num_ch]);
1821
            }
1822

    
1823
            return $size;
1824

    
1825
        } else {
1826
            return OctDec(trim($tar_size));
1827
        }
1828
    }
1829

    
1830
    /**
1831
     * Detect and report a malicious file name
1832
     *
1833
     * @param string $file
1834
     *
1835
     * @return bool
1836
     */
1837
    private function _maliciousFilename($file)
1838
    {
1839
        if (strpos($file, 'phar://') === 0) {
1840
            return true;
1841
        }
1842
        if (strpos($file, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) !== false) {
1843
            return true;
1844
        }
1845
        if (strpos($file, '..' . DIRECTORY_SEPARATOR) === 0) {
1846
            return true;
1847
        }
1848
        return false;
1849
    }
1850

    
1851
    /**
1852
     * @param $v_header
1853
     * @return bool
1854
     */
1855
    public function _readLongHeader(&$v_header)
1856
    {
1857
        $v_filename = '';
1858
        $v_filesize = $v_header['size'];
1859
        $n = floor($v_header['size'] / 512);
1860
        for ($i = 0; $i < $n; $i++) {
1861
            $v_content = $this->_readBlock();
1862
            $v_filename .= $v_content;
1863
        }
1864
        if (($v_header['size'] % 512) != 0) {
1865
            $v_content = $this->_readBlock();
1866
            $v_filename .= $v_content;
1867
        }
1868

    
1869
        // ----- Read the next header
1870
        $v_binary_data = $this->_readBlock();
1871

    
1872
        if (!$this->_readHeader($v_binary_data, $v_header)) {
1873
            return false;
1874
        }
1875

    
1876
        $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1877
        $v_header['filename'] = $v_filename;
1878
        if ($this->_maliciousFilename($v_filename)) {
1879
            $this->_error(
1880
                'Malicious .tar detected, file "' . $v_filename .
1881
                '" will not install in desired directory tree'
1882
            );
1883
            return false;
1884
        }
1885

    
1886
        return true;
1887
    }
1888

    
1889
    /**
1890
     * This method extract from the archive one file identified by $p_filename.
1891
     * The return value is a string with the file content, or null on error.
1892
     *
1893
     * @param string $p_filename The path of the file to extract in a string.
1894
     *
1895
     * @return a string with the file content or null.
1896
     */
1897
    private function _extractInString($p_filename)
1898
    {
1899
        $v_result_str = "";
1900

    
1901
        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1902
            if (!$this->_readHeader($v_binary_data, $v_header)) {
1903
                return null;
1904
            }
1905

    
1906
            if ($v_header['filename'] == '') {
1907
                continue;
1908
            }
1909

    
1910
            switch ($v_header['typeflag']) {
1911
                case 'L': {
1912
                    if (!$this->_readLongHeader($v_header)) {
1913
                        return null;
1914
                    }
1915
                } break;
1916

    
1917
                case 'K': {
1918
                    $v_link_header = $v_header;
1919
                    if (!$this->_readLongHeader($v_link_header)) {
1920
                        return null;
1921
                    }
1922
                    $v_header['link'] = $v_link_header['filename'];
1923
                } break;
1924
            }
1925

    
1926
            if ($v_header['filename'] == $p_filename) {
1927
                if ($v_header['typeflag'] == "5") {
1928
                    $this->_error(
1929
                        'Unable to extract in string a directory '
1930
                        . 'entry {' . $v_header['filename'] . '}'
1931
                    );
1932
                    return null;
1933
                } else {
1934
                    $n = floor($v_header['size'] / 512);
1935
                    for ($i = 0; $i < $n; $i++) {
1936
                        $v_result_str .= $this->_readBlock();
1937
                    }
1938
                    if (($v_header['size'] % 512) != 0) {
1939
                        $v_content = $this->_readBlock();
1940
                        $v_result_str .= substr(
1941
                            $v_content,
1942
                            0,
1943
                            ($v_header['size'] % 512)
1944
                        );
1945
                    }
1946
                    return $v_result_str;
1947
                }
1948
            } else {
1949
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1950
            }
1951
        }
1952

    
1953
        return null;
1954
    }
1955

    
1956
    /**
1957
     * @param string $p_path
1958
     * @param string $p_list_detail
1959
     * @param string $p_mode
1960
     * @param string $p_file_list
1961
     * @param string $p_remove_path
1962
     * @param bool $p_preserve
1963
     * @return bool
1964
     */
1965
    public function _extractList(
1966
        $p_path,
1967
        &$p_list_detail,
1968
        $p_mode,
1969
        $p_file_list,
1970
        $p_remove_path,
1971
        $p_preserve = false
1972
    ) {
1973
        $v_result = true;
1974
        $v_nb = 0;
1975
        $v_extract_all = true;
1976
        $v_listing = false;
1977

    
1978
        $p_path = $this->_translateWinPath($p_path, false);
1979
        if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1980
                && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
1981
        ) {
1982
            $p_path = "./" . $p_path;
1983
        }
1984
        $p_remove_path = $this->_translateWinPath($p_remove_path);
1985

    
1986
        // ----- Look for path to remove format (should end by /)
1987
        if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
1988
            $p_remove_path .= '/';
1989
        }
1990
        $p_remove_path_size = strlen($p_remove_path);
1991

    
1992
        switch ($p_mode) {
1993
            case "complete" :
1994
                $v_extract_all = true;
1995
                $v_listing = false;
1996
                break;
1997
            case "partial" :
1998
                $v_extract_all = false;
1999
                $v_listing = false;
2000
                break;
2001
            case "list" :
2002
                $v_extract_all = false;
2003
                $v_listing = true;
2004
                break;
2005
            default :
2006
                $this->_error('Invalid extract mode (' . $p_mode . ')');
2007
                return false;
2008
        }
2009

    
2010
        clearstatcache();
2011

    
2012
        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
2013
            $v_extract_file = false;
2014
            $v_extraction_stopped = 0;
2015

    
2016
            if (!$this->_readHeader($v_binary_data, $v_header)) {
2017
                return false;
2018
            }
2019

    
2020
            if ($v_header['filename'] == '') {
2021
                continue;
2022
            }
2023

    
2024
            switch ($v_header['typeflag']) {
2025
                case 'L': {
2026
                    if (!$this->_readLongHeader($v_header)) {
2027
                        return null;
2028
                    }
2029
                } break;
2030

    
2031
                case 'K': {
2032
                    $v_link_header = $v_header;
2033
                    if (!$this->_readLongHeader($v_link_header)) {
2034
                        return null;
2035
                    }
2036
                    $v_header['link'] = $v_link_header['filename'];
2037
                } break;
2038
            }
2039

    
2040
            // ignore extended / pax headers
2041
            if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
2042
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2043
                continue;
2044
            }
2045

    
2046
            if ((!$v_extract_all) && (is_array($p_file_list))) {
2047
                // ----- By default no unzip if the file is not found
2048
                $v_extract_file = false;
2049

    
2050
                for ($i = 0; $i < sizeof($p_file_list); $i++) {
2051
                    // ----- Look if it is a directory
2052
                    if (substr($p_file_list[$i], -1) == '/') {
2053
                        // ----- Look if the directory is in the filename path
2054
                        if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
2055
                            && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
2056
                                == $p_file_list[$i])
2057
                        ) {
2058
                            $v_extract_file = true;
2059
                            break;
2060
                        }
2061
                    } // ----- It is a file, so compare the file names
2062
                    elseif ($p_file_list[$i] == $v_header['filename']) {
2063
                        $v_extract_file = true;
2064
                        break;
2065
                    }
2066
                }
2067
            } else {
2068
                $v_extract_file = true;
2069
            }
2070

    
2071
            // ----- Look if this file need to be extracted
2072
            if (($v_extract_file) && (!$v_listing)) {
2073
                if (($p_remove_path != '')
2074
                    && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
2075
                        == $p_remove_path)
2076
                ) {
2077
                    $v_header['filename'] = substr(
2078
                        $v_header['filename'],
2079
                        $p_remove_path_size
2080
                    );
2081
                    if ($v_header['filename'] == '') {
2082
                        continue;
2083
                    }
2084
                }
2085
                if (($p_path != './') && ($p_path != '/')) {
2086
                    while (substr($p_path, -1) == '/') {
2087
                        $p_path = substr($p_path, 0, strlen($p_path) - 1);
2088
                    }
2089

    
2090
                    if (substr($v_header['filename'], 0, 1) == '/') {
2091
                        $v_header['filename'] = $p_path . $v_header['filename'];
2092
                    } else {
2093
                        $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2094
                    }
2095
                }
2096
                if (file_exists($v_header['filename'])) {
2097
                    if ((@is_dir($v_header['filename']))
2098
                        && ($v_header['typeflag'] == '')
2099
                    ) {
2100
                        $this->_error(
2101
                            'File ' . $v_header['filename']
2102
                            . ' already exists as a directory'
2103
                        );
2104
                        return false;
2105
                    }
2106
                    if (($this->_isArchive($v_header['filename']))
2107
                        && ($v_header['typeflag'] == "5")
2108
                    ) {
2109
                        $this->_error(
2110
                            'Directory ' . $v_header['filename']
2111
                            . ' already exists as a file'
2112
                        );
2113
                        return false;
2114
                    }
2115
                    if (!is_writeable($v_header['filename'])) {
2116
                        $this->_error(
2117
                            'File ' . $v_header['filename']
2118
                            . ' already exists and is write protected'
2119
                        );
2120
                        return false;
2121
                    }
2122
                    if (filemtime($v_header['filename']) > $v_header['mtime']) {
2123
                        // To be completed : An error or silent no replace ?
2124
                    }
2125
                } // ----- Check the directory availability and create it if necessary
2126
                elseif (($v_result
2127
                        = $this->_dirCheck(
2128
                        ($v_header['typeflag'] == "5"
2129
                            ? $v_header['filename']
2130
                            : dirname($v_header['filename']))
2131
                    )) != 1
2132
                ) {
2133
                    $this->_error('Unable to create path for ' . $v_header['filename']);
2134
                    return false;
2135
                }
2136

    
2137
                if ($v_extract_file) {
2138
                    if ($v_header['typeflag'] == "5") {
2139
                        if (!@file_exists($v_header['filename'])) {
2140
                            if (!@mkdir($v_header['filename'], 0777)) {
2141
                                $this->_error(
2142
                                    'Unable to create directory {'
2143
                                    . $v_header['filename'] . '}'
2144
                                );
2145
                                return false;
2146
                            }
2147
                        }
2148
                    } elseif ($v_header['typeflag'] == "2") {
2149
                        if (@file_exists($v_header['filename'])) {
2150
                            @drupal_unlink($v_header['filename']);
2151
                        }
2152
                        if (!@symlink($v_header['link'], $v_header['filename'])) {
2153
                            $this->_error(
2154
                                'Unable to extract symbolic link {'
2155
                                . $v_header['filename'] . '}'
2156
                            );
2157
                            return false;
2158
                        }
2159
                    } else {
2160
                        if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2161
                            $this->_error(
2162
                                'Error while opening {' . $v_header['filename']
2163
                                . '} in write binary mode'
2164
                            );
2165
                            return false;
2166
                        } else {
2167
                            $n = floor($v_header['size'] / 512);
2168
                            for ($i = 0; $i < $n; $i++) {
2169
                                $v_content = $this->_readBlock();
2170
                                fwrite($v_dest_file, $v_content, 512);
2171
                            }
2172
                            if (($v_header['size'] % 512) != 0) {
2173
                                $v_content = $this->_readBlock();
2174
                                fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2175
                            }
2176

    
2177
                            @fclose($v_dest_file);
2178

    
2179
                            if ($p_preserve) {
2180
                                @chown($v_header['filename'], $v_header['uid']);
2181
                                @chgrp($v_header['filename'], $v_header['gid']);
2182
                            }
2183

    
2184
                            // ----- Change the file mode, mtime
2185
                            @touch($v_header['filename'], $v_header['mtime']);
2186
                            if ($v_header['mode'] & 0111) {
2187
                                // make file executable, obey umask
2188
                                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
2189
                                @chmod($v_header['filename'], $mode);
2190
                            }
2191
                        }
2192

    
2193
                        // ----- Check the file size
2194
                        clearstatcache();
2195
                        if (!is_file($v_header['filename'])) {
2196
                            $this->_error(
2197
                                'Extracted file ' . $v_header['filename']
2198
                                . 'does not exist. Archive may be corrupted.'
2199
                            );
2200
                            return false;
2201
                        }
2202

    
2203
                        $filesize = filesize($v_header['filename']);
2204
                        if ($filesize != $v_header['size']) {
2205
                            $this->_error(
2206
                                'Extracted file ' . $v_header['filename']
2207
                                . ' does not have the correct file size \''
2208
                                . $filesize
2209
                                . '\' (' . $v_header['size']
2210
                                . ' expected). Archive may be corrupted.'
2211
                            );
2212
                            return false;
2213
                        }
2214
                    }
2215
                } else {
2216
                    $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2217
                }
2218
            } else {
2219
                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2220
            }
2221

    
2222
            /* TBC : Seems to be unused ...
2223
            if ($this->_compress)
2224
              $v_end_of_file = @gzeof($this->_file);
2225
            else
2226
              $v_end_of_file = @feof($this->_file);
2227
              */
2228

    
2229
            if ($v_listing || $v_extract_file || $v_extraction_stopped) {
2230
                // ----- Log extracted files
2231
                if (($v_file_dir = dirname($v_header['filename']))
2232
                    == $v_header['filename']
2233
                ) {
2234
                    $v_file_dir = '';
2235
                }
2236
                if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2237
                    $v_file_dir = '/';
2238
                }
2239

    
2240
                $p_list_detail[$v_nb++] = $v_header;
2241
                if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2242
                    return true;
2243
                }
2244
            }
2245
        }
2246

    
2247
        return true;
2248
    }
2249

    
2250
    /**
2251
     * @return bool
2252
     */
2253
    public function _openAppend()
2254
    {
2255
        if (filesize($this->_tarname) == 0) {
2256
            return $this->_openWrite();
2257
        }
2258

    
2259
        if ($this->_compress) {
2260
            $this->_close();
2261

    
2262
            if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2263
                $this->_error(
2264
                    'Error while renaming \'' . $this->_tarname
2265
                    . '\' to temporary file \'' . $this->_tarname
2266
                    . '.tmp\''
2267
                );
2268
                return false;
2269
            }
2270

    
2271
            if ($this->_compress_type == 'gz') {
2272
                $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
2273
            } elseif ($this->_compress_type == 'bz2') {
2274
                $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
2275
            } elseif ($this->_compress_type == 'lzma2') {
2276
                $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
2277
            }
2278

    
2279

    
2280
            if ($v_temp_tar == 0) {
2281
                $this->_error(
2282
                    'Unable to open file \'' . $this->_tarname
2283
                    . '.tmp\' in binary read mode'
2284
                );
2285
                @rename($this->_tarname . ".tmp", $this->_tarname);
2286
                return false;
2287
            }
2288

    
2289
            if (!$this->_openWrite()) {
2290
                @rename($this->_tarname . ".tmp", $this->_tarname);
2291
                return false;
2292
            }
2293

    
2294
            if ($this->_compress_type == 'gz') {
2295
                $end_blocks = 0;
2296

    
2297
                while (!@gzeof($v_temp_tar)) {
2298
                    $v_buffer = @gzread($v_temp_tar, 512);
2299
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2300
                        $end_blocks++;
2301
                        // do not copy end blocks, we will re-make them
2302
                        // after appending
2303
                        continue;
2304
                    } elseif ($end_blocks > 0) {
2305
                        for ($i = 0; $i < $end_blocks; $i++) {
2306
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2307
                        }
2308
                        $end_blocks = 0;
2309
                    }
2310
                    $v_binary_data = pack("a512", $v_buffer);
2311
                    $this->_writeBlock($v_binary_data);
2312
                }
2313

    
2314
                @gzclose($v_temp_tar);
2315
            } elseif ($this->_compress_type == 'bz2') {
2316
                $end_blocks = 0;
2317

    
2318
                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2319
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2320
                        $end_blocks++;
2321
                        // do not copy end blocks, we will re-make them
2322
                        // after appending
2323
                        continue;
2324
                    } elseif ($end_blocks > 0) {
2325
                        for ($i = 0; $i < $end_blocks; $i++) {
2326
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2327
                        }
2328
                        $end_blocks = 0;
2329
                    }
2330
                    $v_binary_data = pack("a512", $v_buffer);
2331
                    $this->_writeBlock($v_binary_data);
2332
                }
2333

    
2334
                @bzclose($v_temp_tar);
2335
            } elseif ($this->_compress_type == 'lzma2') {
2336
                $end_blocks = 0;
2337

    
2338
                while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2339
                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2340
                        $end_blocks++;
2341
                        // do not copy end blocks, we will re-make them
2342
                        // after appending
2343
                        continue;
2344
                    } elseif ($end_blocks > 0) {
2345
                        for ($i = 0; $i < $end_blocks; $i++) {
2346
                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2347
                        }
2348
                        $end_blocks = 0;
2349
                    }
2350
                    $v_binary_data = pack("a512", $v_buffer);
2351
                    $this->_writeBlock($v_binary_data);
2352
                }
2353

    
2354
                @xzclose($v_temp_tar);
2355
            }
2356

    
2357
            if (!@drupal_unlink($this->_tarname . ".tmp")) {
2358
                $this->_error(
2359
                    'Error while deleting temporary file \''
2360
                    . $this->_tarname . '.tmp\''
2361
                );
2362
            }
2363
        } else {
2364
            // ----- For not compressed tar, just add files before the last
2365
            //       one or two 512 bytes block
2366
            if (!$this->_openReadWrite()) {
2367
                return false;
2368
            }
2369

    
2370
            clearstatcache();
2371
            $v_size = filesize($this->_tarname);
2372

    
2373
            // We might have zero, one or two end blocks.
2374
            // The standard is two, but we should try to handle
2375
            // other cases.
2376
            fseek($this->_file, $v_size - 1024);
2377
            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2378
                fseek($this->_file, $v_size - 1024);
2379
            } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2380
                fseek($this->_file, $v_size - 512);
2381
            }
2382
        }
2383

    
2384
        return true;
2385
    }
2386

    
2387
    /**
2388
     * @param $p_filelist
2389
     * @param string $p_add_dir
2390
     * @param string $p_remove_dir
2391
     * @return bool
2392
     */
2393
    public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2394
    {
2395
        if (!$this->_openAppend()) {
2396
            return false;
2397
        }
2398

    
2399
        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2400
            $this->_writeFooter();
2401
        }
2402

    
2403
        $this->_close();
2404

    
2405
        return true;
2406
    }
2407

    
2408
    /**
2409
     * Check if a directory exists and create it (including parent
2410
     * dirs) if not.
2411
     *
2412
     * @param string $p_dir directory to check
2413
     *
2414
     * @return bool true if the directory exists or was created
2415
     */
2416
    public function _dirCheck($p_dir)
2417
    {
2418
        clearstatcache();
2419
        if ((@is_dir($p_dir)) || ($p_dir == '')) {
2420
            return true;
2421
        }
2422

    
2423
        $p_parent_dir = dirname($p_dir);
2424

    
2425
        if (($p_parent_dir != $p_dir) &&
2426
            ($p_parent_dir != '') &&
2427
            (!$this->_dirCheck($p_parent_dir))
2428
        ) {
2429
            return false;
2430
        }
2431

    
2432
        if (!@mkdir($p_dir, 0777)) {
2433
            $this->_error("Unable to create directory '$p_dir'");
2434
            return false;
2435
        }
2436

    
2437
        return true;
2438
    }
2439

    
2440
    /**
2441
     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2442
     * rand emove double slashes.
2443
     *
2444
     * @param string $p_dir path to reduce
2445
     *
2446
     * @return string reduced path
2447
     */
2448
    private function _pathReduction($p_dir)
2449
    {
2450
        $v_result = '';
2451

    
2452
        // ----- Look for not empty path
2453
        if ($p_dir != '') {
2454
            // ----- Explode path by directory names
2455
            $v_list = explode('/', $p_dir);
2456

    
2457
            // ----- Study directories from last to first
2458
            for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
2459
                // ----- Look for current path
2460
                if ($v_list[$i] == ".") {
2461
                    // ----- Ignore this directory
2462
                    // Should be the first $i=0, but no check is done
2463
                } else {
2464
                    if ($v_list[$i] == "..") {
2465
                        // ----- Ignore it and ignore the $i-1
2466
                        $i--;
2467
                    } else {
2468
                        if (($v_list[$i] == '')
2469
                            && ($i != (sizeof($v_list) - 1))
2470
                            && ($i != 0)
2471
                        ) {
2472
                            // ----- Ignore only the double '//' in path,
2473
                            // but not the first and last /
2474
                        } else {
2475
                            $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2476
                                    . $v_result : '');
2477
                        }
2478
                    }
2479
                }
2480
            }
2481
        }
2482

    
2483
        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2484
            $v_result = strtr($v_result, '\\', '/');
2485
        }
2486

    
2487
        return $v_result;
2488
    }
2489

    
2490
    /**
2491
     * @param $p_path
2492
     * @param bool $p_remove_disk_letter
2493
     * @return string
2494
     */
2495
    public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2496
    {
2497
        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2498
            // ----- Look for potential disk letter
2499
            if (($p_remove_disk_letter)
2500
                && (($v_position = strpos($p_path, ':')) != false)
2501
            ) {
2502
                $p_path = substr($p_path, $v_position + 1);
2503
            }
2504
            // ----- Change potential windows directory separator
2505
            if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2506
                $p_path = strtr($p_path, '\\', '/');
2507
            }
2508
        }
2509
        return $p_path;
2510
    }
2511
}