Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TTarFileExtractor class file** @author Vincent Blavet <vincent@phpconcept.net>* @copyright Copyright © 1997-2003 The PHP Group* @version $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $* @package System.IO*//* vim: set ts=4 sw=4: */// +----------------------------------------------------------------------+// | PHP Version 4 |// +----------------------------------------------------------------------+// | Copyright (c) 1997-2003 The PHP Group |// +----------------------------------------------------------------------+// | This source file is subject to version 3.0 of the PHP license, |// | that is bundled with this package in the file LICENSE, and is |// | available through the world-wide-web at the following url: |// | http://www.php.net/license/3_0.txt. |// | If you did not receive a copy of the PHP license and are unable to |// | obtain it through the world-wide-web, please send a note to |// | license@php.net so we can mail you a copy immediately. |// +----------------------------------------------------------------------+// | Author: Vincent Blavet <vincent@phpconcept.net> |// +----------------------------------------------------------------------+//// $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $/*** TTarFileExtractor class** @author Vincent Blavet <vincent@phpconcept.net>* @version $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $* @package System.IO* @since 3.0*/class TTarFileExtractor{/*** @var string Name of the Tar*/private $_tarname='';/*** @var file descriptor*/private $_file=0;/*** @var string Local Tar name of a remote Tar (http:// or ftp://)*/private $_temp_tarname='';/*** Archive_Tar Class constructor. This flavour of the constructor only* declare a new Archive_Tar object, identifying it by the name of the* tar file.** @param string $p_tarname The name of the tar archive to create* @access public*/public function __construct($p_tarname){$this->_tarname = $p_tarname;}public function __destruct(){$this->_close();// ----- Look for a local copy to deleteif ($this->_temp_tarname != '')@unlink($this->_temp_tarname);}public function extract($p_path=''){return $this->extractModify($p_path, '');}/*** This method extract all the content of the archive in the directory* indicated by $p_path. When relevant the memorized path of the* files/dir can be modified by removing the $p_remove_path path at the* beginning of the file/dir path.* While extracting a file, if the directory path does not exists it is* created.* While extracting a file, if the file already exists it is replaced* without looking for last modification date.* While extracting a file, if the file already exists and is write* protected, the extraction is aborted.* While extracting a file, if a directory with the same name already* exists, the extraction is aborted.* While extracting a directory, if a file with the same name already* exists, the extraction is aborted.* While extracting a file/directory if the destination directory exist* and is write protected, or does not exist but can not be created,* the extraction is aborted.* If after extraction an extracted file does not show the correct* stored file size, the extraction is aborted.* When the extraction is aborted, a PEAR error text is set and false* is returned. However the result can be a partial extraction that may* need to be manually cleaned.** @param string $p_path The path of the directory where the* files/dir need to by extracted.* @param string $p_remove_path Part of the memorized path that can be* removed if present at the beginning of* the file/dir path.* @return boolean true on success, false on error.* @access public*/protected function extractModify($p_path, $p_remove_path){$v_result = true;$v_list_detail = array();if ($v_result = $this->_openRead()) {$v_result = $this->_extractList($p_path, $v_list_detail,"complete", 0, $p_remove_path);$this->_close();}return $v_result;}protected function _error($p_message){throw new Exception($p_message);}private function _isArchive($p_filename=null){if ($p_filename == null) {$p_filename = $this->_tarname;}clearstatcache();return @is_file($p_filename);}private function _openRead(){if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {// ----- Look if a local copy need to be doneif ($this->_temp_tarname == '') {$this->_temp_tarname = uniqid('tar').'.tmp';if (!$v_file_from = @fopen($this->_tarname, 'rb')) {$this->_error('Unable to open in read mode \''.$this->_tarname.'\'');$this->_temp_tarname = '';return false;}if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {$this->_error('Unable to open in write mode \''.$this->_temp_tarname.'\'');$this->_temp_tarname = '';return false;}while ($v_data = @fread($v_file_from, 1024))@fwrite($v_file_to, $v_data);@fclose($v_file_from);@fclose($v_file_to);}// ----- File to open if the local copy$v_filename = $this->_temp_tarname;} else// ----- File to open if the normal Tar file$v_filename = $this->_tarname;$this->_file = @fopen($v_filename, "rb");if ($this->_file == 0) {$this->_error('Unable to open in read mode \''.$v_filename.'\'');return false;}return true;}private function _close(){//if (isset($this->_file)) {if (is_resource($this->_file)){@fclose($this->_file);$this->_file = 0;}// ----- Look if a local copy need to be erase// Note that it might be interesting to keep the url for a time : ToDoif ($this->_temp_tarname != '') {@unlink($this->_temp_tarname);$this->_temp_tarname = '';}return true;}private function _cleanFile(){$this->_close();// ----- Look for a local copyif ($this->_temp_tarname != '') {// ----- Remove the local copy but not the remote tarname@unlink($this->_temp_tarname);$this->_temp_tarname = '';} else {// ----- Remove the local tarname file@unlink($this->_tarname);}$this->_tarname = '';return true;}private function _readBlock(){$v_block = null;if (is_resource($this->_file)) {$v_block = @fread($this->_file, 512);}return $v_block;}private function _jumpBlock($p_len=null){if (is_resource($this->_file)) {if ($p_len === null)$p_len = 1;@fseek($this->_file, @ftell($this->_file)+($p_len*512));}return true;}private function _readHeader($v_binary_data, &$v_header){if (strlen($v_binary_data)==0) {$v_header['filename'] = '';return true;}if (strlen($v_binary_data) != 512) {$v_header['filename'] = '';$this->_error('Invalid block size : '.strlen($v_binary_data));return false;}// ----- Calculate the checksum$v_checksum = 0;// ..... First part of the headerfor ($i=0; $i<148; $i++)$v_checksum+=ord(substr($v_binary_data,$i,1));// ..... Ignore the checksum value and replace it by ' ' (space)for ($i=148; $i<156; $i++)$v_checksum += ord(' ');// ..... Last part of the headerfor ($i=156; $i<512; $i++)$v_checksum+=ord(substr($v_binary_data,$i,1));$v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"."a8checksum/a1typeflag/a100link/a6magic/a2version/"."a32uname/a32gname/a8devmajor/a8devminor",$v_binary_data);// ----- Extract the checksum$v_header['checksum'] = OctDec(trim($v_data['checksum']));if ($v_header['checksum'] != $v_checksum) {$v_header['filename'] = '';// ----- Look for last block (empty block)if (($v_checksum == 256) && ($v_header['checksum'] == 0))return true;$this->_error('Invalid checksum for file "'.$v_data['filename'].'" : '.$v_checksum.' calculated, '.$v_header['checksum'].' expected');return false;}// ----- Extract the properties$v_header['filename'] = trim($v_data['filename']);$v_header['mode'] = OctDec(trim($v_data['mode']));$v_header['uid'] = OctDec(trim($v_data['uid']));$v_header['gid'] = OctDec(trim($v_data['gid']));$v_header['size'] = OctDec(trim($v_data['size']));$v_header['mtime'] = OctDec(trim($v_data['mtime']));if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {$v_header['size'] = 0;}return true;}private function _readLongHeader(&$v_header){$v_filename = '';$n = floor($v_header['size']/512);for ($i=0; $i<$n; $i++) {$v_content = $this->_readBlock();$v_filename .= $v_content;}if (($v_header['size'] % 512) != 0) {$v_content = $this->_readBlock();$v_filename .= $v_content;}// ----- Read the next header$v_binary_data = $this->_readBlock();if (!$this->_readHeader($v_binary_data, $v_header))return false;$v_header['filename'] = $v_filename;return true;}protected function _extractList($p_path, &$p_list_detail, $p_mode,$p_file_list, $p_remove_path){$v_result=true;$v_nb = 0;$v_extract_all = true;$v_listing = false;$p_path = $this->_translateWinPath($p_path, false);if ($p_path == '' || (substr($p_path, 0, 1) != '/'&& substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {$p_path = "./".$p_path;}$p_remove_path = $this->_translateWinPath($p_remove_path);// ----- Look for path to remove format (should end by /)if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))$p_remove_path .= '/';$p_remove_path_size = strlen($p_remove_path);switch ($p_mode) {case "complete" :$v_extract_all = true;$v_listing = false;break;case "partial" :$v_extract_all = false;$v_listing = false;break;case "list" :$v_extract_all = false;$v_listing = true;break;default :$this->_error('Invalid extract mode ('.$p_mode.')');return false;}clearstatcache();while (strlen($v_binary_data = $this->_readBlock()) != 0){$v_extract_file = false;$v_extraction_stopped = 0;if (!$this->_readHeader($v_binary_data, $v_header))return false;if ($v_header['filename'] == '') {continue;}// ----- Look for long filenameif ($v_header['typeflag'] == 'L') {if (!$this->_readLongHeader($v_header))return false;}if ((!$v_extract_all) && (is_array($p_file_list))) {// ----- By default no unzip if the file is not found$v_extract_file = false;for ($i=0; $i<sizeof($p_file_list); $i++) {// ----- Look if it is a directoryif (substr($p_file_list[$i], -1) == '/') {// ----- Look if the directory is in the filename pathif ((strlen($v_header['filename']) > strlen($p_file_list[$i]))&& (substr($v_header['filename'], 0, strlen($p_file_list[$i]))== $p_file_list[$i])) {$v_extract_file = true;break;}}// ----- It is a file, so compare the file nameselseif ($p_file_list[$i] == $v_header['filename']) {$v_extract_file = true;break;}}} else {$v_extract_file = true;}// ----- Look if this file need to be extractedif (($v_extract_file) && (!$v_listing)){if (($p_remove_path != '')&& (substr($v_header['filename'], 0, $p_remove_path_size)== $p_remove_path))$v_header['filename'] = substr($v_header['filename'],$p_remove_path_size);if (($p_path != './') && ($p_path != '/')) {while (substr($p_path, -1) == '/')$p_path = substr($p_path, 0, strlen($p_path)-1);if (substr($v_header['filename'], 0, 1) == '/')$v_header['filename'] = $p_path.$v_header['filename'];else$v_header['filename'] = $p_path.'/'.$v_header['filename'];}if (file_exists($v_header['filename'])) {if ( (@is_dir($v_header['filename']))&& ($v_header['typeflag'] == '')) {$this->_error('File '.$v_header['filename'].' already exists as a directory');return false;}if ( ($this->_isArchive($v_header['filename']))&& ($v_header['typeflag'] == "5")) {$this->_error('Directory '.$v_header['filename'].' already exists as a file');return false;}if (!is_writeable($v_header['filename'])) {$this->_error('File '.$v_header['filename'].' already exists and is write protected');return false;}if (filemtime($v_header['filename']) > $v_header['mtime']) {// To be completed : An error or silent no replace ?}}// ----- Check the directory availability and create it if necessaryelseif (($v_result= $this->_dirCheck(($v_header['typeflag'] == "5"?$v_header['filename']:dirname($v_header['filename'])))) != 1) {$this->_error('Unable to create path for '.$v_header['filename']);return false;}if ($v_extract_file) {if ($v_header['typeflag'] == "5") {if (!@file_exists($v_header['filename'])) {if (!@mkdir($v_header['filename'], PRADO_CHMOD)) {$this->_error('Unable to create directory {'.$v_header['filename'].'}');return false;}chmod($v_header['filename'], PRADO_CHMOD);}} else {if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {$this->_error('Error while opening {'.$v_header['filename'].'} in write binary mode');return false;} else {$n = floor($v_header['size']/512);for ($i=0; $i<$n; $i++) {$v_content = $this->_readBlock();fwrite($v_dest_file, $v_content, 512);}if (($v_header['size'] % 512) != 0) {$v_content = $this->_readBlock();fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));}@fclose($v_dest_file);// ----- Change the file mode, mtime@touch($v_header['filename'], $v_header['mtime']);// To be completed//chmod($v_header[filename], DecOct($v_header[mode]));}// ----- Check the file sizeclearstatcache();if (filesize($v_header['filename']) != $v_header['size']) {$this->_error('Extracted file '.$v_header['filename'].' does not have the correct file size \''.filesize($v_header['filename']).'\' ('.$v_header['size'].' expected). Archive may be corrupted.');return false;}}} else {$this->_jumpBlock(ceil(($v_header['size']/512)));}} else {$this->_jumpBlock(ceil(($v_header['size']/512)));}/* TBC : Seems to be unused ...if ($this->_compress)$v_end_of_file = @gzeof($this->_file);else$v_end_of_file = @feof($this->_file);*/if ($v_listing || $v_extract_file || $v_extraction_stopped) {// ----- Log extracted filesif (($v_file_dir = dirname($v_header['filename']))== $v_header['filename'])$v_file_dir = '';if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))$v_file_dir = '/';$p_list_detail[$v_nb++] = $v_header;}}return true;}/*** Check if a directory exists and create it (including parent* dirs) if not.** @param string $p_dir directory to check** @return bool true if the directory exists or was created*/protected function _dirCheck($p_dir){if ((@is_dir($p_dir)) || ($p_dir == ''))return true;$p_parent_dir = dirname($p_dir);if (($p_parent_dir != $p_dir) &&($p_parent_dir != '') &&(!$this->_dirCheck($p_parent_dir)))return false;if (!@mkdir($p_dir, PRADO_CHMOD)) {$this->_error("Unable to create directory '$p_dir'");return false;}chmod($p_dir,PRADO_CHMOD);return true;}protected function _translateWinPath($p_path, $p_remove_disk_letter=true){if (substr(PHP_OS, 0, 3) == 'WIN') {// ----- Look for potential disk letterif ( ($p_remove_disk_letter)&& (($v_position = strpos($p_path, ':')) != false)) {$p_path = substr($p_path, $v_position+1);}// ----- Change potential windows directory separatorif ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {$p_path = strtr($p_path, '\\', '/');}}return $p_path;}}?>