Mercurial > hg > cmmr2012-drupal-site
comparison core/lib/Drupal/Core/Archiver/ArchiveTar.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | a9cd425dd02b |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 <?php | 1 <?php |
2 // @codingStandardsIgnoreFile | 2 |
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ | 3 namespace Drupal\Core\Archiver; |
4 | 4 |
5 /** | 5 /** |
6 * File::CSV | 6 * Extends Pear's Archive_Tar to use exceptions. |
7 * | |
8 * PHP versions 4 and 5 | |
9 * | |
10 * Copyright (c) 1997-2008, | |
11 * Vincent Blavet <vincent@phpconcept.net> | |
12 * All rights reserved. | |
13 * | |
14 * Redistribution and use in source and binary forms, with or without | |
15 * modification, are permitted provided that the following conditions are met: | |
16 * | |
17 * * Redistributions of source code must retain the above copyright notice, | |
18 * this list of conditions and the following disclaimer. | |
19 * * Redistributions in binary form must reproduce the above copyright | |
20 * notice, this list of conditions and the following disclaimer in the | |
21 * documentation and/or other materials provided with the distribution. | |
22 * | |
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
26 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | |
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
33 * | |
34 * @category File_Formats | |
35 * @package Archive_Tar | |
36 * @author Vincent Blavet <vincent@phpconcept.net> | |
37 * @copyright 1997-2010 The Authors | |
38 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License | |
39 * @version CVS: $Id$ | |
40 * @link http://pear.php.net/package/Archive_Tar | |
41 */ | 7 */ |
8 class ArchiveTar extends \Archive_Tar { | |
42 | 9 |
43 /** | 10 /** |
44 * Note on Drupal 8 porting. | 11 * {@inheritdoc} |
45 * This file origin is Tar.php, release 1.4.5 (stable) with some code | 12 */ |
46 * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net. | 13 public function _error($p_message) { |
47 * To simplify future porting from pear of this file, you should not | 14 throw new \Exception($p_message); |
48 * do cosmetic or other non significant changes to this file. | 15 } |
49 * The following changes have been done: | |
50 * Added namespace Drupal\Core\Archiver. | |
51 * Removed require_once 'PEAR.php'. | |
52 * Added definition of OS_WINDOWS taken from PEAR.php. | |
53 * Renamed class to ArchiveTar. | |
54 * Removed extends PEAR from class. | |
55 * Removed call parent:: __construct(). | |
56 * Changed PEAR::loadExtension($extname) to this->loadExtension($extname). | |
57 * Added function loadExtension() taken from PEAR.php. | |
58 * Changed all calls of unlink() to drupal_unlink(). | |
59 * Changed $this->error_object = &$this->raiseError($p_message) | |
60 * to throw new \Exception($p_message). | |
61 */ | |
62 | 16 |
17 /** | |
18 * {@inheritdoc} | |
19 */ | |
20 public function _warning($p_message) { | |
21 throw new \Exception($p_message); | |
22 } | |
63 | 23 |
64 // Drupal addition. | |
65 namespace { | |
66 | |
67 // Drupal removal require_once 'PEAR.php'. | |
68 | |
69 // Drupal addition OS_WINDOWS as defined in PEAR.php. | |
70 if (substr(PHP_OS, 0, 3) == 'WIN') { | |
71 define('OS_WINDOWS', true); | |
72 } else { | |
73 define('OS_WINDOWS', false); | |
74 } | 24 } |
75 | |
76 define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); | |
77 define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); | |
78 | |
79 if (!function_exists('gzopen') && function_exists('gzopen64')) { | |
80 function gzopen($filename, $mode, $use_include_path = 0) | |
81 { | |
82 return gzopen64($filename, $mode, $use_include_path); | |
83 } | |
84 } | |
85 | |
86 if (!function_exists('gztell') && function_exists('gztell64')) { | |
87 function gztell($zp) | |
88 { | |
89 return gztell64($zp); | |
90 } | |
91 } | |
92 | |
93 if (!function_exists('gzseek') && function_exists('gzseek64')) { | |
94 function gzseek($zp, $offset, $whence = SEEK_SET) | |
95 { | |
96 return gzseek64($zp, $offset, $whence); | |
97 } | |
98 } | |
99 } | |
100 | |
101 // Drupal addition. | |
102 namespace Drupal\Core\Archiver { | |
103 /** | |
104 * Creates a (compressed) Tar archive | |
105 * | |
106 * @package Archive_Tar | |
107 * @author Vincent Blavet <vincent@phpconcept.net> | |
108 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License | |
109 * @version $Revision$ | |
110 */ | |
111 // Drupal change class Archive_Tar extends PEAR. | |
112 class ArchiveTar | |
113 { | |
114 /** | |
115 * @var string Name of the Tar | |
116 */ | |
117 public $_tarname = ''; | |
118 | |
119 /** | |
120 * @var boolean if true, the Tar file will be gzipped | |
121 */ | |
122 public $_compress = false; | |
123 | |
124 /** | |
125 * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2' | |
126 */ | |
127 public $_compress_type = 'none'; | |
128 | |
129 /** | |
130 * @var string Explode separator | |
131 */ | |
132 public $_separator = ' '; | |
133 | |
134 /** | |
135 * @var file descriptor | |
136 */ | |
137 public $_file = 0; | |
138 | |
139 /** | |
140 * @var string Local Tar name of a remote Tar (http:// or ftp://) | |
141 */ | |
142 public $_temp_tarname = ''; | |
143 | |
144 /** | |
145 * @var string regular expression for ignoring files or directories | |
146 */ | |
147 public $_ignore_regexp = ''; | |
148 | |
149 /** | |
150 * @var object PEAR_Error object | |
151 */ | |
152 public $error_object = null; | |
153 | |
154 /** | |
155 * Format for data extraction | |
156 * | |
157 * @var string | |
158 */ | |
159 public $_fmt =''; | |
160 /** | |
161 * Archive_Tar Class constructor. This flavour of the constructor only | |
162 * declare a new Archive_Tar object, identifying it by the name of the | |
163 * tar file. | |
164 * If the compress argument is set the tar will be read or created as a | |
165 * gzip or bz2 compressed TAR file. | |
166 * | |
167 * @param string $p_tarname The name of the tar archive to create | |
168 * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This | |
169 * parameter indicates if gzip, bz2 or lzma2 compression | |
170 * is required. For compatibility reason the | |
171 * boolean value 'true' means 'gz'. | |
172 * | |
173 * @return bool | |
174 */ | |
175 public function __construct($p_tarname, $p_compress = null) | |
176 { | |
177 // Drupal removal parent::__construct(). | |
178 | |
179 $this->_compress = false; | |
180 $this->_compress_type = 'none'; | |
181 if (($p_compress === null) || ($p_compress == '')) { | |
182 if (@file_exists($p_tarname)) { | |
183 if ($fp = @fopen($p_tarname, "rb")) { | |
184 // look for gzip magic cookie | |
185 $data = fread($fp, 2); | |
186 fclose($fp); | |
187 if ($data == "\37\213") { | |
188 $this->_compress = true; | |
189 $this->_compress_type = 'gz'; | |
190 // Not sure it's enough for a magic code .... | |
191 } elseif ($data == "BZ") { | |
192 $this->_compress = true; | |
193 $this->_compress_type = 'bz2'; | |
194 } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') { | |
195 $this->_compress = true; | |
196 $this->_compress_type = 'lzma2'; | |
197 } | |
198 } | |
199 } else { | |
200 // probably a remote file or some file accessible | |
201 // through a stream interface | |
202 if (substr($p_tarname, -2) == 'gz') { | |
203 $this->_compress = true; | |
204 $this->_compress_type = 'gz'; | |
205 } elseif ((substr($p_tarname, -3) == 'bz2') || | |
206 (substr($p_tarname, -2) == 'bz') | |
207 ) { | |
208 $this->_compress = true; | |
209 $this->_compress_type = 'bz2'; | |
210 } else { | |
211 if (substr($p_tarname, -2) == 'xz') { | |
212 $this->_compress = true; | |
213 $this->_compress_type = 'lzma2'; | |
214 } | |
215 } | |
216 } | |
217 } else { | |
218 if (($p_compress === true) || ($p_compress == 'gz')) { | |
219 $this->_compress = true; | |
220 $this->_compress_type = 'gz'; | |
221 } else { | |
222 if ($p_compress == 'bz2') { | |
223 $this->_compress = true; | |
224 $this->_compress_type = 'bz2'; | |
225 } else { | |
226 if ($p_compress == 'lzma2') { | |
227 $this->_compress = true; | |
228 $this->_compress_type = 'lzma2'; | |
229 } else { | |
230 $this->_error( | |
231 "Unsupported compression type '$p_compress'\n" . | |
232 "Supported types are 'gz', 'bz2' and 'lzma2'.\n" | |
233 ); | |
234 return false; | |
235 } | |
236 } | |
237 } | |
238 } | |
239 $this->_tarname = $p_tarname; | |
240 if ($this->_compress) { // assert zlib or bz2 or xz extension support | |
241 if ($this->_compress_type == 'gz') { | |
242 $extname = 'zlib'; | |
243 } else { | |
244 if ($this->_compress_type == 'bz2') { | |
245 $extname = 'bz2'; | |
246 } else { | |
247 if ($this->_compress_type == 'lzma2') { | |
248 $extname = 'xz'; | |
249 } | |
250 } | |
251 } | |
252 | |
253 if (!extension_loaded($extname)) { | |
254 // Drupal change PEAR::loadExtension($extname). | |
255 $this->loadExtension($extname); | |
256 } | |
257 if (!extension_loaded($extname)) { | |
258 $this->_error( | |
259 "The extension '$extname' couldn't be found.\n" . | |
260 "Please make sure your version of PHP was built " . | |
261 "with '$extname' support.\n" | |
262 ); | |
263 return false; | |
264 } | |
265 } | |
266 | |
267 | |
268 if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) { | |
269 $this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . | |
270 "a8checksum/a1typeflag/a100link/a6magic/a2version/" . | |
271 "a32uname/a32gname/a8devmajor/a8devminor/a131prefix"; | |
272 } else { | |
273 $this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" . | |
274 "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" . | |
275 "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix"; | |
276 } | |
277 | |
278 | |
279 } | |
280 | |
281 public function __destruct() | |
282 { | |
283 $this->_close(); | |
284 // ----- Look for a local copy to delete | |
285 if ($this->_temp_tarname != '') { | |
286 @drupal_unlink($this->_temp_tarname); | |
287 } | |
288 } | |
289 | |
290 // Drupal addition from PEAR.php. | |
291 /** | |
292 * OS independent PHP extension load. Remember to take care | |
293 * on the correct extension name for case sensitive OSes. | |
294 * | |
295 * @param string $ext The extension name | |
296 * @return bool Success or not on the dl() call | |
297 */ | |
298 function loadExtension($ext) | |
299 { | |
300 if (extension_loaded($ext)) { | |
301 return true; | |
302 } | |
303 | |
304 // if either returns true dl() will produce a FATAL error, stop that | |
305 if ( | |
306 function_exists('dl') === false || | |
307 ini_get('enable_dl') != 1 || | |
308 ini_get('safe_mode') == 1 | |
309 ) { | |
310 return false; | |
311 } | |
312 | |
313 if (OS_WINDOWS) { | |
314 $suffix = '.dll'; | |
315 } elseif (PHP_OS == 'HP-UX') { | |
316 $suffix = '.sl'; | |
317 } elseif (PHP_OS == 'AIX') { | |
318 $suffix = '.a'; | |
319 } elseif (PHP_OS == 'OSX') { | |
320 $suffix = '.bundle'; | |
321 } else { | |
322 $suffix = '.so'; | |
323 } | |
324 | |
325 return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); | |
326 } | |
327 | |
328 | |
329 /** | |
330 * This method creates the archive file and add the files / directories | |
331 * that are listed in $p_filelist. | |
332 * If a file with the same name exist and is writable, it is replaced | |
333 * by the new tar. | |
334 * The method return false and a PEAR error text. | |
335 * The $p_filelist parameter can be an array of string, each string | |
336 * representing a filename or a directory name with their path if | |
337 * needed. It can also be a single string with names separated by a | |
338 * single blank. | |
339 * For each directory added in the archive, the files and | |
340 * sub-directories are also added. | |
341 * See also createModify() method for more details. | |
342 * | |
343 * @param array $p_filelist An array of filenames and directory names, or a | |
344 * single string with names separated by a single | |
345 * blank space. | |
346 * | |
347 * @return true on success, false on error. | |
348 * @see createModify() | |
349 */ | |
350 public function create($p_filelist) | |
351 { | |
352 return $this->createModify($p_filelist, '', ''); | |
353 } | |
354 | |
355 /** | |
356 * This method add the files / directories that are listed in $p_filelist in | |
357 * the archive. If the archive does not exist it is created. | |
358 * The method return false and a PEAR error text. | |
359 * The files and directories listed are only added at the end of the archive, | |
360 * even if a file with the same name is already archived. | |
361 * See also createModify() method for more details. | |
362 * | |
363 * @param array $p_filelist An array of filenames and directory names, or a | |
364 * single string with names separated by a single | |
365 * blank space. | |
366 * | |
367 * @return true on success, false on error. | |
368 * @see createModify() | |
369 * @access public | |
370 */ | |
371 public function add($p_filelist) | |
372 { | |
373 return $this->addModify($p_filelist, '', ''); | |
374 } | |
375 | |
376 /** | |
377 * @param string $p_path | |
378 * @param bool $p_preserve | |
379 * @return bool | |
380 */ | |
381 public function extract($p_path = '', $p_preserve = false) | |
382 { | |
383 return $this->extractModify($p_path, '', $p_preserve); | |
384 } | |
385 | |
386 /** | |
387 * @return array|int | |
388 */ | |
389 public function listContent() | |
390 { | |
391 $v_list_detail = array(); | |
392 | |
393 if ($this->_openRead()) { | |
394 if (!$this->_extractList('', $v_list_detail, "list", '', '')) { | |
395 unset($v_list_detail); | |
396 $v_list_detail = 0; | |
397 } | |
398 $this->_close(); | |
399 } | |
400 | |
401 return $v_list_detail; | |
402 } | |
403 | |
404 /** | |
405 * This method creates the archive file and add the files / directories | |
406 * that are listed in $p_filelist. | |
407 * If the file already exists and is writable, it is replaced by the | |
408 * new tar. It is a create and not an add. If the file exists and is | |
409 * read-only or is a directory it is not replaced. The method return | |
410 * false and a PEAR error text. | |
411 * The $p_filelist parameter can be an array of string, each string | |
412 * representing a filename or a directory name with their path if | |
413 * needed. It can also be a single string with names separated by a | |
414 * single blank. | |
415 * The path indicated in $p_remove_dir will be removed from the | |
416 * memorized path of each file / directory listed when this path | |
417 * exists. By default nothing is removed (empty path '') | |
418 * The path indicated in $p_add_dir will be added at the beginning of | |
419 * the memorized path of each file / directory listed. However it can | |
420 * be set to empty ''. The adding of a path is done after the removing | |
421 * of path. | |
422 * The path add/remove ability enables the user to prepare an archive | |
423 * for extraction in a different path than the origin files are. | |
424 * See also addModify() method for file adding properties. | |
425 * | |
426 * @param array $p_filelist An array of filenames and directory names, | |
427 * or a single string with names separated by | |
428 * a single blank space. | |
429 * @param string $p_add_dir A string which contains a path to be added | |
430 * to the memorized path of each element in | |
431 * the list. | |
432 * @param string $p_remove_dir A string which contains a path to be | |
433 * removed from the memorized path of each | |
434 * element in the list, when relevant. | |
435 * | |
436 * @return boolean true on success, false on error. | |
437 * @see addModify() | |
438 */ | |
439 public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '') | |
440 { | |
441 $v_result = true; | |
442 | |
443 if (!$this->_openWrite()) { | |
444 return false; | |
445 } | |
446 | |
447 if ($p_filelist != '') { | |
448 if (is_array($p_filelist)) { | |
449 $v_list = $p_filelist; | |
450 } elseif (is_string($p_filelist)) { | |
451 $v_list = explode($this->_separator, $p_filelist); | |
452 } else { | |
453 $this->_cleanFile(); | |
454 $this->_error('Invalid file list'); | |
455 return false; | |
456 } | |
457 | |
458 $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); | |
459 } | |
460 | |
461 if ($v_result) { | |
462 $this->_writeFooter(); | |
463 $this->_close(); | |
464 } else { | |
465 $this->_cleanFile(); | |
466 } | |
467 | |
468 return $v_result; | |
469 } | |
470 | |
471 /** | |
472 * This method add the files / directories listed in $p_filelist at the | |
473 * end of the existing archive. If the archive does not yet exists it | |
474 * is created. | |
475 * The $p_filelist parameter can be an array of string, each string | |
476 * representing a filename or a directory name with their path if | |
477 * needed. It can also be a single string with names separated by a | |
478 * single blank. | |
479 * The path indicated in $p_remove_dir will be removed from the | |
480 * memorized path of each file / directory listed when this path | |
481 * exists. By default nothing is removed (empty path '') | |
482 * The path indicated in $p_add_dir will be added at the beginning of | |
483 * the memorized path of each file / directory listed. However it can | |
484 * be set to empty ''. The adding of a path is done after the removing | |
485 * of path. | |
486 * The path add/remove ability enables the user to prepare an archive | |
487 * for extraction in a different path than the origin files are. | |
488 * If a file/dir is already in the archive it will only be added at the | |
489 * end of the archive. There is no update of the existing archived | |
490 * file/dir. However while extracting the archive, the last file will | |
491 * replace the first one. This results in a none optimization of the | |
492 * archive size. | |
493 * If a file/dir does not exist the file/dir is ignored. However an | |
494 * error text is send to PEAR error. | |
495 * If a file/dir is not readable the file/dir is ignored. However an | |
496 * error text is send to PEAR error. | |
497 * | |
498 * @param array $p_filelist An array of filenames and directory | |
499 * names, or a single string with names | |
500 * separated by a single blank space. | |
501 * @param string $p_add_dir A string which contains a path to be | |
502 * added to the memorized path of each | |
503 * element in the list. | |
504 * @param string $p_remove_dir A string which contains a path to be | |
505 * removed from the memorized path of | |
506 * each element in the list, when | |
507 * relevant. | |
508 * | |
509 * @return true on success, false on error. | |
510 */ | |
511 public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '') | |
512 { | |
513 $v_result = true; | |
514 | |
515 if (!$this->_isArchive()) { | |
516 $v_result = $this->createModify( | |
517 $p_filelist, | |
518 $p_add_dir, | |
519 $p_remove_dir | |
520 ); | |
521 } else { | |
522 if (is_array($p_filelist)) { | |
523 $v_list = $p_filelist; | |
524 } elseif (is_string($p_filelist)) { | |
525 $v_list = explode($this->_separator, $p_filelist); | |
526 } else { | |
527 $this->_error('Invalid file list'); | |
528 return false; | |
529 } | |
530 | |
531 $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); | |
532 } | |
533 | |
534 return $v_result; | |
535 } | |
536 | |
537 /** | |
538 * This method add a single string as a file at the | |
539 * end of the existing archive. If the archive does not yet exists it | |
540 * is created. | |
541 * | |
542 * @param string $p_filename A string which contains the full | |
543 * filename path that will be associated | |
544 * with the string. | |
545 * @param string $p_string The content of the file added in | |
546 * the archive. | |
547 * @param bool|int $p_datetime A custom date/time (unix timestamp) | |
548 * for the file (optional). | |
549 * @param array $p_params An array of optional params: | |
550 * stamp => the datetime (replaces | |
551 * datetime above if it exists) | |
552 * mode => the permissions on the | |
553 * file (600 by default) | |
554 * type => is this a link? See the | |
555 * tar specification for details. | |
556 * (default = regular file) | |
557 * uid => the user ID of the file | |
558 * (default = 0 = root) | |
559 * gid => the group ID of the file | |
560 * (default = 0 = root) | |
561 * | |
562 * @return true on success, false on error. | |
563 */ | |
564 public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) | |
565 { | |
566 $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); | |
567 $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; | |
568 $p_type = @$p_params["type"] ? $p_params["type"] : ""; | |
569 $p_uid = @$p_params["uid"] ? $p_params["uid"] : ""; | |
570 $p_gid = @$p_params["gid"] ? $p_params["gid"] : ""; | |
571 $v_result = true; | |
572 | |
573 if (!$this->_isArchive()) { | |
574 if (!$this->_openWrite()) { | |
575 return false; | |
576 } | |
577 $this->_close(); | |
578 } | |
579 | |
580 if (!$this->_openAppend()) { | |
581 return false; | |
582 } | |
583 | |
584 // Need to check the get back to the temporary file ? .... | |
585 $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params); | |
586 | |
587 $this->_writeFooter(); | |
588 | |
589 $this->_close(); | |
590 | |
591 return $v_result; | |
592 } | |
593 | |
594 /** | |
595 * This method extract all the content of the archive in the directory | |
596 * indicated by $p_path. When relevant the memorized path of the | |
597 * files/dir can be modified by removing the $p_remove_path path at the | |
598 * beginning of the file/dir path. | |
599 * While extracting a file, if the directory path does not exist it is | |
600 * created. | |
601 * While extracting a file, if the file already exists it is replaced | |
602 * without looking for last modification date. | |
603 * While extracting a file, if the file already exists and is write | |
604 * protected, the extraction is aborted. | |
605 * While extracting a file, if a directory with the same name already | |
606 * exists, the extraction is aborted. | |
607 * While extracting a directory, if a file with the same name already | |
608 * exists, the extraction is aborted. | |
609 * While extracting a file/directory if the destination directory exist | |
610 * and is write protected, or does not exist but can not be created, | |
611 * the extraction is aborted. | |
612 * If after extraction an extracted file does not show the correct | |
613 * stored file size, the extraction is aborted. | |
614 * When the extraction is aborted, a PEAR error text is set and false | |
615 * is returned. However the result can be a partial extraction that may | |
616 * need to be manually cleaned. | |
617 * | |
618 * @param string $p_path The path of the directory where the | |
619 * files/dir need to by extracted. | |
620 * @param string $p_remove_path Part of the memorized path that can be | |
621 * removed if present at the beginning of | |
622 * the file/dir path. | |
623 * @param boolean $p_preserve Preserve user/group ownership of files | |
624 * | |
625 * @return boolean true on success, false on error. | |
626 * @see extractList() | |
627 */ | |
628 public function extractModify($p_path, $p_remove_path, $p_preserve = false) | |
629 { | |
630 $v_result = true; | |
631 $v_list_detail = array(); | |
632 | |
633 if ($v_result = $this->_openRead()) { | |
634 $v_result = $this->_extractList( | |
635 $p_path, | |
636 $v_list_detail, | |
637 "complete", | |
638 0, | |
639 $p_remove_path, | |
640 $p_preserve | |
641 ); | |
642 $this->_close(); | |
643 } | |
644 | |
645 return $v_result; | |
646 } | |
647 | |
648 /** | |
649 * This method extract from the archive one file identified by $p_filename. | |
650 * The return value is a string with the file content, or NULL on error. | |
651 * | |
652 * @param string $p_filename The path of the file to extract in a string. | |
653 * | |
654 * @return a string with the file content or NULL. | |
655 */ | |
656 public function extractInString($p_filename) | |
657 { | |
658 if ($this->_openRead()) { | |
659 $v_result = $this->_extractInString($p_filename); | |
660 $this->_close(); | |
661 } else { | |
662 $v_result = null; | |
663 } | |
664 | |
665 return $v_result; | |
666 } | |
667 | |
668 /** | |
669 * This method extract from the archive only the files indicated in the | |
670 * $p_filelist. These files are extracted in the current directory or | |
671 * in the directory indicated by the optional $p_path parameter. | |
672 * If indicated the $p_remove_path can be used in the same way as it is | |
673 * used in extractModify() method. | |
674 * | |
675 * @param array $p_filelist An array of filenames and directory names, | |
676 * or a single string with names separated | |
677 * by a single blank space. | |
678 * @param string $p_path The path of the directory where the | |
679 * files/dir need to by extracted. | |
680 * @param string $p_remove_path Part of the memorized path that can be | |
681 * removed if present at the beginning of | |
682 * the file/dir path. | |
683 * @param boolean $p_preserve Preserve user/group ownership of files | |
684 * | |
685 * @return true on success, false on error. | |
686 * @see extractModify() | |
687 */ | |
688 public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false) | |
689 { | |
690 $v_result = true; | |
691 $v_list_detail = array(); | |
692 | |
693 if (is_array($p_filelist)) { | |
694 $v_list = $p_filelist; | |
695 } elseif (is_string($p_filelist)) { | |
696 $v_list = explode($this->_separator, $p_filelist); | |
697 } else { | |
698 $this->_error('Invalid string list'); | |
699 return false; | |
700 } | |
701 | |
702 if ($v_result = $this->_openRead()) { | |
703 $v_result = $this->_extractList( | |
704 $p_path, | |
705 $v_list_detail, | |
706 "partial", | |
707 $v_list, | |
708 $p_remove_path, | |
709 $p_preserve | |
710 ); | |
711 $this->_close(); | |
712 } | |
713 | |
714 return $v_result; | |
715 } | |
716 | |
717 /** | |
718 * This method set specific attributes of the archive. It uses a variable | |
719 * list of parameters, in the format attribute code + attribute values : | |
720 * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); | |
721 * | |
722 * @return true on success, false on error. | |
723 */ | |
724 public function setAttribute() | |
725 { | |
726 $v_result = true; | |
727 | |
728 // ----- Get the number of variable list of arguments | |
729 if (($v_size = func_num_args()) == 0) { | |
730 return true; | |
731 } | |
732 | |
733 // ----- Get the arguments | |
734 $v_att_list = func_get_args(); | |
735 | |
736 // ----- Read the attributes | |
737 $i = 0; | |
738 while ($i < $v_size) { | |
739 | |
740 // ----- Look for next option | |
741 switch ($v_att_list[$i]) { | |
742 // ----- Look for options that request a string value | |
743 case ARCHIVE_TAR_ATT_SEPARATOR : | |
744 // ----- Check the number of parameters | |
745 if (($i + 1) >= $v_size) { | |
746 $this->_error( | |
747 'Invalid number of parameters for ' | |
748 . 'attribute ARCHIVE_TAR_ATT_SEPARATOR' | |
749 ); | |
750 return false; | |
751 } | |
752 | |
753 // ----- Get the value | |
754 $this->_separator = $v_att_list[$i + 1]; | |
755 $i++; | |
756 break; | |
757 | |
758 default : | |
759 $this->_error('Unknown attribute code ' . $v_att_list[$i] . ''); | |
760 return false; | |
761 } | |
762 | |
763 // ----- Next attribute | |
764 $i++; | |
765 } | |
766 | |
767 return $v_result; | |
768 } | |
769 | |
770 /** | |
771 * This method sets the regular expression for ignoring files and directories | |
772 * at import, for example: | |
773 * $arch->setIgnoreRegexp("#CVS|\.svn#"); | |
774 * | |
775 * @param string $regexp regular expression defining which files or directories to ignore | |
776 */ | |
777 public function setIgnoreRegexp($regexp) | |
778 { | |
779 $this->_ignore_regexp = $regexp; | |
780 } | |
781 | |
782 /** | |
783 * This method sets the regular expression for ignoring all files and directories | |
784 * matching the filenames in the array list at import, for example: | |
785 * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); | |
786 * | |
787 * @param array $list a list of file or directory names to ignore | |
788 * | |
789 * @access public | |
790 */ | |
791 public function setIgnoreList($list) | |
792 { | |
793 $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); | |
794 $regexp = '#/' . join('$|/', $list) . '#'; | |
795 $this->setIgnoreRegexp($regexp); | |
796 } | |
797 | |
798 /** | |
799 * @param string $p_message | |
800 */ | |
801 public function _error($p_message) | |
802 { | |
803 // Drupal change $this->error_object = $this->raiseError($p_message). | |
804 throw new \Exception($p_message); | |
805 } | |
806 | |
807 /** | |
808 * @param string $p_message | |
809 */ | |
810 public function _warning($p_message) | |
811 { | |
812 // Drupal change $this->error_object = $this->raiseError($p_message). | |
813 throw new \Exception($p_message); | |
814 } | |
815 | |
816 /** | |
817 * @param string $p_filename | |
818 * @return bool | |
819 */ | |
820 public function _isArchive($p_filename = null) | |
821 { | |
822 if ($p_filename == null) { | |
823 $p_filename = $this->_tarname; | |
824 } | |
825 clearstatcache(); | |
826 return @is_file($p_filename) && !@is_link($p_filename); | |
827 } | |
828 | |
829 /** | |
830 * @return bool | |
831 */ | |
832 public function _openWrite() | |
833 { | |
834 if ($this->_compress_type == 'gz' && function_exists('gzopen')) { | |
835 $this->_file = @gzopen($this->_tarname, "wb9"); | |
836 } else { | |
837 if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { | |
838 $this->_file = @bzopen($this->_tarname, "w"); | |
839 } else { | |
840 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { | |
841 $this->_file = @xzopen($this->_tarname, 'w'); | |
842 } else { | |
843 if ($this->_compress_type == 'none') { | |
844 $this->_file = @fopen($this->_tarname, "wb"); | |
845 } else { | |
846 $this->_error( | |
847 'Unknown or missing compression type (' | |
848 . $this->_compress_type . ')' | |
849 ); | |
850 return false; | |
851 } | |
852 } | |
853 } | |
854 } | |
855 | |
856 if ($this->_file == 0) { | |
857 $this->_error( | |
858 'Unable to open in write mode \'' | |
859 . $this->_tarname . '\'' | |
860 ); | |
861 return false; | |
862 } | |
863 | |
864 return true; | |
865 } | |
866 | |
867 /** | |
868 * @return bool | |
869 */ | |
870 public function _openRead() | |
871 { | |
872 if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { | |
873 | |
874 // ----- Look if a local copy need to be done | |
875 if ($this->_temp_tarname == '') { | |
876 $this->_temp_tarname = uniqid('tar') . '.tmp'; | |
877 if (!$v_file_from = @fopen($this->_tarname, 'rb')) { | |
878 $this->_error( | |
879 'Unable to open in read mode \'' | |
880 . $this->_tarname . '\'' | |
881 ); | |
882 $this->_temp_tarname = ''; | |
883 return false; | |
884 } | |
885 if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { | |
886 $this->_error( | |
887 'Unable to open in write mode \'' | |
888 . $this->_temp_tarname . '\'' | |
889 ); | |
890 $this->_temp_tarname = ''; | |
891 return false; | |
892 } | |
893 while ($v_data = @fread($v_file_from, 1024)) { | |
894 @fwrite($v_file_to, $v_data); | |
895 } | |
896 @fclose($v_file_from); | |
897 @fclose($v_file_to); | |
898 } | |
899 | |
900 // ----- File to open if the local copy | |
901 $v_filename = $this->_temp_tarname; | |
902 } else { | |
903 // ----- File to open if the normal Tar file | |
904 | |
905 $v_filename = $this->_tarname; | |
906 } | |
907 | |
908 if ($this->_compress_type == 'gz' && function_exists('gzopen')) { | |
909 $this->_file = @gzopen($v_filename, "rb"); | |
910 } else { | |
911 if ($this->_compress_type == 'bz2' && function_exists('bzopen')) { | |
912 $this->_file = @bzopen($v_filename, "r"); | |
913 } else { | |
914 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) { | |
915 $this->_file = @xzopen($v_filename, "r"); | |
916 } else { | |
917 if ($this->_compress_type == 'none') { | |
918 $this->_file = @fopen($v_filename, "rb"); | |
919 } else { | |
920 $this->_error( | |
921 'Unknown or missing compression type (' | |
922 . $this->_compress_type . ')' | |
923 ); | |
924 return false; | |
925 } | |
926 } | |
927 } | |
928 } | |
929 | |
930 if ($this->_file == 0) { | |
931 $this->_error('Unable to open in read mode \'' . $v_filename . '\''); | |
932 return false; | |
933 } | |
934 | |
935 return true; | |
936 } | |
937 | |
938 /** | |
939 * @return bool | |
940 */ | |
941 public function _openReadWrite() | |
942 { | |
943 if ($this->_compress_type == 'gz') { | |
944 $this->_file = @gzopen($this->_tarname, "r+b"); | |
945 } else { | |
946 if ($this->_compress_type == 'bz2') { | |
947 $this->_error( | |
948 'Unable to open bz2 in read/write mode \'' | |
949 . $this->_tarname . '\' (limitation of bz2 extension)' | |
950 ); | |
951 return false; | |
952 } else { | |
953 if ($this->_compress_type == 'lzma2') { | |
954 $this->_error( | |
955 'Unable to open lzma2 in read/write mode \'' | |
956 . $this->_tarname . '\' (limitation of lzma2 extension)' | |
957 ); | |
958 return false; | |
959 } else { | |
960 if ($this->_compress_type == 'none') { | |
961 $this->_file = @fopen($this->_tarname, "r+b"); | |
962 } else { | |
963 $this->_error( | |
964 'Unknown or missing compression type (' | |
965 . $this->_compress_type . ')' | |
966 ); | |
967 return false; | |
968 } | |
969 } | |
970 } | |
971 } | |
972 | |
973 if ($this->_file == 0) { | |
974 $this->_error( | |
975 'Unable to open in read/write mode \'' | |
976 . $this->_tarname . '\'' | |
977 ); | |
978 return false; | |
979 } | |
980 | |
981 return true; | |
982 } | |
983 | |
984 /** | |
985 * @return bool | |
986 */ | |
987 public function _close() | |
988 { | |
989 //if (isset($this->_file)) { | |
990 if (is_resource($this->_file)) { | |
991 if ($this->_compress_type == 'gz') { | |
992 @gzclose($this->_file); | |
993 } else { | |
994 if ($this->_compress_type == 'bz2') { | |
995 @bzclose($this->_file); | |
996 } else { | |
997 if ($this->_compress_type == 'lzma2') { | |
998 @xzclose($this->_file); | |
999 } else { | |
1000 if ($this->_compress_type == 'none') { | |
1001 @fclose($this->_file); | |
1002 } else { | |
1003 $this->_error( | |
1004 'Unknown or missing compression type (' | |
1005 . $this->_compress_type . ')' | |
1006 ); | |
1007 } | |
1008 } | |
1009 } | |
1010 } | |
1011 | |
1012 $this->_file = 0; | |
1013 } | |
1014 | |
1015 // ----- Look if a local copy need to be erase | |
1016 // Note that it might be interesting to keep the url for a time : ToDo | |
1017 if ($this->_temp_tarname != '') { | |
1018 @drupal_unlink($this->_temp_tarname); | |
1019 $this->_temp_tarname = ''; | |
1020 } | |
1021 | |
1022 return true; | |
1023 } | |
1024 | |
1025 /** | |
1026 * @return bool | |
1027 */ | |
1028 public function _cleanFile() | |
1029 { | |
1030 $this->_close(); | |
1031 | |
1032 // ----- Look for a local copy | |
1033 if ($this->_temp_tarname != '') { | |
1034 // ----- Remove the local copy but not the remote tarname | |
1035 @drupal_unlink($this->_temp_tarname); | |
1036 $this->_temp_tarname = ''; | |
1037 } else { | |
1038 // ----- Remove the local tarname file | |
1039 @drupal_unlink($this->_tarname); | |
1040 } | |
1041 $this->_tarname = ''; | |
1042 | |
1043 return true; | |
1044 } | |
1045 | |
1046 /** | |
1047 * @param mixed $p_binary_data | |
1048 * @param integer $p_len | |
1049 * @return bool | |
1050 */ | |
1051 public function _writeBlock($p_binary_data, $p_len = null) | |
1052 { | |
1053 if (is_resource($this->_file)) { | |
1054 if ($p_len === null) { | |
1055 if ($this->_compress_type == 'gz') { | |
1056 @gzputs($this->_file, $p_binary_data); | |
1057 } else { | |
1058 if ($this->_compress_type == 'bz2') { | |
1059 @bzwrite($this->_file, $p_binary_data); | |
1060 } else { | |
1061 if ($this->_compress_type == 'lzma2') { | |
1062 @xzwrite($this->_file, $p_binary_data); | |
1063 } else { | |
1064 if ($this->_compress_type == 'none') { | |
1065 @fputs($this->_file, $p_binary_data); | |
1066 } else { | |
1067 $this->_error( | |
1068 'Unknown or missing compression type (' | |
1069 . $this->_compress_type . ')' | |
1070 ); | |
1071 } | |
1072 } | |
1073 } | |
1074 } | |
1075 } else { | |
1076 if ($this->_compress_type == 'gz') { | |
1077 @gzputs($this->_file, $p_binary_data, $p_len); | |
1078 } else { | |
1079 if ($this->_compress_type == 'bz2') { | |
1080 @bzwrite($this->_file, $p_binary_data, $p_len); | |
1081 } else { | |
1082 if ($this->_compress_type == 'lzma2') { | |
1083 @xzwrite($this->_file, $p_binary_data, $p_len); | |
1084 } else { | |
1085 if ($this->_compress_type == 'none') { | |
1086 @fputs($this->_file, $p_binary_data, $p_len); | |
1087 } else { | |
1088 $this->_error( | |
1089 'Unknown or missing compression type (' | |
1090 . $this->_compress_type . ')' | |
1091 ); | |
1092 } | |
1093 } | |
1094 } | |
1095 } | |
1096 } | |
1097 } | |
1098 return true; | |
1099 } | |
1100 | |
1101 /** | |
1102 * @return null|string | |
1103 */ | |
1104 public function _readBlock() | |
1105 { | |
1106 $v_block = null; | |
1107 if (is_resource($this->_file)) { | |
1108 if ($this->_compress_type == 'gz') { | |
1109 $v_block = @gzread($this->_file, 512); | |
1110 } else { | |
1111 if ($this->_compress_type == 'bz2') { | |
1112 $v_block = @bzread($this->_file, 512); | |
1113 } else { | |
1114 if ($this->_compress_type == 'lzma2') { | |
1115 $v_block = @xzread($this->_file, 512); | |
1116 } else { | |
1117 if ($this->_compress_type == 'none') { | |
1118 $v_block = @fread($this->_file, 512); | |
1119 } else { | |
1120 $this->_error( | |
1121 'Unknown or missing compression type (' | |
1122 . $this->_compress_type . ')' | |
1123 ); | |
1124 } | |
1125 } | |
1126 } | |
1127 } | |
1128 } | |
1129 return $v_block; | |
1130 } | |
1131 | |
1132 /** | |
1133 * @param null $p_len | |
1134 * @return bool | |
1135 */ | |
1136 public function _jumpBlock($p_len = null) | |
1137 { | |
1138 if (is_resource($this->_file)) { | |
1139 if ($p_len === null) { | |
1140 $p_len = 1; | |
1141 } | |
1142 | |
1143 if ($this->_compress_type == 'gz') { | |
1144 @gzseek($this->_file, gztell($this->_file) + ($p_len * 512)); | |
1145 } else { | |
1146 if ($this->_compress_type == 'bz2') { | |
1147 // ----- Replace missing bztell() and bzseek() | |
1148 for ($i = 0; $i < $p_len; $i++) { | |
1149 $this->_readBlock(); | |
1150 } | |
1151 } else { | |
1152 if ($this->_compress_type == 'lzma2') { | |
1153 // ----- Replace missing xztell() and xzseek() | |
1154 for ($i = 0; $i < $p_len; $i++) { | |
1155 $this->_readBlock(); | |
1156 } | |
1157 } else { | |
1158 if ($this->_compress_type == 'none') { | |
1159 @fseek($this->_file, $p_len * 512, SEEK_CUR); | |
1160 } else { | |
1161 $this->_error( | |
1162 'Unknown or missing compression type (' | |
1163 . $this->_compress_type . ')' | |
1164 ); | |
1165 } | |
1166 } | |
1167 } | |
1168 } | |
1169 } | |
1170 return true; | |
1171 } | |
1172 | |
1173 /** | |
1174 * @return bool | |
1175 */ | |
1176 public function _writeFooter() | |
1177 { | |
1178 if (is_resource($this->_file)) { | |
1179 // ----- Write the last 0 filled block for end of archive | |
1180 $v_binary_data = pack('a1024', ''); | |
1181 $this->_writeBlock($v_binary_data); | |
1182 } | |
1183 return true; | |
1184 } | |
1185 | |
1186 /** | |
1187 * @param array $p_list | |
1188 * @param string $p_add_dir | |
1189 * @param string $p_remove_dir | |
1190 * @return bool | |
1191 */ | |
1192 public function _addList($p_list, $p_add_dir, $p_remove_dir) | |
1193 { | |
1194 $v_result = true; | |
1195 $v_header = array(); | |
1196 | |
1197 // ----- Remove potential windows directory separator | |
1198 $p_add_dir = $this->_translateWinPath($p_add_dir); | |
1199 $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); | |
1200 | |
1201 if (!$this->_file) { | |
1202 $this->_error('Invalid file descriptor'); | |
1203 return false; | |
1204 } | |
1205 | |
1206 if (sizeof($p_list) == 0) { | |
1207 return true; | |
1208 } | |
1209 | |
1210 foreach ($p_list as $v_filename) { | |
1211 if (!$v_result) { | |
1212 break; | |
1213 } | |
1214 | |
1215 // ----- Skip the current tar name | |
1216 if ($v_filename == $this->_tarname) { | |
1217 continue; | |
1218 } | |
1219 | |
1220 if ($v_filename == '') { | |
1221 continue; | |
1222 } | |
1223 | |
1224 // ----- ignore files and directories matching the ignore regular expression | |
1225 if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) { | |
1226 $this->_warning("File '$v_filename' ignored"); | |
1227 continue; | |
1228 } | |
1229 | |
1230 if (!file_exists($v_filename) && !is_link($v_filename)) { | |
1231 $this->_warning("File '$v_filename' does not exist"); | |
1232 continue; | |
1233 } | |
1234 | |
1235 // ----- Add the file or directory header | |
1236 if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) { | |
1237 return false; | |
1238 } | |
1239 | |
1240 if (@is_dir($v_filename) && !@is_link($v_filename)) { | |
1241 if (!($p_hdir = opendir($v_filename))) { | |
1242 $this->_warning("Directory '$v_filename' can not be read"); | |
1243 continue; | |
1244 } | |
1245 while (false !== ($p_hitem = readdir($p_hdir))) { | |
1246 if (($p_hitem != '.') && ($p_hitem != '..')) { | |
1247 if ($v_filename != ".") { | |
1248 $p_temp_list[0] = $v_filename . '/' . $p_hitem; | |
1249 } else { | |
1250 $p_temp_list[0] = $p_hitem; | |
1251 } | |
1252 | |
1253 $v_result = $this->_addList( | |
1254 $p_temp_list, | |
1255 $p_add_dir, | |
1256 $p_remove_dir | |
1257 ); | |
1258 } | |
1259 } | |
1260 | |
1261 unset($p_temp_list); | |
1262 unset($p_hdir); | |
1263 unset($p_hitem); | |
1264 } | |
1265 } | |
1266 | |
1267 return $v_result; | |
1268 } | |
1269 | |
1270 /** | |
1271 * @param string $p_filename | |
1272 * @param mixed $p_header | |
1273 * @param string $p_add_dir | |
1274 * @param string $p_remove_dir | |
1275 * @param null $v_stored_filename | |
1276 * @return bool | |
1277 */ | |
1278 public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null) | |
1279 { | |
1280 if (!$this->_file) { | |
1281 $this->_error('Invalid file descriptor'); | |
1282 return false; | |
1283 } | |
1284 | |
1285 if ($p_filename == '') { | |
1286 $this->_error('Invalid file name'); | |
1287 return false; | |
1288 } | |
1289 | |
1290 if (is_null($v_stored_filename)) { | |
1291 // ----- Calculate the stored filename | |
1292 $p_filename = $this->_translateWinPath($p_filename, false); | |
1293 $v_stored_filename = $p_filename; | |
1294 | |
1295 if (strcmp($p_filename, $p_remove_dir) == 0) { | |
1296 return true; | |
1297 } | |
1298 | |
1299 if ($p_remove_dir != '') { | |
1300 if (substr($p_remove_dir, -1) != '/') { | |
1301 $p_remove_dir .= '/'; | |
1302 } | |
1303 | |
1304 if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) { | |
1305 $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); | |
1306 } | |
1307 } | |
1308 | |
1309 $v_stored_filename = $this->_translateWinPath($v_stored_filename); | |
1310 if ($p_add_dir != '') { | |
1311 if (substr($p_add_dir, -1) == '/') { | |
1312 $v_stored_filename = $p_add_dir . $v_stored_filename; | |
1313 } else { | |
1314 $v_stored_filename = $p_add_dir . '/' . $v_stored_filename; | |
1315 } | |
1316 } | |
1317 | |
1318 $v_stored_filename = $this->_pathReduction($v_stored_filename); | |
1319 } | |
1320 | |
1321 if ($this->_isArchive($p_filename)) { | |
1322 if (($v_file = @fopen($p_filename, "rb")) == 0) { | |
1323 $this->_warning( | |
1324 "Unable to open file '" . $p_filename | |
1325 . "' in binary read mode" | |
1326 ); | |
1327 return true; | |
1328 } | |
1329 | |
1330 if (!$this->_writeHeader($p_filename, $v_stored_filename)) { | |
1331 return false; | |
1332 } | |
1333 | |
1334 while (($v_buffer = fread($v_file, 512)) != '') { | |
1335 $v_binary_data = pack("a512", "$v_buffer"); | |
1336 $this->_writeBlock($v_binary_data); | |
1337 } | |
1338 | |
1339 fclose($v_file); | |
1340 } else { | |
1341 // ----- Only header for dir | |
1342 if (!$this->_writeHeader($p_filename, $v_stored_filename)) { | |
1343 return false; | |
1344 } | |
1345 } | |
1346 | |
1347 return true; | |
1348 } | |
1349 | |
1350 /** | |
1351 * @param string $p_filename | |
1352 * @param string $p_string | |
1353 * @param bool $p_datetime | |
1354 * @param array $p_params | |
1355 * @return bool | |
1356 */ | |
1357 public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array()) | |
1358 { | |
1359 $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time()); | |
1360 $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600; | |
1361 $p_type = @$p_params["type"] ? $p_params["type"] : ""; | |
1362 $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0; | |
1363 $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0; | |
1364 if (!$this->_file) { | |
1365 $this->_error('Invalid file descriptor'); | |
1366 return false; | |
1367 } | |
1368 | |
1369 if ($p_filename == '') { | |
1370 $this->_error('Invalid file name'); | |
1371 return false; | |
1372 } | |
1373 | |
1374 // ----- Calculate the stored filename | |
1375 $p_filename = $this->_translateWinPath($p_filename, false); | |
1376 | |
1377 // ----- If datetime is not specified, set current time | |
1378 if ($p_datetime === false) { | |
1379 $p_datetime = time(); | |
1380 } | |
1381 | |
1382 if (!$this->_writeHeaderBlock( | |
1383 $p_filename, | |
1384 strlen($p_string), | |
1385 $p_stamp, | |
1386 $p_mode, | |
1387 $p_type, | |
1388 $p_uid, | |
1389 $p_gid | |
1390 ) | |
1391 ) { | |
1392 return false; | |
1393 } | |
1394 | |
1395 $i = 0; | |
1396 while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') { | |
1397 $v_binary_data = pack("a512", $v_buffer); | |
1398 $this->_writeBlock($v_binary_data); | |
1399 } | |
1400 | |
1401 return true; | |
1402 } | |
1403 | |
1404 /** | |
1405 * @param string $p_filename | |
1406 * @param string $p_stored_filename | |
1407 * @return bool | |
1408 */ | |
1409 public function _writeHeader($p_filename, $p_stored_filename) | |
1410 { | |
1411 if ($p_stored_filename == '') { | |
1412 $p_stored_filename = $p_filename; | |
1413 } | |
1414 | |
1415 $v_reduced_filename = $this->_pathReduction($p_stored_filename); | |
1416 | |
1417 if (strlen($v_reduced_filename) > 99) { | |
1418 if (!$this->_writeLongHeader($v_reduced_filename, false)) { | |
1419 return false; | |
1420 } | |
1421 } | |
1422 | |
1423 $v_linkname = ''; | |
1424 if (@is_link($p_filename)) { | |
1425 $v_linkname = readlink($p_filename); | |
1426 } | |
1427 | |
1428 if (strlen($v_linkname) > 99) { | |
1429 if (!$this->_writeLongHeader($v_linkname, true)) { | |
1430 return false; | |
1431 } | |
1432 } | |
1433 | |
1434 $v_info = lstat($p_filename); | |
1435 $v_uid = sprintf("%07s", DecOct($v_info[4])); | |
1436 $v_gid = sprintf("%07s", DecOct($v_info[5])); | |
1437 $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); | |
1438 $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); | |
1439 | |
1440 if (@is_link($p_filename)) { | |
1441 $v_typeflag = '2'; | |
1442 $v_size = sprintf("%011s", DecOct(0)); | |
1443 } elseif (@is_dir($p_filename)) { | |
1444 $v_typeflag = "5"; | |
1445 $v_size = sprintf("%011s", DecOct(0)); | |
1446 } else { | |
1447 $v_typeflag = '0'; | |
1448 clearstatcache(); | |
1449 $v_size = sprintf("%011s", DecOct($v_info['size'])); | |
1450 } | |
1451 | |
1452 $v_magic = 'ustar '; | |
1453 $v_version = ' '; | |
1454 | |
1455 if (function_exists('posix_getpwuid')) { | |
1456 $userinfo = posix_getpwuid($v_info[4]); | |
1457 $groupinfo = posix_getgrgid($v_info[5]); | |
1458 | |
1459 $v_uname = $userinfo['name']; | |
1460 $v_gname = $groupinfo['name']; | |
1461 } else { | |
1462 $v_uname = ''; | |
1463 $v_gname = ''; | |
1464 } | |
1465 | |
1466 $v_devmajor = ''; | |
1467 $v_devminor = ''; | |
1468 $v_prefix = ''; | |
1469 | |
1470 $v_binary_data_first = pack( | |
1471 "a100a8a8a8a12a12", | |
1472 $v_reduced_filename, | |
1473 $v_perms, | |
1474 $v_uid, | |
1475 $v_gid, | |
1476 $v_size, | |
1477 $v_mtime | |
1478 ); | |
1479 $v_binary_data_last = pack( | |
1480 "a1a100a6a2a32a32a8a8a155a12", | |
1481 $v_typeflag, | |
1482 $v_linkname, | |
1483 $v_magic, | |
1484 $v_version, | |
1485 $v_uname, | |
1486 $v_gname, | |
1487 $v_devmajor, | |
1488 $v_devminor, | |
1489 $v_prefix, | |
1490 '' | |
1491 ); | |
1492 | |
1493 // ----- Calculate the checksum | |
1494 $v_checksum = 0; | |
1495 // ..... First part of the header | |
1496 for ($i = 0; $i < 148; $i++) { | |
1497 $v_checksum += ord(substr($v_binary_data_first, $i, 1)); | |
1498 } | |
1499 // ..... Ignore the checksum value and replace it by ' ' (space) | |
1500 for ($i = 148; $i < 156; $i++) { | |
1501 $v_checksum += ord(' '); | |
1502 } | |
1503 // ..... Last part of the header | |
1504 for ($i = 156, $j = 0; $i < 512; $i++, $j++) { | |
1505 $v_checksum += ord(substr($v_binary_data_last, $j, 1)); | |
1506 } | |
1507 | |
1508 // ----- Write the first 148 bytes of the header in the archive | |
1509 $this->_writeBlock($v_binary_data_first, 148); | |
1510 | |
1511 // ----- Write the calculated checksum | |
1512 $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum)); | |
1513 $v_binary_data = pack("a8", $v_checksum); | |
1514 $this->_writeBlock($v_binary_data, 8); | |
1515 | |
1516 // ----- Write the last 356 bytes of the header in the archive | |
1517 $this->_writeBlock($v_binary_data_last, 356); | |
1518 | |
1519 return true; | |
1520 } | |
1521 | |
1522 /** | |
1523 * @param string $p_filename | |
1524 * @param int $p_size | |
1525 * @param int $p_mtime | |
1526 * @param int $p_perms | |
1527 * @param string $p_type | |
1528 * @param int $p_uid | |
1529 * @param int $p_gid | |
1530 * @return bool | |
1531 */ | |
1532 public function _writeHeaderBlock( | |
1533 $p_filename, | |
1534 $p_size, | |
1535 $p_mtime = 0, | |
1536 $p_perms = 0, | |
1537 $p_type = '', | |
1538 $p_uid = 0, | |
1539 $p_gid = 0 | |
1540 ) { | |
1541 $p_filename = $this->_pathReduction($p_filename); | |
1542 | |
1543 if (strlen($p_filename) > 99) { | |
1544 if (!$this->_writeLongHeader($p_filename, false)) { | |
1545 return false; | |
1546 } | |
1547 } | |
1548 | |
1549 if ($p_type == "5") { | |
1550 $v_size = sprintf("%011s", DecOct(0)); | |
1551 } else { | |
1552 $v_size = sprintf("%011s", DecOct($p_size)); | |
1553 } | |
1554 | |
1555 $v_uid = sprintf("%07s", DecOct($p_uid)); | |
1556 $v_gid = sprintf("%07s", DecOct($p_gid)); | |
1557 $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); | |
1558 | |
1559 $v_mtime = sprintf("%11s", DecOct($p_mtime)); | |
1560 | |
1561 $v_linkname = ''; | |
1562 | |
1563 $v_magic = 'ustar '; | |
1564 | |
1565 $v_version = ' '; | |
1566 | |
1567 if (function_exists('posix_getpwuid')) { | |
1568 $userinfo = posix_getpwuid($p_uid); | |
1569 $groupinfo = posix_getgrgid($p_gid); | |
1570 | |
1571 $v_uname = $userinfo['name']; | |
1572 $v_gname = $groupinfo['name']; | |
1573 } else { | |
1574 $v_uname = ''; | |
1575 $v_gname = ''; | |
1576 } | |
1577 | |
1578 $v_devmajor = ''; | |
1579 | |
1580 $v_devminor = ''; | |
1581 | |
1582 $v_prefix = ''; | |
1583 | |
1584 $v_binary_data_first = pack( | |
1585 "a100a8a8a8a12A12", | |
1586 $p_filename, | |
1587 $v_perms, | |
1588 $v_uid, | |
1589 $v_gid, | |
1590 $v_size, | |
1591 $v_mtime | |
1592 ); | |
1593 $v_binary_data_last = pack( | |
1594 "a1a100a6a2a32a32a8a8a155a12", | |
1595 $p_type, | |
1596 $v_linkname, | |
1597 $v_magic, | |
1598 $v_version, | |
1599 $v_uname, | |
1600 $v_gname, | |
1601 $v_devmajor, | |
1602 $v_devminor, | |
1603 $v_prefix, | |
1604 '' | |
1605 ); | |
1606 | |
1607 // ----- Calculate the checksum | |
1608 $v_checksum = 0; | |
1609 // ..... First part of the header | |
1610 for ($i = 0; $i < 148; $i++) { | |
1611 $v_checksum += ord(substr($v_binary_data_first, $i, 1)); | |
1612 } | |
1613 // ..... Ignore the checksum value and replace it by ' ' (space) | |
1614 for ($i = 148; $i < 156; $i++) { | |
1615 $v_checksum += ord(' '); | |
1616 } | |
1617 // ..... Last part of the header | |
1618 for ($i = 156, $j = 0; $i < 512; $i++, $j++) { | |
1619 $v_checksum += ord(substr($v_binary_data_last, $j, 1)); | |
1620 } | |
1621 | |
1622 // ----- Write the first 148 bytes of the header in the archive | |
1623 $this->_writeBlock($v_binary_data_first, 148); | |
1624 | |
1625 // ----- Write the calculated checksum | |
1626 $v_checksum = sprintf("%06s ", DecOct($v_checksum)); | |
1627 $v_binary_data = pack("a8", $v_checksum); | |
1628 $this->_writeBlock($v_binary_data, 8); | |
1629 | |
1630 // ----- Write the last 356 bytes of the header in the archive | |
1631 $this->_writeBlock($v_binary_data_last, 356); | |
1632 | |
1633 return true; | |
1634 } | |
1635 | |
1636 /** | |
1637 * @param string $p_filename | |
1638 * @return bool | |
1639 */ | |
1640 public function _writeLongHeader($p_filename, $is_link = false) | |
1641 { | |
1642 $v_uid = sprintf("%07s", 0); | |
1643 $v_gid = sprintf("%07s", 0); | |
1644 $v_perms = sprintf("%07s", 0); | |
1645 $v_size = sprintf("%'011s", DecOct(strlen($p_filename))); | |
1646 $v_mtime = sprintf("%011s", 0); | |
1647 $v_typeflag = ($is_link ? 'K' : 'L'); | |
1648 $v_linkname = ''; | |
1649 $v_magic = 'ustar '; | |
1650 $v_version = ' '; | |
1651 $v_uname = ''; | |
1652 $v_gname = ''; | |
1653 $v_devmajor = ''; | |
1654 $v_devminor = ''; | |
1655 $v_prefix = ''; | |
1656 | |
1657 $v_binary_data_first = pack( | |
1658 "a100a8a8a8a12a12", | |
1659 '././@LongLink', | |
1660 $v_perms, | |
1661 $v_uid, | |
1662 $v_gid, | |
1663 $v_size, | |
1664 $v_mtime | |
1665 ); | |
1666 $v_binary_data_last = pack( | |
1667 "a1a100a6a2a32a32a8a8a155a12", | |
1668 $v_typeflag, | |
1669 $v_linkname, | |
1670 $v_magic, | |
1671 $v_version, | |
1672 $v_uname, | |
1673 $v_gname, | |
1674 $v_devmajor, | |
1675 $v_devminor, | |
1676 $v_prefix, | |
1677 '' | |
1678 ); | |
1679 | |
1680 // ----- Calculate the checksum | |
1681 $v_checksum = 0; | |
1682 // ..... First part of the header | |
1683 for ($i = 0; $i < 148; $i++) { | |
1684 $v_checksum += ord(substr($v_binary_data_first, $i, 1)); | |
1685 } | |
1686 // ..... Ignore the checksum value and replace it by ' ' (space) | |
1687 for ($i = 148; $i < 156; $i++) { | |
1688 $v_checksum += ord(' '); | |
1689 } | |
1690 // ..... Last part of the header | |
1691 for ($i = 156, $j = 0; $i < 512; $i++, $j++) { | |
1692 $v_checksum += ord(substr($v_binary_data_last, $j, 1)); | |
1693 } | |
1694 | |
1695 // ----- Write the first 148 bytes of the header in the archive | |
1696 $this->_writeBlock($v_binary_data_first, 148); | |
1697 | |
1698 // ----- Write the calculated checksum | |
1699 $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum)); | |
1700 $v_binary_data = pack("a8", $v_checksum); | |
1701 $this->_writeBlock($v_binary_data, 8); | |
1702 | |
1703 // ----- Write the last 356 bytes of the header in the archive | |
1704 $this->_writeBlock($v_binary_data_last, 356); | |
1705 | |
1706 // ----- Write the filename as content of the block | |
1707 $i = 0; | |
1708 while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') { | |
1709 $v_binary_data = pack("a512", "$v_buffer"); | |
1710 $this->_writeBlock($v_binary_data); | |
1711 } | |
1712 | |
1713 return true; | |
1714 } | |
1715 | |
1716 /** | |
1717 * @param mixed $v_binary_data | |
1718 * @param mixed $v_header | |
1719 * @return bool | |
1720 */ | |
1721 public function _readHeader($v_binary_data, &$v_header) | |
1722 { | |
1723 if (strlen($v_binary_data) == 0) { | |
1724 $v_header['filename'] = ''; | |
1725 return true; | |
1726 } | |
1727 | |
1728 if (strlen($v_binary_data) != 512) { | |
1729 $v_header['filename'] = ''; | |
1730 $this->_error('Invalid block size : ' . strlen($v_binary_data)); | |
1731 return false; | |
1732 } | |
1733 | |
1734 if (!is_array($v_header)) { | |
1735 $v_header = array(); | |
1736 } | |
1737 // ----- Calculate the checksum | |
1738 $v_checksum = 0; | |
1739 // ..... First part of the header | |
1740 $v_binary_split = str_split($v_binary_data); | |
1741 $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148))); | |
1742 $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',))); | |
1743 $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512))); | |
1744 | |
1745 | |
1746 $v_data = unpack($this->_fmt, $v_binary_data); | |
1747 | |
1748 if (strlen($v_data["prefix"]) > 0) { | |
1749 $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; | |
1750 } | |
1751 | |
1752 // ----- Extract the checksum | |
1753 $v_header['checksum'] = OctDec(trim($v_data['checksum'])); | |
1754 if ($v_header['checksum'] != $v_checksum) { | |
1755 $v_header['filename'] = ''; | |
1756 | |
1757 // ----- Look for last block (empty block) | |
1758 if (($v_checksum == 256) && ($v_header['checksum'] == 0)) { | |
1759 return true; | |
1760 } | |
1761 | |
1762 $this->_error( | |
1763 'Invalid checksum for file "' . $v_data['filename'] | |
1764 . '" : ' . $v_checksum . ' calculated, ' | |
1765 . $v_header['checksum'] . ' expected' | |
1766 ); | |
1767 return false; | |
1768 } | |
1769 | |
1770 // ----- Extract the properties | |
1771 $v_header['filename'] = rtrim($v_data['filename'], "\0"); | |
1772 if ($this->_maliciousFilename($v_header['filename'])) { | |
1773 $this->_error( | |
1774 'Malicious .tar detected, file "' . $v_header['filename'] . | |
1775 '" will not install in desired directory tree' | |
1776 ); | |
1777 return false; | |
1778 } | |
1779 $v_header['mode'] = OctDec(trim($v_data['mode'])); | |
1780 $v_header['uid'] = OctDec(trim($v_data['uid'])); | |
1781 $v_header['gid'] = OctDec(trim($v_data['gid'])); | |
1782 $v_header['size'] = $this->_tarRecToSize($v_data['size']); | |
1783 $v_header['mtime'] = OctDec(trim($v_data['mtime'])); | |
1784 if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { | |
1785 $v_header['size'] = 0; | |
1786 } | |
1787 $v_header['link'] = trim($v_data['link']); | |
1788 /* ----- All these fields are removed form the header because | |
1789 they do not carry interesting info | |
1790 $v_header[magic] = trim($v_data[magic]); | |
1791 $v_header[version] = trim($v_data[version]); | |
1792 $v_header[uname] = trim($v_data[uname]); | |
1793 $v_header[gname] = trim($v_data[gname]); | |
1794 $v_header[devmajor] = trim($v_data[devmajor]); | |
1795 $v_header[devminor] = trim($v_data[devminor]); | |
1796 */ | |
1797 | |
1798 return true; | |
1799 } | |
1800 | |
1801 /** | |
1802 * Convert Tar record size to actual size | |
1803 * | |
1804 * @param string $tar_size | |
1805 * @return size of tar record in bytes | |
1806 */ | |
1807 private function _tarRecToSize($tar_size) | |
1808 { | |
1809 /* | |
1810 * First byte of size has a special meaning if bit 7 is set. | |
1811 * | |
1812 * Bit 7 indicates base-256 encoding if set. | |
1813 * Bit 6 is the sign bit. | |
1814 * Bits 5:0 are most significant value bits. | |
1815 */ | |
1816 $ch = ord($tar_size[0]); | |
1817 if ($ch & 0x80) { | |
1818 // Full 12-bytes record is required. | |
1819 $rec_str = $tar_size . "\x00"; | |
1820 | |
1821 $size = ($ch & 0x40) ? -1 : 0; | |
1822 $size = ($size << 6) | ($ch & 0x3f); | |
1823 | |
1824 for ($num_ch = 1; $num_ch < 12; ++$num_ch) { | |
1825 $size = ($size * 256) + ord($rec_str[$num_ch]); | |
1826 } | |
1827 | |
1828 return $size; | |
1829 | |
1830 } else { | |
1831 return OctDec(trim($tar_size)); | |
1832 } | |
1833 } | |
1834 | |
1835 /** | |
1836 * Detect and report a malicious file name | |
1837 * | |
1838 * @param string $file | |
1839 * | |
1840 * @return bool | |
1841 */ | |
1842 private function _maliciousFilename($file) | |
1843 { | |
1844 if (strpos($file, 'phar://') === 0) { | |
1845 return true; | |
1846 } | |
1847 if (strpos($file, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) !== false) { | |
1848 return true; | |
1849 } | |
1850 if (strpos($file, '..' . DIRECTORY_SEPARATOR) === 0) { | |
1851 return true; | |
1852 } | |
1853 return false; | |
1854 } | |
1855 | |
1856 /** | |
1857 * @param $v_header | |
1858 * @return bool | |
1859 */ | |
1860 public function _readLongHeader(&$v_header) | |
1861 { | |
1862 $v_filename = ''; | |
1863 $v_filesize = $v_header['size']; | |
1864 $n = floor($v_header['size'] / 512); | |
1865 for ($i = 0; $i < $n; $i++) { | |
1866 $v_content = $this->_readBlock(); | |
1867 $v_filename .= $v_content; | |
1868 } | |
1869 if (($v_header['size'] % 512) != 0) { | |
1870 $v_content = $this->_readBlock(); | |
1871 $v_filename .= $v_content; | |
1872 } | |
1873 | |
1874 // ----- Read the next header | |
1875 $v_binary_data = $this->_readBlock(); | |
1876 | |
1877 if (!$this->_readHeader($v_binary_data, $v_header)) { | |
1878 return false; | |
1879 } | |
1880 | |
1881 $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0"); | |
1882 $v_header['filename'] = $v_filename; | |
1883 if ($this->_maliciousFilename($v_filename)) { | |
1884 $this->_error( | |
1885 'Malicious .tar detected, file "' . $v_filename . | |
1886 '" will not install in desired directory tree' | |
1887 ); | |
1888 return false; | |
1889 } | |
1890 | |
1891 return true; | |
1892 } | |
1893 | |
1894 /** | |
1895 * This method extract from the archive one file identified by $p_filename. | |
1896 * The return value is a string with the file content, or null on error. | |
1897 * | |
1898 * @param string $p_filename The path of the file to extract in a string. | |
1899 * | |
1900 * @return a string with the file content or null. | |
1901 */ | |
1902 private function _extractInString($p_filename) | |
1903 { | |
1904 $v_result_str = ""; | |
1905 | |
1906 while (strlen($v_binary_data = $this->_readBlock()) != 0) { | |
1907 if (!$this->_readHeader($v_binary_data, $v_header)) { | |
1908 return null; | |
1909 } | |
1910 | |
1911 if ($v_header['filename'] == '') { | |
1912 continue; | |
1913 } | |
1914 | |
1915 switch ($v_header['typeflag']) { | |
1916 case 'L': { | |
1917 if (!$this->_readLongHeader($v_header)) { | |
1918 return null; | |
1919 } | |
1920 } break; | |
1921 | |
1922 case 'K': { | |
1923 $v_link_header = $v_header; | |
1924 if (!$this->_readLongHeader($v_link_header)) { | |
1925 return null; | |
1926 } | |
1927 $v_header['link'] = $v_link_header['filename']; | |
1928 } break; | |
1929 } | |
1930 | |
1931 if ($v_header['filename'] == $p_filename) { | |
1932 if ($v_header['typeflag'] == "5") { | |
1933 $this->_error( | |
1934 'Unable to extract in string a directory ' | |
1935 . 'entry {' . $v_header['filename'] . '}' | |
1936 ); | |
1937 return null; | |
1938 } else { | |
1939 $n = floor($v_header['size'] / 512); | |
1940 for ($i = 0; $i < $n; $i++) { | |
1941 $v_result_str .= $this->_readBlock(); | |
1942 } | |
1943 if (($v_header['size'] % 512) != 0) { | |
1944 $v_content = $this->_readBlock(); | |
1945 $v_result_str .= substr( | |
1946 $v_content, | |
1947 0, | |
1948 ($v_header['size'] % 512) | |
1949 ); | |
1950 } | |
1951 return $v_result_str; | |
1952 } | |
1953 } else { | |
1954 $this->_jumpBlock(ceil(($v_header['size'] / 512))); | |
1955 } | |
1956 } | |
1957 | |
1958 return null; | |
1959 } | |
1960 | |
1961 /** | |
1962 * @param string $p_path | |
1963 * @param string $p_list_detail | |
1964 * @param string $p_mode | |
1965 * @param string $p_file_list | |
1966 * @param string $p_remove_path | |
1967 * @param bool $p_preserve | |
1968 * @return bool | |
1969 */ | |
1970 public function _extractList( | |
1971 $p_path, | |
1972 &$p_list_detail, | |
1973 $p_mode, | |
1974 $p_file_list, | |
1975 $p_remove_path, | |
1976 $p_preserve = false | |
1977 ) { | |
1978 $v_result = true; | |
1979 $v_nb = 0; | |
1980 $v_extract_all = true; | |
1981 $v_listing = false; | |
1982 | |
1983 $p_path = $this->_translateWinPath($p_path, false); | |
1984 if ($p_path == '' || (substr($p_path, 0, 1) != '/' | |
1985 && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':')) | |
1986 ) { | |
1987 $p_path = "./" . $p_path; | |
1988 } | |
1989 $p_remove_path = $this->_translateWinPath($p_remove_path); | |
1990 | |
1991 // ----- Look for path to remove format (should end by /) | |
1992 if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) { | |
1993 $p_remove_path .= '/'; | |
1994 } | |
1995 $p_remove_path_size = strlen($p_remove_path); | |
1996 | |
1997 switch ($p_mode) { | |
1998 case "complete" : | |
1999 $v_extract_all = true; | |
2000 $v_listing = false; | |
2001 break; | |
2002 case "partial" : | |
2003 $v_extract_all = false; | |
2004 $v_listing = false; | |
2005 break; | |
2006 case "list" : | |
2007 $v_extract_all = false; | |
2008 $v_listing = true; | |
2009 break; | |
2010 default : | |
2011 $this->_error('Invalid extract mode (' . $p_mode . ')'); | |
2012 return false; | |
2013 } | |
2014 | |
2015 clearstatcache(); | |
2016 | |
2017 while (strlen($v_binary_data = $this->_readBlock()) != 0) { | |
2018 $v_extract_file = false; | |
2019 $v_extraction_stopped = 0; | |
2020 | |
2021 if (!$this->_readHeader($v_binary_data, $v_header)) { | |
2022 return false; | |
2023 } | |
2024 | |
2025 if ($v_header['filename'] == '') { | |
2026 continue; | |
2027 } | |
2028 | |
2029 switch ($v_header['typeflag']) { | |
2030 case 'L': { | |
2031 if (!$this->_readLongHeader($v_header)) { | |
2032 return null; | |
2033 } | |
2034 } break; | |
2035 | |
2036 case 'K': { | |
2037 $v_link_header = $v_header; | |
2038 if (!$this->_readLongHeader($v_link_header)) { | |
2039 return null; | |
2040 } | |
2041 $v_header['link'] = $v_link_header['filename']; | |
2042 } break; | |
2043 } | |
2044 | |
2045 // ignore extended / pax headers | |
2046 if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') { | |
2047 $this->_jumpBlock(ceil(($v_header['size'] / 512))); | |
2048 continue; | |
2049 } | |
2050 | |
2051 if ((!$v_extract_all) && (is_array($p_file_list))) { | |
2052 // ----- By default no unzip if the file is not found | |
2053 $v_extract_file = false; | |
2054 | |
2055 for ($i = 0; $i < sizeof($p_file_list); $i++) { | |
2056 // ----- Look if it is a directory | |
2057 if (substr($p_file_list[$i], -1) == '/') { | |
2058 // ----- Look if the directory is in the filename path | |
2059 if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) | |
2060 && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) | |
2061 == $p_file_list[$i]) | |
2062 ) { | |
2063 $v_extract_file = true; | |
2064 break; | |
2065 } | |
2066 } // ----- It is a file, so compare the file names | |
2067 elseif ($p_file_list[$i] == $v_header['filename']) { | |
2068 $v_extract_file = true; | |
2069 break; | |
2070 } | |
2071 } | |
2072 } else { | |
2073 $v_extract_file = true; | |
2074 } | |
2075 | |
2076 // ----- Look if this file need to be extracted | |
2077 if (($v_extract_file) && (!$v_listing)) { | |
2078 if (($p_remove_path != '') | |
2079 && (substr($v_header['filename'] . '/', 0, $p_remove_path_size) | |
2080 == $p_remove_path) | |
2081 ) { | |
2082 $v_header['filename'] = substr( | |
2083 $v_header['filename'], | |
2084 $p_remove_path_size | |
2085 ); | |
2086 if ($v_header['filename'] == '') { | |
2087 continue; | |
2088 } | |
2089 } | |
2090 if (($p_path != './') && ($p_path != '/')) { | |
2091 while (substr($p_path, -1) == '/') { | |
2092 $p_path = substr($p_path, 0, strlen($p_path) - 1); | |
2093 } | |
2094 | |
2095 if (substr($v_header['filename'], 0, 1) == '/') { | |
2096 $v_header['filename'] = $p_path . $v_header['filename']; | |
2097 } else { | |
2098 $v_header['filename'] = $p_path . '/' . $v_header['filename']; | |
2099 } | |
2100 } | |
2101 if (file_exists($v_header['filename'])) { | |
2102 if ((@is_dir($v_header['filename'])) | |
2103 && ($v_header['typeflag'] == '') | |
2104 ) { | |
2105 $this->_error( | |
2106 'File ' . $v_header['filename'] | |
2107 . ' already exists as a directory' | |
2108 ); | |
2109 return false; | |
2110 } | |
2111 if (($this->_isArchive($v_header['filename'])) | |
2112 && ($v_header['typeflag'] == "5") | |
2113 ) { | |
2114 $this->_error( | |
2115 'Directory ' . $v_header['filename'] | |
2116 . ' already exists as a file' | |
2117 ); | |
2118 return false; | |
2119 } | |
2120 if (!is_writeable($v_header['filename'])) { | |
2121 $this->_error( | |
2122 'File ' . $v_header['filename'] | |
2123 . ' already exists and is write protected' | |
2124 ); | |
2125 return false; | |
2126 } | |
2127 if (filemtime($v_header['filename']) > $v_header['mtime']) { | |
2128 // To be completed : An error or silent no replace ? | |
2129 } | |
2130 } // ----- Check the directory availability and create it if necessary | |
2131 elseif (($v_result | |
2132 = $this->_dirCheck( | |
2133 ($v_header['typeflag'] == "5" | |
2134 ? $v_header['filename'] | |
2135 : dirname($v_header['filename'])) | |
2136 )) != 1 | |
2137 ) { | |
2138 $this->_error('Unable to create path for ' . $v_header['filename']); | |
2139 return false; | |
2140 } | |
2141 | |
2142 if ($v_extract_file) { | |
2143 if ($v_header['typeflag'] == "5") { | |
2144 if (!@file_exists($v_header['filename'])) { | |
2145 if (!@mkdir($v_header['filename'], 0777)) { | |
2146 $this->_error( | |
2147 'Unable to create directory {' | |
2148 . $v_header['filename'] . '}' | |
2149 ); | |
2150 return false; | |
2151 } | |
2152 } | |
2153 } elseif ($v_header['typeflag'] == "2") { | |
2154 if (@file_exists($v_header['filename'])) { | |
2155 @drupal_unlink($v_header['filename']); | |
2156 } | |
2157 if (!@symlink($v_header['link'], $v_header['filename'])) { | |
2158 $this->_error( | |
2159 'Unable to extract symbolic link {' | |
2160 . $v_header['filename'] . '}' | |
2161 ); | |
2162 return false; | |
2163 } | |
2164 } else { | |
2165 if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { | |
2166 $this->_error( | |
2167 'Error while opening {' . $v_header['filename'] | |
2168 . '} in write binary mode' | |
2169 ); | |
2170 return false; | |
2171 } else { | |
2172 $n = floor($v_header['size'] / 512); | |
2173 for ($i = 0; $i < $n; $i++) { | |
2174 $v_content = $this->_readBlock(); | |
2175 fwrite($v_dest_file, $v_content, 512); | |
2176 } | |
2177 if (($v_header['size'] % 512) != 0) { | |
2178 $v_content = $this->_readBlock(); | |
2179 fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); | |
2180 } | |
2181 | |
2182 @fclose($v_dest_file); | |
2183 | |
2184 if ($p_preserve) { | |
2185 @chown($v_header['filename'], $v_header['uid']); | |
2186 @chgrp($v_header['filename'], $v_header['gid']); | |
2187 } | |
2188 | |
2189 // ----- Change the file mode, mtime | |
2190 @touch($v_header['filename'], $v_header['mtime']); | |
2191 if ($v_header['mode'] & 0111) { | |
2192 // make file executable, obey umask | |
2193 $mode = fileperms($v_header['filename']) | (~umask() & 0111); | |
2194 @chmod($v_header['filename'], $mode); | |
2195 } | |
2196 } | |
2197 | |
2198 // ----- Check the file size | |
2199 clearstatcache(); | |
2200 if (!is_file($v_header['filename'])) { | |
2201 $this->_error( | |
2202 'Extracted file ' . $v_header['filename'] | |
2203 . 'does not exist. Archive may be corrupted.' | |
2204 ); | |
2205 return false; | |
2206 } | |
2207 | |
2208 $filesize = filesize($v_header['filename']); | |
2209 if ($filesize != $v_header['size']) { | |
2210 $this->_error( | |
2211 'Extracted file ' . $v_header['filename'] | |
2212 . ' does not have the correct file size \'' | |
2213 . $filesize | |
2214 . '\' (' . $v_header['size'] | |
2215 . ' expected). Archive may be corrupted.' | |
2216 ); | |
2217 return false; | |
2218 } | |
2219 } | |
2220 } else { | |
2221 $this->_jumpBlock(ceil(($v_header['size'] / 512))); | |
2222 } | |
2223 } else { | |
2224 $this->_jumpBlock(ceil(($v_header['size'] / 512))); | |
2225 } | |
2226 | |
2227 /* TBC : Seems to be unused ... | |
2228 if ($this->_compress) | |
2229 $v_end_of_file = @gzeof($this->_file); | |
2230 else | |
2231 $v_end_of_file = @feof($this->_file); | |
2232 */ | |
2233 | |
2234 if ($v_listing || $v_extract_file || $v_extraction_stopped) { | |
2235 // ----- Log extracted files | |
2236 if (($v_file_dir = dirname($v_header['filename'])) | |
2237 == $v_header['filename'] | |
2238 ) { | |
2239 $v_file_dir = ''; | |
2240 } | |
2241 if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) { | |
2242 $v_file_dir = '/'; | |
2243 } | |
2244 | |
2245 $p_list_detail[$v_nb++] = $v_header; | |
2246 if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { | |
2247 return true; | |
2248 } | |
2249 } | |
2250 } | |
2251 | |
2252 return true; | |
2253 } | |
2254 | |
2255 /** | |
2256 * @return bool | |
2257 */ | |
2258 public function _openAppend() | |
2259 { | |
2260 if (filesize($this->_tarname) == 0) { | |
2261 return $this->_openWrite(); | |
2262 } | |
2263 | |
2264 if ($this->_compress) { | |
2265 $this->_close(); | |
2266 | |
2267 if (!@rename($this->_tarname, $this->_tarname . ".tmp")) { | |
2268 $this->_error( | |
2269 'Error while renaming \'' . $this->_tarname | |
2270 . '\' to temporary file \'' . $this->_tarname | |
2271 . '.tmp\'' | |
2272 ); | |
2273 return false; | |
2274 } | |
2275 | |
2276 if ($this->_compress_type == 'gz') { | |
2277 $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb"); | |
2278 } elseif ($this->_compress_type == 'bz2') { | |
2279 $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r"); | |
2280 } elseif ($this->_compress_type == 'lzma2') { | |
2281 $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r"); | |
2282 } | |
2283 | |
2284 | |
2285 if ($v_temp_tar == 0) { | |
2286 $this->_error( | |
2287 'Unable to open file \'' . $this->_tarname | |
2288 . '.tmp\' in binary read mode' | |
2289 ); | |
2290 @rename($this->_tarname . ".tmp", $this->_tarname); | |
2291 return false; | |
2292 } | |
2293 | |
2294 if (!$this->_openWrite()) { | |
2295 @rename($this->_tarname . ".tmp", $this->_tarname); | |
2296 return false; | |
2297 } | |
2298 | |
2299 if ($this->_compress_type == 'gz') { | |
2300 $end_blocks = 0; | |
2301 | |
2302 while (!@gzeof($v_temp_tar)) { | |
2303 $v_buffer = @gzread($v_temp_tar, 512); | |
2304 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { | |
2305 $end_blocks++; | |
2306 // do not copy end blocks, we will re-make them | |
2307 // after appending | |
2308 continue; | |
2309 } elseif ($end_blocks > 0) { | |
2310 for ($i = 0; $i < $end_blocks; $i++) { | |
2311 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); | |
2312 } | |
2313 $end_blocks = 0; | |
2314 } | |
2315 $v_binary_data = pack("a512", $v_buffer); | |
2316 $this->_writeBlock($v_binary_data); | |
2317 } | |
2318 | |
2319 @gzclose($v_temp_tar); | |
2320 } elseif ($this->_compress_type == 'bz2') { | |
2321 $end_blocks = 0; | |
2322 | |
2323 while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { | |
2324 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { | |
2325 $end_blocks++; | |
2326 // do not copy end blocks, we will re-make them | |
2327 // after appending | |
2328 continue; | |
2329 } elseif ($end_blocks > 0) { | |
2330 for ($i = 0; $i < $end_blocks; $i++) { | |
2331 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); | |
2332 } | |
2333 $end_blocks = 0; | |
2334 } | |
2335 $v_binary_data = pack("a512", $v_buffer); | |
2336 $this->_writeBlock($v_binary_data); | |
2337 } | |
2338 | |
2339 @bzclose($v_temp_tar); | |
2340 } elseif ($this->_compress_type == 'lzma2') { | |
2341 $end_blocks = 0; | |
2342 | |
2343 while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) { | |
2344 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { | |
2345 $end_blocks++; | |
2346 // do not copy end blocks, we will re-make them | |
2347 // after appending | |
2348 continue; | |
2349 } elseif ($end_blocks > 0) { | |
2350 for ($i = 0; $i < $end_blocks; $i++) { | |
2351 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); | |
2352 } | |
2353 $end_blocks = 0; | |
2354 } | |
2355 $v_binary_data = pack("a512", $v_buffer); | |
2356 $this->_writeBlock($v_binary_data); | |
2357 } | |
2358 | |
2359 @xzclose($v_temp_tar); | |
2360 } | |
2361 | |
2362 if (!@drupal_unlink($this->_tarname . ".tmp")) { | |
2363 $this->_error( | |
2364 'Error while deleting temporary file \'' | |
2365 . $this->_tarname . '.tmp\'' | |
2366 ); | |
2367 } | |
2368 } else { | |
2369 // ----- For not compressed tar, just add files before the last | |
2370 // one or two 512 bytes block | |
2371 if (!$this->_openReadWrite()) { | |
2372 return false; | |
2373 } | |
2374 | |
2375 clearstatcache(); | |
2376 $v_size = filesize($this->_tarname); | |
2377 | |
2378 // We might have zero, one or two end blocks. | |
2379 // The standard is two, but we should try to handle | |
2380 // other cases. | |
2381 fseek($this->_file, $v_size - 1024); | |
2382 if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { | |
2383 fseek($this->_file, $v_size - 1024); | |
2384 } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { | |
2385 fseek($this->_file, $v_size - 512); | |
2386 } | |
2387 } | |
2388 | |
2389 return true; | |
2390 } | |
2391 | |
2392 /** | |
2393 * @param $p_filelist | |
2394 * @param string $p_add_dir | |
2395 * @param string $p_remove_dir | |
2396 * @return bool | |
2397 */ | |
2398 public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '') | |
2399 { | |
2400 if (!$this->_openAppend()) { | |
2401 return false; | |
2402 } | |
2403 | |
2404 if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) { | |
2405 $this->_writeFooter(); | |
2406 } | |
2407 | |
2408 $this->_close(); | |
2409 | |
2410 return true; | |
2411 } | |
2412 | |
2413 /** | |
2414 * Check if a directory exists and create it (including parent | |
2415 * dirs) if not. | |
2416 * | |
2417 * @param string $p_dir directory to check | |
2418 * | |
2419 * @return bool true if the directory exists or was created | |
2420 */ | |
2421 public function _dirCheck($p_dir) | |
2422 { | |
2423 clearstatcache(); | |
2424 if ((@is_dir($p_dir)) || ($p_dir == '')) { | |
2425 return true; | |
2426 } | |
2427 | |
2428 $p_parent_dir = dirname($p_dir); | |
2429 | |
2430 if (($p_parent_dir != $p_dir) && | |
2431 ($p_parent_dir != '') && | |
2432 (!$this->_dirCheck($p_parent_dir)) | |
2433 ) { | |
2434 return false; | |
2435 } | |
2436 | |
2437 if (!@mkdir($p_dir, 0777)) { | |
2438 $this->_error("Unable to create directory '$p_dir'"); | |
2439 return false; | |
2440 } | |
2441 | |
2442 return true; | |
2443 } | |
2444 | |
2445 /** | |
2446 * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", | |
2447 * and remove double slashes. | |
2448 * | |
2449 * @param string $p_dir path to reduce | |
2450 * | |
2451 * @return string reduced path | |
2452 */ | |
2453 private function _pathReduction($p_dir) | |
2454 { | |
2455 $v_result = ''; | |
2456 | |
2457 // ----- Look for not empty path | |
2458 if ($p_dir != '') { | |
2459 // ----- Explode path by directory names | |
2460 $v_list = explode('/', $p_dir); | |
2461 | |
2462 // ----- Study directories from last to first | |
2463 for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { | |
2464 // ----- Look for current path | |
2465 if ($v_list[$i] == ".") { | |
2466 // ----- Ignore this directory | |
2467 // Should be the first $i=0, but no check is done | |
2468 } else { | |
2469 if ($v_list[$i] == "..") { | |
2470 // ----- Ignore it and ignore the $i-1 | |
2471 $i--; | |
2472 } else { | |
2473 if (($v_list[$i] == '') | |
2474 && ($i != (sizeof($v_list) - 1)) | |
2475 && ($i != 0) | |
2476 ) { | |
2477 // ----- Ignore only the double '//' in path, | |
2478 // but not the first and last / | |
2479 } else { | |
2480 $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/' | |
2481 . $v_result : ''); | |
2482 } | |
2483 } | |
2484 } | |
2485 } | |
2486 } | |
2487 | |
2488 if (defined('OS_WINDOWS') && OS_WINDOWS) { | |
2489 $v_result = strtr($v_result, '\\', '/'); | |
2490 } | |
2491 | |
2492 return $v_result; | |
2493 } | |
2494 | |
2495 /** | |
2496 * @param $p_path | |
2497 * @param bool $p_remove_disk_letter | |
2498 * @return string | |
2499 */ | |
2500 public function _translateWinPath($p_path, $p_remove_disk_letter = true) | |
2501 { | |
2502 if (defined('OS_WINDOWS') && OS_WINDOWS) { | |
2503 // ----- Look for potential disk letter | |
2504 if (($p_remove_disk_letter) | |
2505 && (($v_position = strpos($p_path, ':')) != false) | |
2506 ) { | |
2507 $p_path = substr($p_path, $v_position + 1); | |
2508 } | |
2509 // ----- Change potential windows directory separator | |
2510 if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { | |
2511 $p_path = strtr($p_path, '\\', '/'); | |
2512 } | |
2513 } | |
2514 return $p_path; | |
2515 } | |
2516 } | |
2517 } |