PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/php/Archive/Tar.php

https://bitbucket.org/adarshj/convenient_website
PHP | 1909 lines | 1436 code | 203 blank | 270 comment | 375 complexity | 7406d6ed69f7c24506c93e02b0cdf839 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-2-Clause, GPL-2.0, LGPL-3.0
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4. * File::CSV
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * Copyright (c) 1997-2008,
  9. * Vincent Blavet <vincent@phpconcept.net>
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. *
  15. * * Redistributions of source code must retain the above copyright notice,
  16. * this list of conditions and the following disclaimer.
  17. * * Redistributions in binary form must reproduce the above copyright
  18. * notice, this list of conditions and the following disclaimer in the
  19. * documentation and/or other materials provided with the distribution.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  24. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  25. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  27. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. *
  32. *
  33. * @category File_Formats
  34. * @package Archive_Tar
  35. * @author Vincent Blavet <vincent@phpconcept.net>
  36. * @copyright 1997-2008 The Authors
  37. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  38. * @version CVS: $Id: Tar.php 295988 2010-03-09 08:39:37Z mrook $
  39. * @link http://pear.php.net/package/Archive_Tar
  40. */
  41. require_once 'PEAR.php';
  42. define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
  43. define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
  44. /**
  45. * Creates a (compressed) Tar archive
  46. *
  47. * @author Vincent Blavet <vincent@phpconcept.net>
  48. * @version $Revision: 295988 $
  49. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  50. * @package Archive_Tar
  51. */
  52. class Archive_Tar extends PEAR
  53. {
  54. /**
  55. * @var string Name of the Tar
  56. */
  57. var $_tarname='';
  58. /**
  59. * @var boolean if true, the Tar file will be gzipped
  60. */
  61. var $_compress=false;
  62. /**
  63. * @var string Type of compression : 'none', 'gz' or 'bz2'
  64. */
  65. var $_compress_type='none';
  66. /**
  67. * @var string Explode separator
  68. */
  69. var $_separator=' ';
  70. /**
  71. * @var file descriptor
  72. */
  73. var $_file=0;
  74. /**
  75. * @var string Local Tar name of a remote Tar (http:// or ftp://)
  76. */
  77. var $_temp_tarname='';
  78. /**
  79. * @var string regular expression for ignoring files or directories
  80. */
  81. var $_ignore_regexp='';
  82. // {{{ constructor
  83. /**
  84. * Archive_Tar Class constructor. This flavour of the constructor only
  85. * declare a new Archive_Tar object, identifying it by the name of the
  86. * tar file.
  87. * If the compress argument is set the tar will be read or created as a
  88. * gzip or bz2 compressed TAR file.
  89. *
  90. * @param string $p_tarname The name of the tar archive to create
  91. * @param string $p_compress can be null, 'gz' or 'bz2'. This
  92. * parameter indicates if gzip or bz2 compression
  93. * is required. For compatibility reason the
  94. * boolean value 'true' means 'gz'.
  95. * @access public
  96. */
  97. function Archive_Tar($p_tarname, $p_compress = null)
  98. {
  99. $this->PEAR();
  100. $this->_compress = false;
  101. $this->_compress_type = 'none';
  102. if (($p_compress === null) || ($p_compress == '')) {
  103. if (@file_exists($p_tarname)) {
  104. if ($fp = @fopen($p_tarname, "rb")) {
  105. // look for gzip magic cookie
  106. $data = fread($fp, 2);
  107. fclose($fp);
  108. if ($data == "\37\213") {
  109. $this->_compress = true;
  110. $this->_compress_type = 'gz';
  111. // No sure it's enought for a magic code ....
  112. } elseif ($data == "BZ") {
  113. $this->_compress = true;
  114. $this->_compress_type = 'bz2';
  115. }
  116. }
  117. } else {
  118. // probably a remote file or some file accessible
  119. // through a stream interface
  120. if (substr($p_tarname, -2) == 'gz') {
  121. $this->_compress = true;
  122. $this->_compress_type = 'gz';
  123. } elseif ((substr($p_tarname, -3) == 'bz2') ||
  124. (substr($p_tarname, -2) == 'bz')) {
  125. $this->_compress = true;
  126. $this->_compress_type = 'bz2';
  127. }
  128. }
  129. } else {
  130. if (($p_compress === true) || ($p_compress == 'gz')) {
  131. $this->_compress = true;
  132. $this->_compress_type = 'gz';
  133. } else if ($p_compress == 'bz2') {
  134. $this->_compress = true;
  135. $this->_compress_type = 'bz2';
  136. } else {
  137. $this->_error("Unsupported compression type '$p_compress'\n".
  138. "Supported types are 'gz' and 'bz2'.\n");
  139. return false;
  140. }
  141. }
  142. $this->_tarname = $p_tarname;
  143. if ($this->_compress) { // assert zlib or bz2 extension support
  144. if ($this->_compress_type == 'gz')
  145. $extname = 'zlib';
  146. else if ($this->_compress_type == 'bz2')
  147. $extname = 'bz2';
  148. if (!extension_loaded($extname)) {
  149. PEAR::loadExtension($extname);
  150. }
  151. if (!extension_loaded($extname)) {
  152. $this->_error("The extension '$extname' couldn't be found.\n".
  153. "Please make sure your version of PHP was built ".
  154. "with '$extname' support.\n");
  155. return false;
  156. }
  157. }
  158. }
  159. // }}}
  160. // {{{ destructor
  161. function _Archive_Tar()
  162. {
  163. $this->_close();
  164. // ----- Look for a local copy to delete
  165. if ($this->_temp_tarname != '')
  166. @unlink($this->_temp_tarname);
  167. $this->_PEAR();
  168. }
  169. // }}}
  170. // {{{ create()
  171. /**
  172. * This method creates the archive file and add the files / directories
  173. * that are listed in $p_filelist.
  174. * If a file with the same name exist and is writable, it is replaced
  175. * by the new tar.
  176. * The method return false and a PEAR error text.
  177. * The $p_filelist parameter can be an array of string, each string
  178. * representing a filename or a directory name with their path if
  179. * needed. It can also be a single string with names separated by a
  180. * single blank.
  181. * For each directory added in the archive, the files and
  182. * sub-directories are also added.
  183. * See also createModify() method for more details.
  184. *
  185. * @param array $p_filelist An array of filenames and directory names, or a
  186. * single string with names separated by a single
  187. * blank space.
  188. * @return true on success, false on error.
  189. * @see createModify()
  190. * @access public
  191. */
  192. function create($p_filelist)
  193. {
  194. return $this->createModify($p_filelist, '', '');
  195. }
  196. // }}}
  197. // {{{ add()
  198. /**
  199. * This method add the files / directories that are listed in $p_filelist in
  200. * the archive. If the archive does not exist it is created.
  201. * The method return false and a PEAR error text.
  202. * The files and directories listed are only added at the end of the archive,
  203. * even if a file with the same name is already archived.
  204. * See also createModify() method for more details.
  205. *
  206. * @param array $p_filelist An array of filenames and directory names, or a
  207. * single string with names separated by a single
  208. * blank space.
  209. * @return true on success, false on error.
  210. * @see createModify()
  211. * @access public
  212. */
  213. function add($p_filelist)
  214. {
  215. return $this->addModify($p_filelist, '', '');
  216. }
  217. // }}}
  218. // {{{ extract()
  219. function extract($p_path='')
  220. {
  221. return $this->extractModify($p_path, '');
  222. }
  223. // }}}
  224. // {{{ listContent()
  225. function listContent()
  226. {
  227. $v_list_detail = array();
  228. if ($this->_openRead()) {
  229. if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
  230. unset($v_list_detail);
  231. $v_list_detail = 0;
  232. }
  233. $this->_close();
  234. }
  235. return $v_list_detail;
  236. }
  237. // }}}
  238. // {{{ createModify()
  239. /**
  240. * This method creates the archive file and add the files / directories
  241. * that are listed in $p_filelist.
  242. * If the file already exists and is writable, it is replaced by the
  243. * new tar. It is a create and not an add. If the file exists and is
  244. * read-only or is a directory it is not replaced. The method return
  245. * false and a PEAR error text.
  246. * The $p_filelist parameter can be an array of string, each string
  247. * representing a filename or a directory name with their path if
  248. * needed. It can also be a single string with names separated by a
  249. * single blank.
  250. * The path indicated in $p_remove_dir will be removed from the
  251. * memorized path of each file / directory listed when this path
  252. * exists. By default nothing is removed (empty path '')
  253. * The path indicated in $p_add_dir will be added at the beginning of
  254. * the memorized path of each file / directory listed. However it can
  255. * be set to empty ''. The adding of a path is done after the removing
  256. * of path.
  257. * The path add/remove ability enables the user to prepare an archive
  258. * for extraction in a different path than the origin files are.
  259. * See also addModify() method for file adding properties.
  260. *
  261. * @param array $p_filelist An array of filenames and directory names,
  262. * or a single string with names separated by
  263. * a single blank space.
  264. * @param string $p_add_dir A string which contains a path to be added
  265. * to the memorized path of each element in
  266. * the list.
  267. * @param string $p_remove_dir A string which contains a path to be
  268. * removed from the memorized path of each
  269. * element in the list, when relevant.
  270. * @return boolean true on success, false on error.
  271. * @access public
  272. * @see addModify()
  273. */
  274. function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
  275. {
  276. $v_result = true;
  277. if (!$this->_openWrite())
  278. return false;
  279. if ($p_filelist != '') {
  280. if (is_array($p_filelist))
  281. $v_list = $p_filelist;
  282. elseif (is_string($p_filelist))
  283. $v_list = explode($this->_separator, $p_filelist);
  284. else {
  285. $this->_cleanFile();
  286. $this->_error('Invalid file list');
  287. return false;
  288. }
  289. $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
  290. }
  291. if ($v_result) {
  292. $this->_writeFooter();
  293. $this->_close();
  294. } else
  295. $this->_cleanFile();
  296. return $v_result;
  297. }
  298. // }}}
  299. // {{{ addModify()
  300. /**
  301. * This method add the files / directories listed in $p_filelist at the
  302. * end of the existing archive. If the archive does not yet exists it
  303. * is created.
  304. * The $p_filelist parameter can be an array of string, each string
  305. * representing a filename or a directory name with their path if
  306. * needed. It can also be a single string with names separated by a
  307. * single blank.
  308. * The path indicated in $p_remove_dir will be removed from the
  309. * memorized path of each file / directory listed when this path
  310. * exists. By default nothing is removed (empty path '')
  311. * The path indicated in $p_add_dir will be added at the beginning of
  312. * the memorized path of each file / directory listed. However it can
  313. * be set to empty ''. The adding of a path is done after the removing
  314. * of path.
  315. * The path add/remove ability enables the user to prepare an archive
  316. * for extraction in a different path than the origin files are.
  317. * If a file/dir is already in the archive it will only be added at the
  318. * end of the archive. There is no update of the existing archived
  319. * file/dir. However while extracting the archive, the last file will
  320. * replace the first one. This results in a none optimization of the
  321. * archive size.
  322. * If a file/dir does not exist the file/dir is ignored. However an
  323. * error text is send to PEAR error.
  324. * If a file/dir is not readable the file/dir is ignored. However an
  325. * error text is send to PEAR error.
  326. *
  327. * @param array $p_filelist An array of filenames and directory
  328. * names, or a single string with names
  329. * separated by a single blank space.
  330. * @param string $p_add_dir A string which contains a path to be
  331. * added to the memorized path of each
  332. * element in the list.
  333. * @param string $p_remove_dir A string which contains a path to be
  334. * removed from the memorized path of
  335. * each element in the list, when
  336. * relevant.
  337. * @return true on success, false on error.
  338. * @access public
  339. */
  340. function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
  341. {
  342. $v_result = true;
  343. if (!$this->_isArchive())
  344. $v_result = $this->createModify($p_filelist, $p_add_dir,
  345. $p_remove_dir);
  346. else {
  347. if (is_array($p_filelist))
  348. $v_list = $p_filelist;
  349. elseif (is_string($p_filelist))
  350. $v_list = explode($this->_separator, $p_filelist);
  351. else {
  352. $this->_error('Invalid file list');
  353. return false;
  354. }
  355. $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
  356. }
  357. return $v_result;
  358. }
  359. // }}}
  360. // {{{ addString()
  361. /**
  362. * This method add a single string as a file at the
  363. * end of the existing archive. If the archive does not yet exists it
  364. * is created.
  365. *
  366. * @param string $p_filename A string which contains the full
  367. * filename path that will be associated
  368. * with the string.
  369. * @param string $p_string The content of the file added in
  370. * the archive.
  371. * @return true on success, false on error.
  372. * @access public
  373. */
  374. function addString($p_filename, $p_string)
  375. {
  376. $v_result = true;
  377. if (!$this->_isArchive()) {
  378. if (!$this->_openWrite()) {
  379. return false;
  380. }
  381. $this->_close();
  382. }
  383. if (!$this->_openAppend())
  384. return false;
  385. // Need to check the get back to the temporary file ? ....
  386. $v_result = $this->_addString($p_filename, $p_string);
  387. $this->_writeFooter();
  388. $this->_close();
  389. return $v_result;
  390. }
  391. // }}}
  392. // {{{ extractModify()
  393. /**
  394. * This method extract all the content of the archive in the directory
  395. * indicated by $p_path. When relevant the memorized path of the
  396. * files/dir can be modified by removing the $p_remove_path path at the
  397. * beginning of the file/dir path.
  398. * While extracting a file, if the directory path does not exists it is
  399. * created.
  400. * While extracting a file, if the file already exists it is replaced
  401. * without looking for last modification date.
  402. * While extracting a file, if the file already exists and is write
  403. * protected, the extraction is aborted.
  404. * While extracting a file, if a directory with the same name already
  405. * exists, the extraction is aborted.
  406. * While extracting a directory, if a file with the same name already
  407. * exists, the extraction is aborted.
  408. * While extracting a file/directory if the destination directory exist
  409. * and is write protected, or does not exist but can not be created,
  410. * the extraction is aborted.
  411. * If after extraction an extracted file does not show the correct
  412. * stored file size, the extraction is aborted.
  413. * When the extraction is aborted, a PEAR error text is set and false
  414. * is returned. However the result can be a partial extraction that may
  415. * need to be manually cleaned.
  416. *
  417. * @param string $p_path The path of the directory where the
  418. * files/dir need to by extracted.
  419. * @param string $p_remove_path Part of the memorized path that can be
  420. * removed if present at the beginning of
  421. * the file/dir path.
  422. * @return boolean true on success, false on error.
  423. * @access public
  424. * @see extractList()
  425. */
  426. function extractModify($p_path, $p_remove_path)
  427. {
  428. $v_result = true;
  429. $v_list_detail = array();
  430. if ($v_result = $this->_openRead()) {
  431. $v_result = $this->_extractList($p_path, $v_list_detail,
  432. "complete", 0, $p_remove_path);
  433. $this->_close();
  434. }
  435. return $v_result;
  436. }
  437. // }}}
  438. // {{{ extractInString()
  439. /**
  440. * This method extract from the archive one file identified by $p_filename.
  441. * The return value is a string with the file content, or NULL on error.
  442. * @param string $p_filename The path of the file to extract in a string.
  443. * @return a string with the file content or NULL.
  444. * @access public
  445. */
  446. function extractInString($p_filename)
  447. {
  448. if ($this->_openRead()) {
  449. $v_result = $this->_extractInString($p_filename);
  450. $this->_close();
  451. } else {
  452. $v_result = NULL;
  453. }
  454. return $v_result;
  455. }
  456. // }}}
  457. // {{{ extractList()
  458. /**
  459. * This method extract from the archive only the files indicated in the
  460. * $p_filelist. These files are extracted in the current directory or
  461. * in the directory indicated by the optional $p_path parameter.
  462. * If indicated the $p_remove_path can be used in the same way as it is
  463. * used in extractModify() method.
  464. * @param array $p_filelist An array of filenames and directory names,
  465. * or a single string with names separated
  466. * by a single blank space.
  467. * @param string $p_path The path of the directory where the
  468. * files/dir need to by extracted.
  469. * @param string $p_remove_path Part of the memorized path that can be
  470. * removed if present at the beginning of
  471. * the file/dir path.
  472. * @return true on success, false on error.
  473. * @access public
  474. * @see extractModify()
  475. */
  476. function extractList($p_filelist, $p_path='', $p_remove_path='')
  477. {
  478. $v_result = true;
  479. $v_list_detail = array();
  480. if (is_array($p_filelist))
  481. $v_list = $p_filelist;
  482. elseif (is_string($p_filelist))
  483. $v_list = explode($this->_separator, $p_filelist);
  484. else {
  485. $this->_error('Invalid string list');
  486. return false;
  487. }
  488. if ($v_result = $this->_openRead()) {
  489. $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
  490. $v_list, $p_remove_path);
  491. $this->_close();
  492. }
  493. return $v_result;
  494. }
  495. // }}}
  496. // {{{ setAttribute()
  497. /**
  498. * This method set specific attributes of the archive. It uses a variable
  499. * list of parameters, in the format attribute code + attribute values :
  500. * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
  501. * @param mixed $argv variable list of attributes and values
  502. * @return true on success, false on error.
  503. * @access public
  504. */
  505. function setAttribute()
  506. {
  507. $v_result = true;
  508. // ----- Get the number of variable list of arguments
  509. if (($v_size = func_num_args()) == 0) {
  510. return true;
  511. }
  512. // ----- Get the arguments
  513. $v_att_list = &func_get_args();
  514. // ----- Read the attributes
  515. $i=0;
  516. while ($i<$v_size) {
  517. // ----- Look for next option
  518. switch ($v_att_list[$i]) {
  519. // ----- Look for options that request a string value
  520. case ARCHIVE_TAR_ATT_SEPARATOR :
  521. // ----- Check the number of parameters
  522. if (($i+1) >= $v_size) {
  523. $this->_error('Invalid number of parameters for '
  524. .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
  525. return false;
  526. }
  527. // ----- Get the value
  528. $this->_separator = $v_att_list[$i+1];
  529. $i++;
  530. break;
  531. default :
  532. $this->_error('Unknow attribute code '.$v_att_list[$i].'');
  533. return false;
  534. }
  535. // ----- Next attribute
  536. $i++;
  537. }
  538. return $v_result;
  539. }
  540. // }}}
  541. // {{{ setIgnoreRegexp()
  542. /**
  543. * This method sets the regular expression for ignoring files and directories
  544. * at import, for example:
  545. * $arch->setIgnoreRegexp("#CVS|\.svn#");
  546. * @param string $regexp regular expression defining which files or directories to ignore
  547. * @access public
  548. */
  549. function setIgnoreRegexp($regexp)
  550. {
  551. $this->_ignore_regexp = $regexp;
  552. }
  553. // }}}
  554. // {{{ setIgnoreList()
  555. /**
  556. * This method sets the regular expression for ignoring all files and directories
  557. * matching the filenames in the array list at import, for example:
  558. * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
  559. * @param array $list a list of file or directory names to ignore
  560. * @access public
  561. */
  562. function setIgnoreList($list)
  563. {
  564. $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
  565. $regexp = '#/'.join('$|/', $list).'#';
  566. $this->setIgnoreRegexp($regexp);
  567. }
  568. // }}}
  569. // {{{ _error()
  570. function _error($p_message)
  571. {
  572. // ----- To be completed
  573. $this->raiseError($p_message);
  574. }
  575. // }}}
  576. // {{{ _warning()
  577. function _warning($p_message)
  578. {
  579. // ----- To be completed
  580. $this->raiseError($p_message);
  581. }
  582. // }}}
  583. // {{{ _isArchive()
  584. function _isArchive($p_filename=NULL)
  585. {
  586. if ($p_filename == NULL) {
  587. $p_filename = $this->_tarname;
  588. }
  589. clearstatcache();
  590. return @is_file($p_filename) && !@is_link($p_filename);
  591. }
  592. // }}}
  593. // {{{ _openWrite()
  594. function _openWrite()
  595. {
  596. if ($this->_compress_type == 'gz')
  597. $this->_file = @gzopen($this->_tarname, "wb9");
  598. else if ($this->_compress_type == 'bz2')
  599. $this->_file = @bzopen($this->_tarname, "w");
  600. else if ($this->_compress_type == 'none')
  601. $this->_file = @fopen($this->_tarname, "wb");
  602. else
  603. $this->_error('Unknown or missing compression type ('
  604. .$this->_compress_type.')');
  605. if ($this->_file == 0) {
  606. $this->_error('Unable to open in write mode \''
  607. .$this->_tarname.'\'');
  608. return false;
  609. }
  610. return true;
  611. }
  612. // }}}
  613. // {{{ _openRead()
  614. function _openRead()
  615. {
  616. if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
  617. // ----- Look if a local copy need to be done
  618. if ($this->_temp_tarname == '') {
  619. $this->_temp_tarname = uniqid('tar').'.tmp';
  620. if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
  621. $this->_error('Unable to open in read mode \''
  622. .$this->_tarname.'\'');
  623. $this->_temp_tarname = '';
  624. return false;
  625. }
  626. if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
  627. $this->_error('Unable to open in write mode \''
  628. .$this->_temp_tarname.'\'');
  629. $this->_temp_tarname = '';
  630. return false;
  631. }
  632. while ($v_data = @fread($v_file_from, 1024))
  633. @fwrite($v_file_to, $v_data);
  634. @fclose($v_file_from);
  635. @fclose($v_file_to);
  636. }
  637. // ----- File to open if the local copy
  638. $v_filename = $this->_temp_tarname;
  639. } else
  640. // ----- File to open if the normal Tar file
  641. $v_filename = $this->_tarname;
  642. if ($this->_compress_type == 'gz')
  643. $this->_file = @gzopen($v_filename, "rb");
  644. else if ($this->_compress_type == 'bz2')
  645. $this->_file = @bzopen($v_filename, "r");
  646. else if ($this->_compress_type == 'none')
  647. $this->_file = @fopen($v_filename, "rb");
  648. else
  649. $this->_error('Unknown or missing compression type ('
  650. .$this->_compress_type.')');
  651. if ($this->_file == 0) {
  652. $this->_error('Unable to open in read mode \''.$v_filename.'\'');
  653. return false;
  654. }
  655. return true;
  656. }
  657. // }}}
  658. // {{{ _openReadWrite()
  659. function _openReadWrite()
  660. {
  661. if ($this->_compress_type == 'gz')
  662. $this->_file = @gzopen($this->_tarname, "r+b");
  663. else if ($this->_compress_type == 'bz2') {
  664. $this->_error('Unable to open bz2 in read/write mode \''
  665. .$this->_tarname.'\' (limitation of bz2 extension)');
  666. return false;
  667. } else if ($this->_compress_type == 'none')
  668. $this->_file = @fopen($this->_tarname, "r+b");
  669. else
  670. $this->_error('Unknown or missing compression type ('
  671. .$this->_compress_type.')');
  672. if ($this->_file == 0) {
  673. $this->_error('Unable to open in read/write mode \''
  674. .$this->_tarname.'\'');
  675. return false;
  676. }
  677. return true;
  678. }
  679. // }}}
  680. // {{{ _close()
  681. function _close()
  682. {
  683. //if (isset($this->_file)) {
  684. if (is_resource($this->_file)) {
  685. if ($this->_compress_type == 'gz')
  686. @gzclose($this->_file);
  687. else if ($this->_compress_type == 'bz2')
  688. @bzclose($this->_file);
  689. else if ($this->_compress_type == 'none')
  690. @fclose($this->_file);
  691. else
  692. $this->_error('Unknown or missing compression type ('
  693. .$this->_compress_type.')');
  694. $this->_file = 0;
  695. }
  696. // ----- Look if a local copy need to be erase
  697. // Note that it might be interesting to keep the url for a time : ToDo
  698. if ($this->_temp_tarname != '') {
  699. @unlink($this->_temp_tarname);
  700. $this->_temp_tarname = '';
  701. }
  702. return true;
  703. }
  704. // }}}
  705. // {{{ _cleanFile()
  706. function _cleanFile()
  707. {
  708. $this->_close();
  709. // ----- Look for a local copy
  710. if ($this->_temp_tarname != '') {
  711. // ----- Remove the local copy but not the remote tarname
  712. @unlink($this->_temp_tarname);
  713. $this->_temp_tarname = '';
  714. } else {
  715. // ----- Remove the local tarname file
  716. @unlink($this->_tarname);
  717. }
  718. $this->_tarname = '';
  719. return true;
  720. }
  721. // }}}
  722. // {{{ _writeBlock()
  723. function _writeBlock($p_binary_data, $p_len=null)
  724. {
  725. if (is_resource($this->_file)) {
  726. if ($p_len === null) {
  727. if ($this->_compress_type == 'gz')
  728. @gzputs($this->_file, $p_binary_data);
  729. else if ($this->_compress_type == 'bz2')
  730. @bzwrite($this->_file, $p_binary_data);
  731. else if ($this->_compress_type == 'none')
  732. @fputs($this->_file, $p_binary_data);
  733. else
  734. $this->_error('Unknown or missing compression type ('
  735. .$this->_compress_type.')');
  736. } else {
  737. if ($this->_compress_type == 'gz')
  738. @gzputs($this->_file, $p_binary_data, $p_len);
  739. else if ($this->_compress_type == 'bz2')
  740. @bzwrite($this->_file, $p_binary_data, $p_len);
  741. else if ($this->_compress_type == 'none')
  742. @fputs($this->_file, $p_binary_data, $p_len);
  743. else
  744. $this->_error('Unknown or missing compression type ('
  745. .$this->_compress_type.')');
  746. }
  747. }
  748. return true;
  749. }
  750. // }}}
  751. // {{{ _readBlock()
  752. function _readBlock()
  753. {
  754. $v_block = null;
  755. if (is_resource($this->_file)) {
  756. if ($this->_compress_type == 'gz')
  757. $v_block = @gzread($this->_file, 512);
  758. else if ($this->_compress_type == 'bz2')
  759. $v_block = @bzread($this->_file, 512);
  760. else if ($this->_compress_type == 'none')
  761. $v_block = @fread($this->_file, 512);
  762. else
  763. $this->_error('Unknown or missing compression type ('
  764. .$this->_compress_type.')');
  765. }
  766. return $v_block;
  767. }
  768. // }}}
  769. // {{{ _jumpBlock()
  770. function _jumpBlock($p_len=null)
  771. {
  772. if (is_resource($this->_file)) {
  773. if ($p_len === null)
  774. $p_len = 1;
  775. if ($this->_compress_type == 'gz') {
  776. @gzseek($this->_file, gztell($this->_file)+($p_len*512));
  777. }
  778. else if ($this->_compress_type == 'bz2') {
  779. // ----- Replace missing bztell() and bzseek()
  780. for ($i=0; $i<$p_len; $i++)
  781. $this->_readBlock();
  782. } else if ($this->_compress_type == 'none')
  783. @fseek($this->_file, $p_len*512, SEEK_CUR);
  784. else
  785. $this->_error('Unknown or missing compression type ('
  786. .$this->_compress_type.')');
  787. }
  788. return true;
  789. }
  790. // }}}
  791. // {{{ _writeFooter()
  792. function _writeFooter()
  793. {
  794. if (is_resource($this->_file)) {
  795. // ----- Write the last 0 filled block for end of archive
  796. $v_binary_data = pack('a1024', '');
  797. $this->_writeBlock($v_binary_data);
  798. }
  799. return true;
  800. }
  801. // }}}
  802. // {{{ _addList()
  803. function _addList($p_list, $p_add_dir, $p_remove_dir)
  804. {
  805. $v_result=true;
  806. $v_header = array();
  807. // ----- Remove potential windows directory separator
  808. $p_add_dir = $this->_translateWinPath($p_add_dir);
  809. $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
  810. if (!$this->_file) {
  811. $this->_error('Invalid file descriptor');
  812. return false;
  813. }
  814. if (sizeof($p_list) == 0)
  815. return true;
  816. foreach ($p_list as $v_filename) {
  817. if (!$v_result) {
  818. break;
  819. }
  820. // ----- Skip the current tar name
  821. if ($v_filename == $this->_tarname)
  822. continue;
  823. if ($v_filename == '')
  824. continue;
  825. // ----- ignore files and directories matching the ignore regular expression
  826. if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) {
  827. $this->_warning("File '$v_filename' ignored");
  828. continue;
  829. }
  830. if (!file_exists($v_filename)) {
  831. $this->_warning("File '$v_filename' does not exist");
  832. continue;
  833. }
  834. // ----- Add the file or directory header
  835. if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
  836. return false;
  837. if (@is_dir($v_filename) && !@is_link($v_filename)) {
  838. if (!($p_hdir = opendir($v_filename))) {
  839. $this->_warning("Directory '$v_filename' can not be read");
  840. continue;
  841. }
  842. while (false !== ($p_hitem = readdir($p_hdir))) {
  843. if (($p_hitem != '.') && ($p_hitem != '..')) {
  844. if ($v_filename != ".")
  845. $p_temp_list[0] = $v_filename.'/'.$p_hitem;
  846. else
  847. $p_temp_list[0] = $p_hitem;
  848. $v_result = $this->_addList($p_temp_list,
  849. $p_add_dir,
  850. $p_remove_dir);
  851. }
  852. }
  853. unset($p_temp_list);
  854. unset($p_hdir);
  855. unset($p_hitem);
  856. }
  857. }
  858. return $v_result;
  859. }
  860. // }}}
  861. // {{{ _addFile()
  862. function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
  863. {
  864. if (!$this->_file) {
  865. $this->_error('Invalid file descriptor');
  866. return false;
  867. }
  868. if ($p_filename == '') {
  869. $this->_error('Invalid file name');
  870. return false;
  871. }
  872. // ----- Calculate the stored filename
  873. $p_filename = $this->_translateWinPath($p_filename, false);;
  874. $v_stored_filename = $p_filename;
  875. if (strcmp($p_filename, $p_remove_dir) == 0) {
  876. return true;
  877. }
  878. if ($p_remove_dir != '') {
  879. if (substr($p_remove_dir, -1) != '/')
  880. $p_remove_dir .= '/';
  881. if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
  882. $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
  883. }
  884. $v_stored_filename = $this->_translateWinPath($v_stored_filename);
  885. if ($p_add_dir != '') {
  886. if (substr($p_add_dir, -1) == '/')
  887. $v_stored_filename = $p_add_dir.$v_stored_filename;
  888. else
  889. $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
  890. }
  891. $v_stored_filename = $this->_pathReduction($v_stored_filename);
  892. if ($this->_isArchive($p_filename)) {
  893. if (($v_file = @fopen($p_filename, "rb")) == 0) {
  894. $this->_warning("Unable to open file '".$p_filename
  895. ."' in binary read mode");
  896. return true;
  897. }
  898. if (!$this->_writeHeader($p_filename, $v_stored_filename))
  899. return false;
  900. while (($v_buffer = fread($v_file, 512)) != '') {
  901. $v_binary_data = pack("a512", "$v_buffer");
  902. $this->_writeBlock($v_binary_data);
  903. }
  904. fclose($v_file);
  905. } else {
  906. // ----- Only header for dir
  907. if (!$this->_writeHeader($p_filename, $v_stored_filename))
  908. return false;
  909. }
  910. return true;
  911. }
  912. // }}}
  913. // {{{ _addString()
  914. function _addString($p_filename, $p_string)
  915. {
  916. if (!$this->_file) {
  917. $this->_error('Invalid file descriptor');
  918. return false;
  919. }
  920. if ($p_filename == '') {
  921. $this->_error('Invalid file name');
  922. return false;
  923. }
  924. // ----- Calculate the stored filename
  925. $p_filename = $this->_translateWinPath($p_filename, false);;
  926. if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
  927. time(), 384, "", 0, 0))
  928. return false;
  929. $i=0;
  930. while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
  931. $v_binary_data = pack("a512", $v_buffer);
  932. $this->_writeBlock($v_binary_data);
  933. }
  934. return true;
  935. }
  936. // }}}
  937. // {{{ _writeHeader()
  938. function _writeHeader($p_filename, $p_stored_filename)
  939. {
  940. if ($p_stored_filename == '')
  941. $p_stored_filename = $p_filename;
  942. $v_reduce_filename = $this->_pathReduction($p_stored_filename);
  943. if (strlen($v_reduce_filename) > 99) {
  944. if (!$this->_writeLongHeader($v_reduce_filename))
  945. return false;
  946. }
  947. $v_info = lstat($p_filename);
  948. $v_uid = sprintf("%07s", DecOct($v_info[4]));
  949. $v_gid = sprintf("%07s", DecOct($v_info[5]));
  950. $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
  951. $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
  952. $v_linkname = '';
  953. if (@is_link($p_filename)) {
  954. $v_typeflag = '2';
  955. $v_linkname = readlink($p_filename);
  956. $v_size = sprintf("%011s", DecOct(0));
  957. } elseif (@is_dir($p_filename)) {
  958. $v_typeflag = "5";
  959. $v_size = sprintf("%011s", DecOct(0));
  960. } else {
  961. $v_typeflag = '0';
  962. clearstatcache();
  963. $v_size = sprintf("%011s", DecOct($v_info['size']));
  964. }
  965. $v_magic = 'ustar ';
  966. $v_version = ' ';
  967. if (function_exists('posix_getpwuid'))
  968. {
  969. $userinfo = posix_getpwuid($v_info[4]);
  970. $groupinfo = posix_getgrgid($v_info[5]);
  971. $v_uname = $userinfo['name'];
  972. $v_gname = $groupinfo['name'];
  973. }
  974. else
  975. {
  976. $v_uname = '';
  977. $v_gname = '';
  978. }
  979. $v_devmajor = '';
  980. $v_devminor = '';
  981. $v_prefix = '';
  982. $v_binary_data_first = pack("a100a8a8a8a12a12",
  983. $v_reduce_filename, $v_perms, $v_uid,
  984. $v_gid, $v_size, $v_mtime);
  985. $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
  986. $v_typeflag, $v_linkname, $v_magic,
  987. $v_version, $v_uname, $v_gname,
  988. $v_devmajor, $v_devminor, $v_prefix, '');
  989. // ----- Calculate the checksum
  990. $v_checksum = 0;
  991. // ..... First part of the header
  992. for ($i=0; $i<148; $i++)
  993. $v_checksum += ord(substr($v_binary_data_first,$i,1));
  994. // ..... Ignore the checksum value and replace it by ' ' (space)
  995. for ($i=148; $i<156; $i++)
  996. $v_checksum += ord(' ');
  997. // ..... Last part of the header
  998. for ($i=156, $j=0; $i<512; $i++, $j++)
  999. $v_checksum += ord(substr($v_binary_data_last,$j,1));
  1000. // ----- Write the first 148 bytes of the header in the archive
  1001. $this->_writeBlock($v_binary_data_first, 148);
  1002. // ----- Write the calculated checksum
  1003. $v_checksum = sprintf("%06s ", DecOct($v_checksum));
  1004. $v_binary_data = pack("a8", $v_checksum);
  1005. $this->_writeBlock($v_binary_data, 8);
  1006. // ----- Write the last 356 bytes of the header in the archive
  1007. $this->_writeBlock($v_binary_data_last, 356);
  1008. return true;
  1009. }
  1010. // }}}
  1011. // {{{ _writeHeaderBlock()
  1012. function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
  1013. $p_type='', $p_uid=0, $p_gid=0)
  1014. {
  1015. $p_filename = $this->_pathReduction($p_filename);
  1016. if (strlen($p_filename) > 99) {
  1017. if (!$this->_writeLongHeader($p_filename))
  1018. return false;
  1019. }
  1020. if ($p_type == "5") {
  1021. $v_size = sprintf("%011s", DecOct(0));
  1022. } else {
  1023. $v_size = sprintf("%011s", DecOct($p_size));
  1024. }
  1025. $v_uid = sprintf("%07s", DecOct($p_uid));
  1026. $v_gid = sprintf("%07s", DecOct($p_gid));
  1027. $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
  1028. $v_mtime = sprintf("%11s", DecOct($p_mtime));
  1029. $v_linkname = '';
  1030. $v_magic = 'ustar ';
  1031. $v_version = ' ';
  1032. if (function_exists('posix_getpwuid'))
  1033. {
  1034. $userinfo = posix_getpwuid($p_uid);
  1035. $groupinfo = posix_getgrgid($p_gid);
  1036. $v_uname = $userinfo['name'];
  1037. $v_gname = $groupinfo['name'];
  1038. }
  1039. else
  1040. {
  1041. $v_uname = '';
  1042. $v_gname = '';
  1043. }
  1044. $v_devmajor = '';
  1045. $v_devminor = '';
  1046. $v_prefix = '';
  1047. $v_binary_data_first = pack("a100a8a8a8a12A12",
  1048. $p_filename, $v_perms, $v_uid, $v_gid,
  1049. $v_size, $v_mtime);
  1050. $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
  1051. $p_type, $v_linkname, $v_magic,
  1052. $v_version, $v_uname, $v_gname,
  1053. $v_devmajor, $v_devminor, $v_prefix, '');
  1054. // ----- Calculate the checksum
  1055. $v_checksum = 0;
  1056. // ..... First part of the header
  1057. for ($i=0; $i<148; $i++)
  1058. $v_checksum += ord(substr($v_binary_data_first,$i,1));
  1059. // ..... Ignore the checksum value and replace it by ' ' (space)
  1060. for ($i=148; $i<156; $i++)
  1061. $v_checksum += ord(' ');
  1062. // ..... Last part of the header
  1063. for ($i=156, $j=0; $i<512; $i++, $j++)
  1064. $v_checksum += ord(substr($v_binary_data_last,$j,1));
  1065. // ----- Write the first 148 bytes of the header in the archive
  1066. $this->_writeBlock($v_binary_data_first, 148);
  1067. // ----- Write the calculated checksum
  1068. $v_checksum = sprintf("%06s ", DecOct($v_checksum));
  1069. $v_binary_data = pack("a8", $v_checksum);
  1070. $this->_writeBlock($v_binary_data, 8);
  1071. // ----- Write the last 356 bytes of the header in the archive
  1072. $this->_writeBlock($v_binary_data_last, 356);
  1073. return true;
  1074. }
  1075. // }}}
  1076. // {{{ _writeLongHeader()
  1077. function _writeLongHeader($p_filename)
  1078. {
  1079. $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
  1080. $v_typeflag = 'L';
  1081. $v_linkname = '';
  1082. $v_magic = '';
  1083. $v_version = '';
  1084. $v_uname = '';
  1085. $v_gname = '';
  1086. $v_devmajor = '';
  1087. $v_devminor = '';
  1088. $v_prefix = '';
  1089. $v_binary_data_first = pack("a100a8a8a8a12a12",
  1090. '././@LongLink', 0, 0, 0, $v_size, 0);
  1091. $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
  1092. $v_typeflag, $v_linkname, $v_magic,
  1093. $v_version, $v_uname, $v_gname,
  1094. $v_devmajor, $v_devminor, $v_prefix, '');
  1095. // ----- Calculate the checksum
  1096. $v_checksum = 0;
  1097. // ..... First part of the header
  1098. for ($i=0; $i<148; $i++)
  1099. $v_checksum += ord(substr($v_binary_data_first,$i,1));
  1100. // ..... Ignore the checksum value and replace it by ' ' (space)
  1101. for ($i=148; $i<156; $i++)
  1102. $v_checksum += ord(' ');
  1103. // ..... Last part of the header
  1104. for ($i=156, $j=0; $i<512; $i++, $j++)
  1105. $v_checksum += ord(substr($v_binary_data_last,$j,1));
  1106. // ----- Write the first 148 bytes of the header in the archive
  1107. $this->_writeBlock($v_binary_data_first, 148);
  1108. // ----- Write the calculated checksum
  1109. $v_checksum = sprintf("%06s ", DecOct($v_checksum));
  1110. $v_binary_data = pack("a8", $v_checksum);
  1111. $this->_writeBlock($v_binary_data, 8);
  1112. // ----- Write the last 356 bytes of the header in the archive
  1113. $this->_writeBlock($v_binary_data_last, 356);
  1114. // ----- Write the filename as content of the block
  1115. $i=0;
  1116. while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
  1117. $v_binary_data = pack("a512", "$v_buffer");
  1118. $this->_writeBlock($v_binary_data);
  1119. }
  1120. return true;
  1121. }
  1122. // }}}
  1123. // {{{ _readHeader()
  1124. function _readHeader($v_binary_data, &$v_header)
  1125. {
  1126. if (strlen($v_binary_data)==0) {
  1127. $v_header['filename'] = '';
  1128. return true;
  1129. }
  1130. if (strlen($v_binary_data) != 512) {
  1131. $v_header['filename'] = '';
  1132. $this->_error('Invalid block size : '.strlen($v_binary_data));
  1133. return false;
  1134. }
  1135. if (!is_array($v_header)) {
  1136. $v_header = array();
  1137. }
  1138. // ----- Calculate the checksum
  1139. $v_checksum = 0;
  1140. // ..... First part of the header
  1141. for ($i=0; $i<148; $i++)
  1142. $v_checksum+=ord(substr($v_binary_data,$i,1));
  1143. // ..... Ignore the checksum value and replace it by ' ' (space)
  1144. for ($i=148; $i<156; $i++)
  1145. $v_checksum += ord(' ');
  1146. // ..... Last part of the header
  1147. for ($i=156; $i<512; $i++)
  1148. $v_checksum+=ord(substr($v_binary_data,$i,1));
  1149. $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
  1150. ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
  1151. ."a32uname/a32gname/a8devmajor/a8devminor",
  1152. $v_binary_data);
  1153. // ----- Extract the checksum
  1154. $v_header['checksum'] = OctDec(trim($v_data['checksum']));
  1155. if ($v_header['checksum'] != $v_checksum) {
  1156. $v_header['filename'] = '';
  1157. // ----- Look for last block (empty block)
  1158. if (($v_checksum == 256) && ($v_header['checksum'] == 0))
  1159. return true;
  1160. $this->_error('Invalid checksum for file "'.$v_data['filename']
  1161. .'" : '.$v_checksum.' calculated, '
  1162. .$v_header['checksum'].' expected');
  1163. return false;
  1164. }
  1165. // ----- Extract the properties
  1166. $v_header['filename'] = $v_data['filename'];
  1167. if ($this->_maliciousFilename($v_header['filename'])) {
  1168. $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
  1169. '" will not install in desired directory tree');
  1170. return false;
  1171. }
  1172. $v_header['mode'] = OctDec(trim($v_data['mode']));
  1173. $v_header['uid'] = OctDec(trim($v_data['uid']));
  1174. $v_header['gid'] = OctDec(trim($v_data['gid']));
  1175. $v_header['size'] = OctDec(trim($v_data['size']));
  1176. $v_header['mtime'] = OctDec(trim($v_data['mtime']));
  1177. if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
  1178. $v_header['size'] = 0;
  1179. }
  1180. $v_header['link'] = trim($v_data['link']);
  1181. /* ----- All these fields are removed form the header because
  1182. they do not carry interesting info
  1183. $v_header[magic] = trim($v_data[magic]);
  1184. $v_header[version] = trim($v_data[version]);
  1185. $v_header[uname] = trim($v_data[uname]);
  1186. $v_header[gname] = trim($v_data[gname]);
  1187. $v_header[devmajor] = trim($v_data[devmajor]);
  1188. $v_header[devminor] = trim($v_data[devminor]);
  1189. */
  1190. return true;
  1191. }
  1192. // }}}
  1193. // {{{ _maliciousFilename()
  1194. /**
  1195. * Detect and report a malicious file name
  1196. *
  1197. * @param string $file
  1198. * @return bool
  1199. * @access private
  1200. */
  1201. function _maliciousFilename($file)
  1202. {
  1203. if (strpos($file, '/../') !== false) {
  1204. return true;
  1205. }
  1206. if (strpos($file, '../') === 0) {
  1207. return true;
  1208. }
  1209. return false;
  1210. }
  1211. // }}}
  1212. // {{{ _readLongHeader()
  1213. function _readLongHeader(&$v_header)
  1214. {
  1215. $v_filename = '';
  1216. $n = floor($v_header['size']/512);
  1217. for ($i=0; $i<$n; $i++) {
  1218. $v_content = $this->_readBlock();
  1219. $v_filename .= $v_content;
  1220. }
  1221. if (($v_header['size'] % 512) != 0) {
  1222. $v_content = $this->_readBlock();
  1223. $v_filename .= trim($v_content);
  1224. }
  1225. // ----- Read the next header
  1226. $v_binary_data = $this->_readBlock();
  1227. if (!$this->_readHeader($v_binary_data, $v_header))
  1228. return false;
  1229. $v_filename = trim($v_filename);
  1230. $v_header['filename'] = $v_filename;
  1231. if ($this->_maliciousFilename($v_filename)) {
  1232. $this->_error('Malicious .tar detected, file "' . $v_filename .
  1233. '" will not install in desired directory tree');
  1234. return false;
  1235. }
  1236. return true;
  1237. }
  1238. // }}}
  1239. // {{{ _extractInString()
  1240. /**
  1241. * This method extract from the archive one file identified by $p_filename.
  1242. * The return value is a string with the file content, or NULL on error.
  1243. * @param string $p_filename The path of the file to extract in a string.
  1244. * @return a string with the file content or NULL.
  1245. * @access private
  1246. */
  1247. function _extractInString($p_filename)
  1248. {
  1249. $v_result_str = "";
  1250. While (strlen($v_binary_data = $this->_readBlock()) != 0)
  1251. {
  1252. if (!$this->_readHeader($v_binary_data, $v_header))
  1253. return NULL;
  1254. if ($v_header['filename'] == '')
  1255. continue;
  1256. // ----- Look for long filename
  1257. if ($v_header['typeflag'] == 'L') {
  1258. if (!$this->_readLongHeader($v_header))
  1259. return NULL;
  1260. }
  1261. if ($v_header['filename'] == $p_filename) {
  1262. if ($v_header['typeflag'] == "5") {
  1263. $this->_error('Unable to extract in string a directory '
  1264. .'entry {'.$v_header['filename'].'}');
  1265. return NULL;
  1266. } else {
  1267. $n = floor($v_header['size']/512);
  1268. for ($i=0; $i<$n; $i++) {
  1269. $v_result_str .= $this->_readBlock();
  1270. }
  1271. if (($v_header['size'] % 512) != 0) {
  1272. $v_content = $this->_readBlock();
  1273. $v_result_str .= substr($v_content, 0,
  1274. ($v_header['size'] % 512));
  1275. }
  1276. return $v_result_str;
  1277. }
  1278. } else {
  1279. $this->_jumpBlock(ceil(($v_header['size']/512)));
  1280. }
  1281. }
  1282. return NULL;
  1283. }
  1284. // }}}
  1285. // {{{ _extractList()
  1286. function _extractList($p_path, &$p_list_detail, $p_mode,
  1287. $p_file_list, $p_remove_path)
  1288. {
  1289. $v_result=true;
  1290. $v_nb = 0;
  1291. $v_extract_all = true;
  1292. $v_listing = false;
  1293. $p_path = $this->_translateWinPath($p_path, false);
  1294. if ($p_path == '' || (substr($p_path, 0, 1) != '/'
  1295. && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
  1296. $p_path = "./".$p_path;
  1297. }
  1298. $p_remove_path = $this->_translateWinPath($p_remove_path);
  1299. // ----- Look for path to remove format (should end by /)
  1300. if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
  1301. $p_remove_path .= '/';
  1302. $p_remove_path_size = strlen($p_remove_path);
  1303. switch ($p_mode) {
  1304. case "complete" :
  1305. $v_extract_all = TRUE;
  1306. $v_listing = FALSE;
  1307. break;
  1308. case "partial" :
  1309. $v_extract_all = FALSE;
  1310. $v_listing = FALSE;
  1311. break;
  1312. case "list" :
  1313. $v_extract_all = FALSE;
  1314. $v_listing = TRUE;
  1315. break;
  1316. default :
  1317. $this->_error('Invalid extract mode ('.$p_mode.')');
  1318. return false;
  1319. }
  1320. clearstatcache();
  1321. while (strlen($v_binary_data = $this->_readBlock()) != 0)
  1322. {
  1323. $v_extract_file = FALSE;
  1324. $v_extraction_stopped = 0;
  1325. if (!$this->_readHeader($v_binary_data, $v_header))
  1326. return false;
  1327. if ($v_header['filename'] == '') {
  1328. continue;
  1329. }
  1330. // ----- Look for long filename
  1331. if ($v_header['typeflag'] == 'L') {
  1332. if (!$this->_readLongHeader($v_header))
  1333. return false;
  1334. }
  1335. if ((!$v_extract_all) && (is_array($p_file_list))) {
  1336. // ----- By default no unzip if the file is not found
  1337. $v_extract_file = false;
  1338. for ($i=0; $i<sizeof($p_file_list); $i++) {
  1339. // ----- Look if it is a directory
  1340. if (substr($p_file_list[$i], -1) == '/') {
  1341. // ----- Look if the directory is in the filename path
  1342. if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
  1343. && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
  1344. == $p_file_list[$i])) {
  1345. $v_extract_file = TRUE;
  1346. break;
  1347. }
  1348. }
  1349. // ----- It is a file, so compare the file names
  1350. elseif ($p_file_list[$i] == $v_header['filename']) {
  1351. $v_extract_file = TRUE;
  1352. break;
  1353. }
  1354. }
  1355. } else {
  1356. $v_extract_file = TRUE;
  1357. }
  1358. // ----- Look if this file need to be extracted
  1359. if (($v_extract_file) && (!$v_listing))
  1360. {
  1361. if (($p_remove_path != '')
  1362. && (substr($v_header['filename'], 0, $p_remove_path_size)
  1363. == $p_remove_path))
  1364. $v_header['filename'] = substr($v_header['filename'],
  1365. $p_remove_path_size);
  1366. if (($p_path != './') && ($p_path != '/')) {
  1367. while (substr($p_path, -1) == '/')
  1368. $p_path = substr($p_path, 0, strlen($p_path)-1);
  1369. if (substr($v_header['filename'], 0, 1) == '/')
  1370. $v_header['filename'] = $p_path.$v_header['filename'];
  1371. else
  1372. $v_header['filename'] = $p_path.'/'.$v_header['filename'];
  1373. }
  1374. if (file_exists($v_header['filename'])) {
  1375. if ( (@is_dir($v_header['filename']))
  1376. && ($v_header['typeflag'] == '')) {
  1377. $this->_error('File '.$v_header['filename']
  1378. .' already exists as a directory');
  1379. return false;
  1380. }
  1381. if ( ($this->_isArchive($v_header['filename']))
  1382. && ($v_header['typeflag'] == "5")) {
  1383. $this->_error('Directory '.$v_header['filename']
  1384. .' already exists as a file');
  1385. return false;
  1386. }
  1387. if (!is_writeable($v_header['filename'])) {
  1388. $this->_error('File '.$v_header['filename']
  1389. .' already exists and is write protected');
  1390. return false;
  1391. }
  1392. if (filemtime($v_header['filename']) > $v_header['mtime']) {
  1393. // To be completed : An error or silent no replace ?
  1394. }
  1395. }
  1396. // ----- Check the directory availability and create it if necessary
  1397. elseif (($v_result
  1398. = $this->_dirCheck(($v_header['typeflag'] == "5"
  1399. ?$v_header['filename']
  1400. :dirname($v_header['filename'])))) != 1) {
  1401. $this->_error('Unable to create path for '.$v_header['filename']);
  1402. return false;
  1403. }
  1404. if ($v_extract_file) {
  1405. if ($v_header['typeflag'] == "5") {
  1406. if (!@file_exists($v_header['filename'])) {
  1407. if (!@mkdir($v_header['filename'], 0777)) {
  1408. $this->_error('Unable to create directory {'
  1409. .$v_header['filename'].'}');
  1410. return false;
  1411. }
  1412. }
  1413. } elseif ($v_header['typeflag'] == "2") {
  1414. if (@file_exists($v_header['filename'])) {
  1415. @unlink($v_header['filename']);
  1416. }
  1417. if (!@symlink($v_header['link'], $v_header['filename'])) {
  1418. $this->_error('Unable to extract symbolic link {'
  1419. .$v_header['filename'].'}');
  1420. return false;
  1421. }
  1422. } else {
  1423. if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
  1424. $this->_error('Error while opening {'.$v_header['filename']
  1425. .'} in write binary mode');
  1426. return false;
  1427. } else {
  1428. $n = floor($v_header['size']/512);
  1429. for ($i=0; $i<$n; $i++) {
  1430. $v_content = $this->_readBlock();
  1431. fwrite($v_dest_file, $v_content, 512);
  1432. }
  1433. if (($v_header['size'] % 512) != 0) {
  1434. $v_content = $this->_readBlock();
  1435. fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
  1436. }
  1437. @fclose($v_dest_file);
  1438. // ----- Change the file mode, mtime
  1439. @touch($v_header['filename'], $v_header['mtime']);
  1440. if ($v_header['mode'] & 0111) {
  1441. // make file executable, obey umask
  1442. $mode = fileperms($v_header['filename']) | (~umask() & 0111);
  1443. @chmod($v_header['filename'], $mode);
  1444. }
  1445. }
  1446. // ----- Check the file size
  1447. clearstatcache();
  1448. if (filesize($v_header['filename']) != $v_header['size']) {
  1449. $this->_error('Extracted file '.$v_header['filename']
  1450. .' does not have the correct file size \''
  1451. .filesize($v_header['filename'])
  1452. .'\' ('.$v_header['size']
  1453. .' expected). Archive may be corrupted.');
  1454. return false;
  1455. }
  1456. }
  1457. } else {
  1458. $this->_jumpBlock(ceil(($v_header['size']/512)));
  1459. }
  1460. } else {
  1461. $this->_jumpBlock(ceil(($v_header['size']/512)));
  1462. }
  1463. /* TBC : Seems to be unused ...
  1464. if ($this->_compress)
  1465. $v_end_of_file = @gzeof($this->_file);
  1466. else
  1467. $v_end_of_file = @feof($this->_file);
  1468. */
  1469. if ($v_listing || $v_extract_file || $v_extraction_stopped) {
  1470. // ----- Log extracted files
  1471. if (($v_file_dir = dirname($v_header['filename']))
  1472. == $v_header['filename'])
  1473. $v_file_dir = '';
  1474. if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
  1475. $v_file_dir = '/';
  1476. $p_list_detail[$v_nb++] = $v_header;
  1477. if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
  1478. return true;
  1479. }
  1480. }
  1481. }
  1482. return true;
  1483. }
  1484. // }}}
  1485. // {{{ _openAppend()
  1486. function _openAppend()
  1487. {
  1488. if (filesize($this->_tarname) == 0)
  1489. return $this->_openWrite();
  1490. if ($this->_compress) {
  1491. $this->_close();
  1492. if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
  1493. $this->_error('Error while renaming \''.$this->_tarname
  1494. .'\' to temporary file \''.$this->_tarname
  1495. .'.tmp\'');
  1496. return false;
  1497. }
  1498. if ($this->_compress_type == 'gz')
  1499. $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
  1500. elseif ($this->_compress_type == 'bz2')
  1501. $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
  1502. if ($v_temp_tar == 0) {
  1503. $this->_error('Unable to open file \''.$this->_tarname
  1504. .'.tmp\' in binary read mode');
  1505. @rename($this->_tarname.".tmp", $this->_tarname);
  1506. return false;
  1507. }
  1508. if (!$this->_openWrite()) {
  1509. @rename($this->_tarname.".tmp", $this->_tarname);
  1510. return false;
  1511. }
  1512. if ($this->_compress_type == 'gz') {
  1513. while (!@gzeof($v_temp_tar)) {
  1514. $v_buffer = @gzread($v_temp_tar, 512);
  1515. if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
  1516. // do not copy end blocks, we will re-make them
  1517. // after appending
  1518. continue;
  1519. }
  1520. $v_binary_data = pack("a512", $v_buffer);
  1521. $this->_writeBlock($v_binary_data);
  1522. }
  1523. @gzclose($v_temp_tar);
  1524. }
  1525. elseif ($this->_compress_type == 'bz2') {
  1526. while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
  1527. if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
  1528. continue;
  1529. }
  1530. $v_binary_data = pack("a512", $v_buffer);
  1531. $this->_writeBlock($v_binary_data);
  1532. }
  1533. @bzclose($v_temp_tar);
  1534. }
  1535. if (!@unlink($this->_tarname.".tmp")) {
  1536. $this->_error('Error while deleting temporary file \''
  1537. .$this->_tarname.'.tmp\'');
  1538. }
  1539. } else {
  1540. // ----- For not compressed tar, just add files before the last
  1541. // one or two 512 bytes block
  1542. if (!$this->_openReadWrite())
  1543. return false;
  1544. clearstatcache();
  1545. $v_size = filesize($this->_tarname);
  1546. // We might have zero, one or two end blocks.
  1547. // The standard is two, but we should try to handle
  1548. // other cases.
  1549. fseek($this->_file, $v_size - 1024);
  1550. if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
  1551. fseek($this->_file, $v_size - 1024);
  1552. }
  1553. elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
  1554. fseek($this->_file, $v_size - 512);
  1555. }
  1556. }
  1557. return true;
  1558. }
  1559. // }}}
  1560. // {{{ _append()
  1561. function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
  1562. {
  1563. if (!$this->_openAppend())
  1564. return false;
  1565. if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
  1566. $this->_writeFooter();
  1567. $this->_close();
  1568. return true;
  1569. }
  1570. // }}}
  1571. // {{{ _dirCheck()
  1572. /**
  1573. * Check if a directory exists and create it (including parent
  1574. * dirs) if not.
  1575. *
  1576. * @param string $p_dir directory to check
  1577. *
  1578. * @return bool TRUE if the directory exists or was created
  1579. */
  1580. function _dirCheck($p_dir)
  1581. {
  1582. clearstatcache();
  1583. if ((@is_dir($p_dir)) || ($p_dir == ''))
  1584. return true;
  1585. $p_parent_dir = dirname($p_dir);
  1586. if (($p_parent_dir != $p_dir) &&
  1587. ($p_parent_dir != '') &&
  1588. (!$this->_dirCheck($p_parent_dir)))
  1589. return false;
  1590. if (!@mkdir($p_dir, 0777)) {
  1591. $this->_error("Unable to create directory '$p_dir'");
  1592. return false;
  1593. }
  1594. return true;
  1595. }
  1596. // }}}
  1597. // {{{ _pathReduction()
  1598. /**
  1599. * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
  1600. * rand emove double slashes.
  1601. *
  1602. * @param string $p_dir path to reduce
  1603. *
  1604. * @return string reduced path
  1605. *
  1606. * @access private
  1607. *
  1608. */
  1609. function _pathReduction($p_dir)
  1610. {
  1611. $v_result = '';
  1612. // ----- Look for not empty path
  1613. if ($p_dir != '') {
  1614. // ----- Explode path by directory names
  1615. $v_list = explode('/', $p_dir);
  1616. // ----- Study directories from last to first
  1617. for ($i=sizeof($v_list)-1; $i>=0; $i--) {
  1618. // ----- Look for current path
  1619. if ($v_list[$i] == ".") {
  1620. // ----- Ignore this directory
  1621. // Should be the first $i=0, but no check is done
  1622. }
  1623. else if ($v_list[$i] == "..") {
  1624. // ----- Ignore it and ignore the $i-1
  1625. $i--;
  1626. }
  1627. else if ( ($v_list[$i] == '')
  1628. && ($i!=(sizeof($v_list)-1))
  1629. && ($i!=0)) {
  1630. // ----- Ignore only the double '//' in path,
  1631. // but not the first and last /
  1632. } else {
  1633. $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
  1634. .$v_result:'');
  1635. }
  1636. }
  1637. }
  1638. $v_result = strtr($v_result, '\\', '/');
  1639. return $v_result;
  1640. }
  1641. // }}}
  1642. // {{{ _translateWinPath()
  1643. function _translateWinPath($p_path, $p_remove_disk_letter=true)
  1644. {
  1645. if (defined('OS_WINDOWS') && OS_WINDOWS) {
  1646. // ----- Look for potential disk letter
  1647. if ( ($p_remove_disk_letter)
  1648. && (($v_position = strpos($p_path, ':')) != false)) {
  1649. $p_path = substr($p_path, $v_position+1);
  1650. }
  1651. // ----- Change potential windows directory separator
  1652. if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
  1653. $p_path = strtr($p_path, '\\', '/');
  1654. }
  1655. }
  1656. return $p_path;
  1657. }
  1658. // }}}
  1659. }
  1660. ?>