*/ class Mage_Archive_Tar extends Mage_Archive_Abstract implements Mage_Archive_Interface { /** * Constant is used for parse tar's header. */ const FORMAT_PARSE_HEADER = 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer'; /** * Keep file or directory for packing. * * @var string */ protected $_currentFile; /** * Keep path to file or directory for packing. * * @var mixed */ protected $_currentPath; /** * Skip first level parent directory. Example: * use test/fip.php instead test/test/fip.php; * * @var mixed */ protected $_skipRoot; /** * Set option that define ability skip first catalog level. * * @param mixed $skipRoot * @return Mage_Archive_Tar */ protected function _setSkipRoot($skipRoot) { $this->_skipRoot = $skipRoot; return $this; } /** * Set file which is packing. * * @param string $file * @return Mage_Archive_Tar */ protected function _setCurrentFile($file) { $this->_currentFile = $file .((is_dir($file) && substr($file, -1)!=DS)?DS:''); return $this; } /** * Retrieve file which is packing. * * @return string */ protected function _getCurrentFile() { return $this->_currentFile; } /** * Set path to file which is packing. * * @param string $path * @return Mage_Archive_Tar */ protected function _setCurrentPath($path) { if ($this->_skipRoot && is_dir($path)) { $this->_currentPath = $path.(substr($path, -1)!=DS?DS:''); } else { $this->_currentPath = dirname($path) . DS; } return $this; } /** * Retrieve path to file which is packing. * * @return string */ protected function _getCurrentPath() { return $this->_currentPath; } /** * Walk through directory and add to tar file or directory. * Result is packed string on TAR format. * * @param boolean $skipRoot * @return string */ protected function _packToTar($skipRoot=false) { $file = $this->_getCurrentFile(); $header = ''; $data = ''; if (!$skipRoot) { $header = $this->_composeHeader(); $data = $this->_readFile($file); $data = str_pad($data, floor(((is_dir($file) ? 0 : filesize($file)) + 512 - 1) / 512) * 512, "\0"); } $sub = ''; if (is_dir($file)) { $treeDir = scandir($file); if (empty($treeDir)) { throw new Mage_Exception('Can\'t scan dir: ' . $file); } array_shift($treeDir); /* remove './'*/ array_shift($treeDir); /* remove '../'*/ foreach ($treeDir as $item) { $sub .= $this->_setCurrentFile($file.$item)->_packToTar(false); } } $tarData = $header . $data . $sub; $tarData = str_pad($tarData, floor((strlen($tarData) - 1) / 1536) * 1536, "\0"); return $tarData; } /** * Compose header for current file in TAR format. * If length of file's name greater 100 characters, * method breaks header to two pieces. First conatins * header and data with long name. Second contain only header. * * @param boolean $long * @return string */ protected function _composeHeader($long = false) { $file = $this->_getCurrentFile(); $path = $this->_getCurrentPath(); $infoFile = stat($file); $nameFile = str_replace($path, '', $file); $nameFile = str_replace('\\', '/', $nameFile); $packedHeader = ''; $longHeader = ''; if (!$long && strlen($nameFile)>100) { $longHeader = $this->_composeHeader(true); $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0"); } $header = array(); $header['100-name'] = $long?'././@LongLink':substr($nameFile, 0, 100); $header['8-mode'] = $long?' ':str_pad(substr(sprintf("%07o", $infoFile['mode']),-4), 6, '0', STR_PAD_LEFT); $header['8-uid'] = $long || $infoFile['uid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['uid']); $header['8-gid'] = $long || $infoFile['gid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['gid']); $header['12-size'] = $long?sprintf("%011o", strlen($nameFile)):sprintf("%011o", is_dir($file) ? 0 : filesize($file)); $header['12-mtime'] = $long?'00000000000':sprintf("%011o", $infoFile['mtime']); $header['8-check'] = sprintf('% 8s', ''); $header['1-type'] = $long?'L':(is_link($file) ? 2 : is_dir ($file) ? 5 : 0); $header['100-symlink'] = is_link($file) == 2 ? readlink($item) : ''; $header['6-magic'] = 'ustar '; $header['2-version'] = ' '; $a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($file)):array('name'=>''); $header['32-uname'] = $a['name']; $a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($file)):array('name'=>''); $header['32-gname'] = $a['name']; $header['8-devmajor'] = ''; $header['8-devminor'] = ''; $header['155-prefix'] = ''; $header['12-closer'] = ''; $packedHeader = ''; foreach ($header as $key=>$element) { $length = explode('-', $key); $packedHeader .= pack('a' . $length[0], $element); } $checksum = 0; for ($i = 0; $i < 512; $i++) { $checksum += ord(substr($packedHeader, $i, 1)); } $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum)."\0", 148, 8); return $longHeader . $packedHeader; } /** * Read TAR string from file, and unpacked it. * Create files and directories information about discribed * in the string. * * @param string $destination path to file is unpacked * @return array list of files */ protected function _unpackCurrentTar($destination) { $file = $this->_getCurrentFile(); $pointer = fopen($file, 'r'); if (empty($pointer)) { throw new Mage_Exception('Can\'t open file: ' . $file); } $list = array(); while (!feof($pointer)) { $header = $this->_parseHeader($pointer); if ($header) { $currentFile = $destination . $header['name']; if ($header['type']=='5' && @mkdir($currentFile, 0777, true)) { $list[] = $currentFile . DS; } elseif (in_array($header['type'], array("0",chr(0), ''))) { $dirname = dirname($currentFile); if(!file_exists($dirname)) { @mkdir($dirname, 0777, true); } $this->_writeFile($currentFile, $header['data']); $list[] = $currentFile; } } } fclose($pointer); return $list; } /** * Get header from TAR string and unpacked it by format. * * @param resource $pointer * @return string */ protected function _parseHeader(&$pointer) { $firstLine = fread($pointer, 512); if (strlen($firstLine)<512){ return false; } $fmt = self::FORMAT_PARSE_HEADER; $header = unpack ($fmt, $firstLine); $header['mode']=$header['mode']+0; $header['uid']=octdec($header['uid']); $header['gid']=octdec($header['gid']); $header['size']=octdec($header['size']); $header['mtime']=octdec($header['mtime']); $header['checksum']=octdec($header['checksum']); if ($header['type'] == "5") { $header['size'] = 0; } $checksum = 0; $firstLine = substr_replace($firstLine, ' ', 148, 8); for ($i = 0; $i < 512; $i++) { $checksum += ord(substr($firstLine, $i, 1)); } $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); $checksumOk = $header['checksum'] == $checksum; if (isset($header['name']) && $checksumOk) { if ($header['name'] == '././@LongLink' && $header['type'] == 'L') { $realName = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); $headerMain = $this->_parseHeader($pointer); $headerMain['name'] = $realName; return $headerMain; } else { if ($header['size']>0) { $header['data'] = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); } else { $header['data'] = ''; } return $header; } } return false; } /** * Pack file to TAR (Tape Archiver). * * @param string $source * @param string $destination * @param boolean $skipRoot * @return string */ public function pack($source, $destination, $skipRoot=false) { $this->_setSkipRoot($skipRoot); $source = realpath($source); $tarData = $this->_setCurrentPath($source) ->_setCurrentFile($source) ->_packToTar($skipRoot); $this->_writeFile($destination, $tarData); return $destination; } /** * Unpack file from TAR (Tape Archiver). * * @param string $source * @param string $destination * @return string */ public function unpack($source, $destination) { $tempFile = $destination . DS . '~tmp-'.microtime(true).'.tar'; $data = $this->_readFile($source); $this->_writeFile($tempFile, $data); $this->_setCurrentFile($tempFile) ->_setCurrentPath($tempFile) ->_unpackCurrentTar($destination); unlink($tempFile); return $destination; } /** * Extract one file from TAR (Tape Archiver). * * @param string $file * @param string $source * @param string $destination * @return string */ public function extract($file, $source, $destination) { $pointer = fopen($source, 'r'); if (empty($pointer)) { throw new Mage_Exception('Can\'t open file: '.$source); } $list = array(); $extractedFile = ''; while (!feof($pointer)) { $header = $this->_parseHeader($pointer); if ($header['name'] == $file) { $extractedFile = $destination . basename($header['name']); $this->_writeFile($extractedFile, $header['data']); break; } } fclose($pointer); return $extractedFile; } }