PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/downloader/lib/Mage/Archive/Tar.php

https://bitbucket.org/acidel/buykoala
PHP | 372 lines | 267 code | 11 blank | 94 comment | 7 complexity | 50035cad8c613e4f49de2a9e94f33799 MD5 | raw file
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Archive
  23. * @copyright Copyright (c) 2011 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Class to work with tar archives
  28. *
  29. * @category Mage
  30. * @package Mage_Archive
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Archive_Tar extends Mage_Archive_Abstract implements Mage_Archive_Interface
  34. {
  35. /**
  36. * Constant is used for parse tar's header.
  37. */
  38. const FORMAT_PARSE_HEADER = 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer';
  39. /**
  40. * Keep file or directory for packing.
  41. *
  42. * @var string
  43. */
  44. protected $_currentFile;
  45. /**
  46. * Keep path to file or directory for packing.
  47. *
  48. * @var mixed
  49. */
  50. protected $_currentPath;
  51. /**
  52. * Skip first level parent directory. Example:
  53. * use test/fip.php instead test/test/fip.php;
  54. *
  55. * @var mixed
  56. */
  57. protected $_skipRoot;
  58. /**
  59. * Set option that define ability skip first catalog level.
  60. *
  61. * @param mixed $skipRoot
  62. * @return Mage_Archive_Tar
  63. */
  64. protected function _setSkipRoot($skipRoot)
  65. {
  66. $this->_skipRoot = $skipRoot;
  67. return $this;
  68. }
  69. /**
  70. * Set file which is packing.
  71. *
  72. * @param string $file
  73. * @return Mage_Archive_Tar
  74. */
  75. protected function _setCurrentFile($file)
  76. {
  77. $this->_currentFile = $file .((is_dir($file) && substr($file, -1)!=DS)?DS:'');
  78. return $this;
  79. }
  80. /**
  81. * Retrieve file which is packing.
  82. *
  83. * @return string
  84. */
  85. protected function _getCurrentFile()
  86. {
  87. return $this->_currentFile;
  88. }
  89. /**
  90. * Set path to file which is packing.
  91. *
  92. * @param string $path
  93. * @return Mage_Archive_Tar
  94. */
  95. protected function _setCurrentPath($path)
  96. {
  97. if ($this->_skipRoot && is_dir($path)) {
  98. $this->_currentPath = $path.(substr($path, -1)!=DS?DS:'');
  99. } else {
  100. $this->_currentPath = dirname($path) . DS;
  101. }
  102. return $this;
  103. }
  104. /**
  105. * Retrieve path to file which is packing.
  106. *
  107. * @return string
  108. */
  109. protected function _getCurrentPath()
  110. {
  111. return $this->_currentPath;
  112. }
  113. /**
  114. * Walk through directory and add to tar file or directory.
  115. * Result is packed string on TAR format.
  116. *
  117. * @param boolean $skipRoot
  118. * @return string
  119. */
  120. protected function _packToTar($skipRoot=false)
  121. {
  122. $file = $this->_getCurrentFile();
  123. $header = '';
  124. $data = '';
  125. if (!$skipRoot) {
  126. $header = $this->_composeHeader();
  127. $data = $this->_readFile($file);
  128. $data = str_pad($data, floor(((is_dir($file) ? 0 : filesize($file)) + 512 - 1) / 512) * 512, "\0");
  129. }
  130. $sub = '';
  131. if (is_dir($file)) {
  132. $treeDir = scandir($file);
  133. if (empty($treeDir)) {
  134. throw new Mage_Exception('Can\'t scan dir: ' . $file);
  135. }
  136. array_shift($treeDir); /* remove './'*/
  137. array_shift($treeDir); /* remove '../'*/
  138. foreach ($treeDir as $item) {
  139. $sub .= $this->_setCurrentFile($file.$item)->_packToTar(false);
  140. }
  141. }
  142. $tarData = $header . $data . $sub;
  143. $tarData = str_pad($tarData, floor((strlen($tarData) - 1) / 1536) * 1536, "\0");
  144. return $tarData;
  145. }
  146. /**
  147. * Compose header for current file in TAR format.
  148. * If length of file's name greater 100 characters,
  149. * method breaks header to two pieces. First conatins
  150. * header and data with long name. Second contain only header.
  151. *
  152. * @param boolean $long
  153. * @return string
  154. */
  155. protected function _composeHeader($long = false)
  156. {
  157. $file = $this->_getCurrentFile();
  158. $path = $this->_getCurrentPath();
  159. $infoFile = stat($file);
  160. $nameFile = str_replace($path, '', $file);
  161. $nameFile = str_replace('\\', '/', $nameFile);
  162. $packedHeader = '';
  163. $longHeader = '';
  164. if (!$long && strlen($nameFile)>100) {
  165. $longHeader = $this->_composeHeader(true);
  166. $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0");
  167. }
  168. $header = array();
  169. $header['100-name'] = $long?'././@LongLink':substr($nameFile, 0, 100);
  170. $header['8-mode'] = $long?' ':str_pad(substr(sprintf("%07o", $infoFile['mode']),-4), 6, '0', STR_PAD_LEFT);
  171. $header['8-uid'] = $long || $infoFile['uid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['uid']);
  172. $header['8-gid'] = $long || $infoFile['gid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['gid']);
  173. $header['12-size'] = $long?sprintf("%011o", strlen($nameFile)):sprintf("%011o", is_dir($file) ? 0 : filesize($file));
  174. $header['12-mtime'] = $long?'00000000000':sprintf("%011o", $infoFile['mtime']);
  175. $header['8-check'] = sprintf('% 8s', '');
  176. $header['1-type'] = $long?'L':(is_link($file) ? 2 : is_dir ($file) ? 5 : 0);
  177. $header['100-symlink'] = is_link($file) == 2 ? readlink($item) : '';
  178. $header['6-magic'] = 'ustar ';
  179. $header['2-version'] = ' ';
  180. $a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($file)):array('name'=>'');
  181. $header['32-uname'] = $a['name'];
  182. $a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($file)):array('name'=>'');
  183. $header['32-gname'] = $a['name'];
  184. $header['8-devmajor'] = '';
  185. $header['8-devminor'] = '';
  186. $header['155-prefix'] = '';
  187. $header['12-closer'] = '';
  188. $packedHeader = '';
  189. foreach ($header as $key=>$element) {
  190. $length = explode('-', $key);
  191. $packedHeader .= pack('a' . $length[0], $element);
  192. }
  193. $checksum = 0;
  194. for ($i = 0; $i < 512; $i++) {
  195. $checksum += ord(substr($packedHeader, $i, 1));
  196. }
  197. $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum)."\0", 148, 8);
  198. return $longHeader . $packedHeader;
  199. }
  200. /**
  201. * Read TAR string from file, and unpacked it.
  202. * Create files and directories information about discribed
  203. * in the string.
  204. *
  205. * @param string $destination path to file is unpacked
  206. * @return array list of files
  207. */
  208. protected function _unpackCurrentTar($destination)
  209. {
  210. $file = $this->_getCurrentFile();
  211. $pointer = fopen($file, 'r');
  212. if (empty($pointer)) {
  213. throw new Mage_Exception('Can\'t open file: ' . $file);
  214. }
  215. $list = array();
  216. while (!feof($pointer)) {
  217. $header = $this->_parseHeader($pointer);
  218. if ($header) {
  219. $currentFile = $destination . $header['name'];
  220. if ($header['type']=='5' && @mkdir($currentFile, 0777, true)) {
  221. $list[] = $currentFile . DS;
  222. } elseif (in_array($header['type'], array("0",chr(0), ''))) {
  223. $dirname = dirname($currentFile);
  224. if(!file_exists($dirname)) {
  225. @mkdir($dirname, 0777, true);
  226. }
  227. $this->_writeFile($currentFile, $header['data']);
  228. $list[] = $currentFile;
  229. }
  230. }
  231. }
  232. fclose($pointer);
  233. return $list;
  234. }
  235. /**
  236. * Get header from TAR string and unpacked it by format.
  237. *
  238. * @param resource $pointer
  239. * @return string
  240. */
  241. protected function _parseHeader(&$pointer)
  242. {
  243. $firstLine = fread($pointer, 512);
  244. if (strlen($firstLine)<512){
  245. return false;
  246. }
  247. $fmt = self::FORMAT_PARSE_HEADER;
  248. $header = unpack ($fmt, $firstLine);
  249. $header['mode']=$header['mode']+0;
  250. $header['uid']=octdec($header['uid']);
  251. $header['gid']=octdec($header['gid']);
  252. $header['size']=octdec($header['size']);
  253. $header['mtime']=octdec($header['mtime']);
  254. $header['checksum']=octdec($header['checksum']);
  255. if ($header['type'] == "5") {
  256. $header['size'] = 0;
  257. }
  258. $checksum = 0;
  259. $firstLine = substr_replace($firstLine, ' ', 148, 8);
  260. for ($i = 0; $i < 512; $i++) {
  261. $checksum += ord(substr($firstLine, $i, 1));
  262. }
  263. $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5));
  264. $checksumOk = $header['checksum'] == $checksum;
  265. if (isset($header['name']) && $checksumOk) {
  266. if ($header['name'] == '././@LongLink' && $header['type'] == 'L') {
  267. $realName = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']);
  268. $headerMain = $this->_parseHeader($pointer);
  269. $headerMain['name'] = $realName;
  270. return $headerMain;
  271. } else {
  272. if ($header['size']>0) {
  273. $header['data'] = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']);
  274. } else {
  275. $header['data'] = '';
  276. }
  277. return $header;
  278. }
  279. }
  280. return false;
  281. }
  282. /**
  283. * Pack file to TAR (Tape Archiver).
  284. *
  285. * @param string $source
  286. * @param string $destination
  287. * @param boolean $skipRoot
  288. * @return string
  289. */
  290. public function pack($source, $destination, $skipRoot=false)
  291. {
  292. $this->_setSkipRoot($skipRoot);
  293. $source = realpath($source);
  294. $tarData = $this->_setCurrentPath($source)
  295. ->_setCurrentFile($source)
  296. ->_packToTar($skipRoot);
  297. $this->_writeFile($destination, $tarData);
  298. return $destination;
  299. }
  300. /**
  301. * Unpack file from TAR (Tape Archiver).
  302. *
  303. * @param string $source
  304. * @param string $destination
  305. * @return string
  306. */
  307. public function unpack($source, $destination)
  308. {
  309. $tempFile = $destination . DS . '~tmp-'.microtime(true).'.tar';
  310. $data = $this->_readFile($source);
  311. $this->_writeFile($tempFile, $data);
  312. $this->_setCurrentFile($tempFile)
  313. ->_setCurrentPath($tempFile)
  314. ->_unpackCurrentTar($destination);
  315. unlink($tempFile);
  316. return $destination;
  317. }
  318. /**
  319. * Extract one file from TAR (Tape Archiver).
  320. *
  321. * @param string $file
  322. * @param string $source
  323. * @param string $destination
  324. * @return string
  325. */
  326. public function extract($file, $source, $destination)
  327. {
  328. $pointer = fopen($source, 'r');
  329. if (empty($pointer)) {
  330. throw new Mage_Exception('Can\'t open file: '.$source);
  331. }
  332. $list = array();
  333. $extractedFile = '';
  334. while (!feof($pointer)) {
  335. $header = $this->_parseHeader($pointer);
  336. if ($header['name'] == $file) {
  337. $extractedFile = $destination . basename($header['name']);
  338. $this->_writeFile($extractedFile, $header['data']);
  339. break;
  340. }
  341. }
  342. fclose($pointer);
  343. return $extractedFile;
  344. }
  345. }