PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/joomla/administrator/components/com_joomlapack/classes/engine/packer/zip.php

https://github.com/joomla-example/joomla-repo
PHP | 1142 lines | 793 code | 80 blank | 269 comment | 127 complexity | 5c99c76f3f4da045aa2a0f3c74d9da47 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package JoomlaPack
  4. * @copyright Copyright (C) 2006-2009 JoomlaPack Developers. All rights reserved.
  5. * @version $Id$
  6. * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
  7. * @since 1.2.1
  8. *
  9. * JoomlaPack is free software. This version may have been modified pursuant
  10. * to the GNU General Public License, and as distributed it includes or
  11. * is derivative of works licensed under the GNU General Public License or
  12. * other free or open source software licenses.
  13. *
  14. * ZIP Creation Module
  15. *
  16. * Creates a ZIP file based on the contents of a given file list.
  17. * Based upon the Compress library of the Horde Project [http://www.horde.org],
  18. * modified to fit JoomlaPack needs. This class is safe to serialize and deserialize
  19. * between subsequent calls.
  20. *
  21. * JoomlaPack modifications : eficiently read data from files, selective compression,
  22. * defensive memory management to avoid memory exhaustion errors, separated central
  23. * directory and data section creation, split archives, symlink storage
  24. *
  25. * ----------------------------------------------------------------------------
  26. *
  27. * Original code credits, from Horde library:
  28. *
  29. * The ZIP compression code is partially based on code from:
  30. * Eric Mueller <eric@themepark.com>
  31. * http://www.zend.com/codex.php?id=535&single=1
  32. *
  33. * Deins125 <webmaster@atlant.ru>
  34. * http://www.zend.com/codex.php?id=470&single=1
  35. *
  36. * The ZIP compression date code is partially based on code from
  37. * Peter Listiak <mlady@users.sourceforge.net>
  38. *
  39. * Copyright 2000-2006 Chuck Hagenbuch <chuck@horde.org>
  40. * Copyright 2002-2006 Michael Cochrane <mike@graftonhall.co.nz>
  41. * Copyright 2003-2006 Michael Slusarz <slusarz@horde.org>
  42. *
  43. * Additional Credits:
  44. *
  45. * Contains code from pclZip library [http://www.phpconcept.net/pclzip/index.en.php]
  46. *
  47. * Modifications for JoomlaPack:
  48. * Copyright 2007-2009 Nicholas K. Dionysopoulos <nikosdion@gmail.com>
  49. */
  50. defined('_JEXEC') or die('Restricted access');
  51. $config =& JoomlapackModelRegistry::getInstance();
  52. define("_JoomlapackPackerZIP_FORCE_FOPEN", $config->get('mnZIPForceOpen')); // Don't force use of fopen() to read uncompressed data in memory
  53. define("_JoomlapackPackerZIP_COMPRESSION_THRESHOLD", $config->get('mnZIPCompressionThreshold')); // Don't compress files over this size
  54. define("_JoomlapackPackerZIP_DIRECTORY_READ_CHUNK", $config->get('mnZIPDirReadChunk')); // How much data to read at once when finalizing ZIP archives
  55. class JoomlapackPackerZIP extends JoomlapackCUBEArchiver {
  56. /**
  57. * ZIP compression methods. JoomlaPack supports 0x0 (none) and 0x8 (deflated)
  58. *
  59. * @var array
  60. */
  61. var $_methods = array(
  62. 0x0 => 'None',
  63. 0x1 => 'Shrunk',
  64. 0x2 => 'Super Fast',
  65. 0x3 => 'Fast',
  66. 0x4 => 'Normal',
  67. 0x5 => 'Maximum',
  68. 0x6 => 'Imploded',
  69. 0x8 => 'Deflated'
  70. );
  71. /**
  72. * Beginning of central directory record.
  73. *
  74. * @var string
  75. */
  76. var $_ctrlDirHeader = "\x50\x4b\x01\x02";
  77. /**
  78. * End of central directory record.
  79. *
  80. * @var string
  81. */
  82. var $_ctrlDirEnd = "\x50\x4b\x05\x06";
  83. /**
  84. * Beginning of file contents.
  85. *
  86. * @var string
  87. */
  88. var $_fileHeader = "\x50\x4b\x03\x04";
  89. /**
  90. * The name of the temporary file holding the ZIP's Central Directory
  91. *
  92. * @var string
  93. */
  94. var $_ctrlDirFileName;
  95. /**
  96. * The name of the file holding the ZIP's data, which becomes the final archive
  97. *
  98. * @var string
  99. */
  100. var $_dataFileName;
  101. /**
  102. * The total number of files and directories stored in the ZIP archive
  103. *
  104. * @var integer
  105. */
  106. var $_totalFileEntries;
  107. /**
  108. * The chunk size for CRC32 calculations
  109. *
  110. * @var integer
  111. */
  112. var $JoomlapackPackerZIP_CHUNK_SIZE;
  113. // Variables for split ZIP
  114. /** @var bool Should I use Split ZIP? */
  115. var $_useSplitZIP = false;
  116. /** @var int Maximum fragment size, in bytes */
  117. var $_fragmentSize = 0;
  118. /** @var int Current fragment number */
  119. var $_currentFragment = 1;
  120. /** @var int Total number of fragments */
  121. var $_totalFragments = 1;
  122. /** @var string Archive full path without extension */
  123. var $_dataFileNameBase = '';
  124. // Variables for symlink target storage
  125. /** @var bool Should I store symlinks as such (no dereferencing?) */
  126. var $_symlink_store_target = false;
  127. // ------------------------------------------------------------------------
  128. // Implementation of abstract methods
  129. // ------------------------------------------------------------------------
  130. /**
  131. * Class constructor - initializes internal operating parameters
  132. *
  133. * @return JoomlapackPackerZIP The class instance
  134. */
  135. function JoomlapackPackerZIP()
  136. {
  137. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: New instance");
  138. // Get chunk override
  139. $registry =& JoomlapackModelRegistry::getInstance();
  140. if( $registry->get('mnArchiverChunk', 0) > 0 )
  141. {
  142. $this->JoomlapackPackerZIP_CHUNK_SIZE = JPPACK_CHUNK;
  143. }
  144. else
  145. {
  146. // Try to use as much memory as it's possible for CRC32 calculation
  147. $memLimit = ini_get("memory_limit");
  148. if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
  149. if ( ($memLimit == "") ) {
  150. // No memory limit, use 2Mb chunks (fairly large, right?)
  151. $this->JoomlapackPackerZIP_CHUNK_SIZE = 2097152;
  152. } elseif ( function_exists("memory_get_usage") ) {
  153. // PHP can report memory usage, see if there's enough available memory; Joomla! alone eats about 5-6Mb! This code is called on files <= 1Mb
  154. $memLimit = $this->_return_bytes( $memLimit );
  155. $availableRAM = $memLimit - memory_get_usage();
  156. if ($availableRAM <= 0) {
  157. // Some PHP implemenations also return the size of the httpd footprint!
  158. if ( ($memLimit - 6291456) > 0 ) {
  159. $this->JoomlapackPackerZIP_CHUNK_SIZE = $memLimit - 6291456;
  160. } else {
  161. $this->JoomlapackPackerZIP_CHUNK_SIZE = 2097152;
  162. }
  163. } else {
  164. $this->JoomlapackPackerZIP_CHUNK_SIZE = $availableRAM * 0.5;
  165. }
  166. } else {
  167. // PHP can't report memory usage, use a conservative 512Kb
  168. $this->JoomlapackPackerZIP_CHUNK_SIZE = 524288;
  169. }
  170. }
  171. // NEW 2.3: Should we enable Split ZIP feature?
  172. $fragmentsize = $registry->get('splitpartsize', 0);
  173. if($fragmentsize >= 65536)
  174. {
  175. // If the fragment size is AT LEAST 64Kb, enable Split ZIP
  176. $this->_useSplitZIP = true;
  177. $this->_fragmentSize = $fragmentsize;
  178. // Enable CUBE that we have a multipart archive
  179. $cube =& JoomlapackCUBE::getInstance();
  180. $cube->updateMultipart(1); // Indicate that we have at least 1 part
  181. }
  182. // NEW 2.3: Should I use Symlink Target Storage?
  183. $dereferencesymlinks = $registry->get('dereferencesymlinks', true);
  184. if(!$dereferencesymlinks)
  185. {
  186. // We are told not to dereference symlinks. Are we on Windows?
  187. if (function_exists('php_uname'))
  188. {
  189. $isWindows = stristr(php_uname(), 'windows');
  190. }
  191. else
  192. {
  193. $isWindows = (DS == '\\');
  194. }
  195. // If we are not on Windows, enable symlink target storage
  196. $this->_symlink_store_target = !$isWindows;
  197. }
  198. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "Chunk size for CRC is now " . $this->JoomlapackPackerZIP_CHUNK_SIZE . " bytes");
  199. }
  200. /**
  201. * Initialises the archiver class, creating the archive from an existent
  202. * installer's JPA archive.
  203. *
  204. * @param string $sourceJPAPath Absolute path to an installer's JPA archive
  205. * @param string $targetArchivePath Absolute path to the generated archive
  206. * @param array $options A named key array of options (optional). This is currently not supported
  207. * @access public
  208. */
  209. function initialize( $sourceJPAPath, $targetArchivePath, $options = array() )
  210. {
  211. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: initialize - archive $targetArchivePath");
  212. // Get names of temporary files
  213. $configuration =& JoomlapackModelRegistry::getInstance();
  214. $this->_ctrlDirFileName = tempnam( $configuration->getTemporaryDirectory(), 'jpzcd' );
  215. $this->_dataFileName = $targetArchivePath;
  216. // If we use splitting, initialize
  217. if($this->_useSplitZIP)
  218. {
  219. JoomlapackLogger::WriteLog(_JP_LOG_INFO, "JoomlapackPackerZIP :: Split ZIP creation enabled");
  220. $this->_dataFileNameBase = dirname($targetArchivePath).DS.basename($targetArchivePath,'.zip');
  221. $this->_dataFileName = $this->_dataFileNameBase.'.z01';
  222. }
  223. jimport('joomla.filesystem.file');
  224. $tempname = JFile::getName($this->_ctrlDirFileName);
  225. JoomlapackCUBETempfiles::registerTempFile($tempname);
  226. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: CntDir Tempfile = " . $this->_ctrlDirFileName);
  227. // Create temporary file
  228. if(!@touch( $this->_ctrlDirFileName ))
  229. {
  230. $this->setError(JText::_('CUBE_ZIPARCHIVER_CANTWRITETEMP'));
  231. return false;
  232. }
  233. // Try to kill the archive if it exists
  234. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: Killing old archive");
  235. $fp = fopen( $this->_dataFileName, "wb" );
  236. if (!($fp === false)) {
  237. ftruncate( $fp,0 );
  238. fclose( $fp );
  239. } else {
  240. @unlink( $this->_dataFileName );
  241. }
  242. if(!@touch( $this->_dataFileName ))
  243. {
  244. $this->setError(JText::_('CUBE_ZIPARCHIVER_CANTWRITEZIP'));
  245. return false;
  246. }
  247. // On split archives, include the "Split ZIP" header, for PKZIP 2.50+ compatibility
  248. if($this->_useSplitZIP)
  249. {
  250. file_put_contents($this->_dataFileName, "\x50\x4b\x07\x08");
  251. // Also update the statistics table that we are a multipart archive...
  252. $cube =& JoomlapackCUBE::getInstance();
  253. $cube->updateMultipart(1);
  254. }
  255. parent::initialize($sourceJPAPath, $targetArchivePath, $options);
  256. }
  257. /**
  258. * Creates the ZIP file out of its pieces.
  259. * Official ZIP file format: http://www.pkware.com/appnote.txt
  260. *
  261. * @return boolean TRUE on success, FALSE on failure
  262. */
  263. function finalize()
  264. {
  265. // 1. Get size of central directory
  266. clearstatcache();
  267. $cdSize = @filesize( $this->_ctrlDirFileName );
  268. // 2. Append Central Directory to data file and remove the CD temp file afterwards
  269. $dataFP = fopen( $this->_dataFileName, "ab" );
  270. $cdFP = fopen( $this->_ctrlDirFileName, "rb" );
  271. if( $dataFP === false )
  272. {
  273. $this->setError('Could not open ZIP data file '.$this->_dataFileName.' for reading');
  274. return false;
  275. }
  276. if ( $cdFP === false ) {
  277. // Already glued, return
  278. fclose( $dataFP );
  279. return false;
  280. }
  281. if(!$this->_useSplitZIP)
  282. {
  283. while( !feof($cdFP) )
  284. {
  285. $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
  286. $this->_fwrite( $dataFP, $chunk );
  287. if($this->getError()) return;
  288. }
  289. unset( $chunk );
  290. fclose( $cdFP );
  291. }
  292. else
  293. // Special considerations for Split ZIP
  294. {
  295. // Calcuate size of Central Directory + EOCD records
  296. $total_cd_eocd_size = $cdSize + 22 + strlen($this->_comment);
  297. // Free space on the part
  298. clearstatcache();
  299. $current_part_size = @filesize($this->_dataFileName);
  300. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  301. if( ($free_space < $total_cd_eocd_size) && ($total_cd_eocd_size > 65536) )
  302. {
  303. // Not enough space on archive for CD + EOCD, will go on separate part
  304. // Create new final part
  305. if(!$this->_createNewPart(true))
  306. {
  307. // Die if we couldn't create the new part
  308. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  309. return false;
  310. }
  311. else
  312. {
  313. // Close the old data file
  314. fclose($dataFP);
  315. // Open data file for output
  316. $dataFP = @fopen( $this->_dataFileName, "ab");
  317. if ($dataFP === false)
  318. {
  319. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  320. return false;
  321. }
  322. // Write the CD record
  323. while( !feof($cdFP) )
  324. {
  325. $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
  326. $this->_fwrite( $dataFP, $chunk );
  327. if($this->getError()) return;
  328. }
  329. unset( $chunk );
  330. fclose( $cdFP );
  331. }
  332. }
  333. else
  334. {
  335. // Glue the CD + EOCD on the same part if they fit, or anyway if they are less than 64Kb.
  336. // NOTE: WE *MUST NOT* CREATE FRAGMENTS SMALLER THAN 64Kb!!!!
  337. while( !feof($cdFP) )
  338. {
  339. $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
  340. $this->_fwrite( $dataFP, $chunk );
  341. if($this->getError()) return;
  342. }
  343. unset( $chunk );
  344. fclose( $cdFP );
  345. }
  346. }
  347. JoomlapackCUBETempfiles::unregisterAndDeleteTempFile($this->_ctrlDirFileName);
  348. // 3. Write the rest of headers to the end of the ZIP file
  349. fclose( $dataFP );
  350. clearstatcache();
  351. $dataSize = @filesize( $this->_dataFileName ) - $cdSize;
  352. $dataFP = fopen( $this->_dataFileName, "ab" );
  353. if($dataFP === false)
  354. {
  355. $this->setError('Could not open '.$this->_dataFileName.' for append');
  356. return false;
  357. }
  358. $this->_fwrite( $dataFP, $this->_ctrlDirEnd );
  359. if($this->getError()) return;
  360. if($this->_useSplitZIP)
  361. {
  362. // Split ZIP files, enter relevant disk number information
  363. $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Number of this disk. */
  364. $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Disk with central directory start. */
  365. }
  366. else
  367. {
  368. // Non-split ZIP files, the disk numbers MUST be 0
  369. $this->_fwrite( $dataFP, pack('V', 0) );
  370. }
  371. $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries "on this disk". */
  372. $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries overall. */
  373. $this->_fwrite( $dataFP, pack('V', $cdSize) ); /* Size of central directory. */
  374. $this->_fwrite( $dataFP, pack('V', $dataSize) ); /* Offset to start of central dir. */
  375. $sizeOfComment = strlen($this->_comment);
  376. // 2.0.b2 -- Write a ZIP file comment
  377. $this->_fwrite( $dataFP, pack('v', $sizeOfComment) ); /* ZIP file comment length. */
  378. $this->_fwrite( $dataFP, $this->_comment );
  379. fclose( $dataFP );
  380. //sleep(2);
  381. // If Split ZIP and there is no .zip file, rename the last fragment to .ZIP
  382. if($this->_useSplitZIP)
  383. {
  384. $extension = substr($this->_dataFileName, -3);
  385. if($extension != '.zip')
  386. {
  387. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, 'Renaming last ZIP part to .ZIP extension');
  388. $newName = $this->_dataFileNameBase.'.zip';
  389. if(!@rename($this->_dataFileName, $newName))
  390. {
  391. $this->setError('Could not rename last ZIP part to .ZIP extension.');
  392. return false;
  393. }
  394. $this->_dataFileName = $newName;
  395. }
  396. }
  397. // If Split ZIP and only one fragment, change the signature
  398. if($this->_useSplitZIP && ($this->_totalFragments == 1) )
  399. {
  400. $fp = fopen($this->_dataFileName, 'r+b');
  401. $this->_fwrite($fp, "\x50\x4b\x30\x30" );
  402. }
  403. // @todo If Split ZIP, update CUBE with total number of fragments
  404. if( function_exists('chmod') )
  405. {
  406. @chmod($this->_dataFileName, 0755);
  407. }
  408. return true;
  409. }
  410. /**
  411. * The most basic file transaction: add a single entry (file or directory) to
  412. * the archive.
  413. *
  414. * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
  415. * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
  416. * @param string $targetName The (relative) file name under which to store the file in the archive
  417. * @return True on success, false otherwise
  418. * @since 1.2.1
  419. * @access protected
  420. * @abstract
  421. */
  422. function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
  423. {
  424. static $configuration;
  425. $cube =& JoomlapackCUBE::getInstance();
  426. // Note down the starting disk number for Split ZIP archives
  427. if($this->_useSplitZIP)
  428. {
  429. $starting_disk_number_for_this_file = $this->_currentFragment - 1;
  430. }
  431. else
  432. {
  433. $starting_disk_number_for_this_file = 0;
  434. }
  435. if(!$configuration)
  436. {
  437. jpimport('models.registry', true);
  438. $configuration =& JoomlapackModelRegistry::getInstance();
  439. }
  440. // See if it's a directory
  441. $isDir = $isVirtual ? false : is_dir($sourceNameOrData);
  442. // See if it's a symlink (w/out dereference)
  443. $isSymlink = false;
  444. if($this->_symlink_store_target)
  445. {
  446. $isSymlink = is_link($sourceNameOrData);
  447. }
  448. // Get real size before compression
  449. if($isVirtual)
  450. {
  451. $fileSize = strlen($sourceNameOrData);
  452. } else {
  453. if($isSymlink)
  454. {
  455. $fileSize = strlen( @readlink($sourceNameOrData) );
  456. }
  457. else
  458. {
  459. $fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
  460. }
  461. }
  462. // Get last modification time to store in archive
  463. $ftime = $isVirtual ? time() : @filemtime( $sourceNameOrData );
  464. // Decide if we will compress
  465. if ($isDir || $isSymlink) {
  466. $compressionMethod = 0; // don't compress directories...
  467. } else {
  468. // Do we have plenty of memory left?
  469. $memLimit = ini_get("memory_limit");
  470. if (($memLimit == "") || ($fileSize >= _JoomlapackPackerZIP_COMPRESSION_THRESHOLD)) {
  471. // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
  472. $compressionMethod = ($fileSize <= _JoomlapackPackerZIP_COMPRESSION_THRESHOLD) ? 8 : 0;
  473. } elseif ( function_exists("memory_get_usage") ) {
  474. // PHP can report memory usage, see if there's enough available memory; Joomla! alone eats about 5-6Mb! This code is called on files <= 1Mb
  475. $memLimit = $this->_return_bytes( $memLimit );
  476. $availableRAM = $memLimit - memory_get_usage();
  477. $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 8 : 0;
  478. } else {
  479. // PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
  480. $compressionMethod = ($fileSize <= 524288) ? 8 : 0;;
  481. }
  482. }
  483. $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;
  484. $storedName = $targetName;
  485. if($isVirtual) JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, ' Virtual add:'.$storedName.' ('.$fileSize.') - '.$compressionMethod);
  486. /* "Local file header" segment. */
  487. $unc_len = &$fileSize; // File size
  488. if (!$isDir) {
  489. // Get CRC for regular files, not dirs
  490. if($isVirtual)
  491. {
  492. $crc = crc32($sourceNameOrData);
  493. }
  494. else
  495. {
  496. $crcCalculator = new CRC32CalcClass;
  497. $crc = $crcCalculator->crc32_file( $sourceNameOrData, $this->JoomlapackPackerZIP_CHUNK_SIZE ); // This is supposed to be the fast way to calculate CRC32 of a (large) file.
  498. unset( $crcCalculator );
  499. // If the file was unreadable, $crc will be false, so we skip the file
  500. if ($crc === false) {
  501. $cube->addWarning( 'Could not calculate CRC32 for '.$sourceNameOrData);
  502. return false;
  503. }
  504. }
  505. } else if($isSymlink) {
  506. $crc = crc32( @readlink($sourceNameOrData) );
  507. } else {
  508. // Dummy CRC for dirs
  509. $crc = 0;
  510. $storedName .= "/";
  511. $unc_len = 0;
  512. }
  513. // If we have to compress, read the data in memory and compress it
  514. if ($compressionMethod == 8) {
  515. // Get uncompressed data
  516. if( $isVirtual )
  517. {
  518. $udata =& $sourceNameOrData;
  519. }
  520. else
  521. {
  522. if( function_exists("file_get_contents") && (_JoomlapackPackerZIP_FORCE_FOPEN == false) ) {
  523. $udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
  524. } else {
  525. // Argh... the hard way!
  526. $udatafp = @fopen( $sourceNameOrData, "rb" );
  527. if( !($udatafp === false) ) {
  528. $udata = "";
  529. while( !feof($udatafp) ) {
  530. if($configuration->get("enableMySQLKeepalive", false))
  531. {
  532. list($usec, $sec) = explode(" ", microtime());
  533. $endTime = ((float)$usec + (float)$sec);
  534. if($endTime - $this->startTime > 0.5)
  535. {
  536. $this->startTime = $endTime;
  537. JoomlapackCUBETables::WriteVar('dummy', 1);
  538. }
  539. }
  540. $udata .= fread($udatafp, JPPACK_CHUNK);
  541. }
  542. fclose( $udatafp );
  543. } else {
  544. $udata = false;
  545. }
  546. }
  547. }
  548. if ($udata === FALSE) {
  549. // Unreadable file, skip it. Normally, we should have exited on CRC code above
  550. $cube->addWarning( JText::sprintf('CUBE_WARN_UNREADABLEFILE', $sourceNameOrData));
  551. return false;
  552. } else {
  553. // Proceed with compression
  554. $zdata = @gzcompress($udata);
  555. if ($zdata === false) {
  556. // If compression fails, let it behave like no compression was available
  557. $c_len = &$unc_len;
  558. $compressionMethod = 0;
  559. } else {
  560. unset( $udata );
  561. $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
  562. $c_len = strlen($zdata);
  563. }
  564. }
  565. } else {
  566. $c_len = $unc_len;
  567. }
  568. /* Get the hex time. */
  569. $dtime = dechex($this->_unix2DosTime($ftime));
  570. if( strlen($dtime) < 8 ) $dtime = "00000000";
  571. $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) .
  572. chr(hexdec($dtime[4] . $dtime[5])) .
  573. chr(hexdec($dtime[2] . $dtime[3])) .
  574. chr(hexdec($dtime[0] . $dtime[1]));
  575. // Get current data file size
  576. clearstatcache();
  577. $old_offset = @filesize( $this->_dataFileName );
  578. // If it's a split ZIP file, we've got to make sure that the header can fit in the part
  579. if($this->_useSplitZIP)
  580. {
  581. // Get header size, taking into account any extra header necessary
  582. $header_size = 30 + strlen($storedName);
  583. // Compare to free part space
  584. clearstatcache();
  585. $current_part_size = @filesize($this->_dataFileName);
  586. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  587. if($free_space <= $header_size)
  588. {
  589. // Not enough space on current part, create new part
  590. if(!$this->_createNewPart())
  591. {
  592. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  593. return false;
  594. }
  595. }
  596. }
  597. // Open data file for output
  598. $fp = @fopen( $this->_dataFileName, "ab");
  599. if ($fp === false)
  600. {
  601. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  602. return false;
  603. }
  604. $this->_fwrite( $fp, $this->_fileHeader ); /* Begin creating the ZIP data. */
  605. if(!$isSymlink)
  606. {
  607. $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract. */
  608. }
  609. else
  610. {
  611. $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract. */
  612. }
  613. $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag. */
  614. $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
  615. $this->_fwrite( $fp, $hexdtime ); /* Last modification time/date. */
  616. $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
  617. $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
  618. $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
  619. $this->_fwrite( $fp, pack('v', strlen($storedName)) ); /* Length of filename. */
  620. $this->_fwrite( $fp, pack('v', 0) ); /* Extra field length. */
  621. $this->_fwrite( $fp, $storedName ); /* File name. */
  622. /* "File data" segment. */
  623. if ($compressionMethod == 8) {
  624. // Just dump the compressed data
  625. if(!$this->_useSplitZIP)
  626. {
  627. $this->_fwrite( $fp, $zdata );
  628. if($this->getError()) return;
  629. }
  630. else
  631. {
  632. // Split ZIP. Check if we need to split the part in the middle of the data.
  633. clearstatcache();
  634. $current_part_size = @filesize($this->_dataFileName);
  635. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  636. if($free_space >= strlen($zdata) )
  637. {
  638. // Write in one part
  639. $this->_fwrite( $fp, $zdata );
  640. if($this->getError()) return;
  641. }
  642. else
  643. {
  644. $bytes_left = strlen($zdata);
  645. while($bytes_left > 0)
  646. {
  647. clearstatcache();
  648. $current_part_size = @filesize($this->_dataFileName);
  649. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  650. // Split between parts - Write a part
  651. $this->_fwrite( $fp, $zdata, $free_space );
  652. if($this->getError()) return;
  653. // Get the rest of the data
  654. $bytes_left = strlen($zdata) - $free_space;
  655. if($bytes_left > 0)
  656. {
  657. // Create new part
  658. if(!$this->_createNewPart())
  659. {
  660. // Die if we couldn't create the new part
  661. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  662. return false;
  663. }
  664. else
  665. {
  666. // Close the old data file
  667. fclose($fp);
  668. // Open data file for output
  669. $fp = @fopen( $this->_dataFileName, "ab");
  670. if ($fp === false)
  671. {
  672. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  673. return false;
  674. }
  675. }
  676. $zdata = substr($zdata, -$bytes_left);
  677. }
  678. }
  679. }
  680. }
  681. unset( $zdata );
  682. } elseif ( !($isDir || $isSymlink) ) {
  683. // Virtual file, just write the data!
  684. if( $isVirtual )
  685. {
  686. // Just dump the data
  687. if(!$this->_useSplitZIP)
  688. {
  689. $this->_fwrite( $fp, $sourceNameOrData );
  690. if($this->getError()) return;
  691. }
  692. else
  693. {
  694. // Split ZIP. Check if we need to split the part in the middle of the data.
  695. clearstatcache();
  696. $current_part_size = @filesize($this->_dataFileName);
  697. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  698. if($free_space >= strlen($sourceNameOrData) )
  699. {
  700. // Write in one part
  701. $this->_fwrite( $fp, $sourceNameOrData );
  702. if($this->getError()) return;
  703. }
  704. else
  705. {
  706. $bytes_left = strlen($sourceNameOrData);
  707. while($bytes_left > 0)
  708. {
  709. clearstatcache();
  710. $current_part_size = @filesize($this->_dataFileName);
  711. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  712. // Split between parts - Write first part
  713. $this->_fwrite( $fp, $sourceNameOrData, $free_space );
  714. if($this->getError()) return;
  715. // Get the rest of the data
  716. $rest_size = strlen($sourceNameOrData) - $free_space;
  717. if($rest_size > 0)
  718. {
  719. // Create new part if required
  720. if(!$this->_createNewPart())
  721. {
  722. // Die if we couldn't create the new part
  723. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  724. return false;
  725. }
  726. else
  727. {
  728. // Close the old data file
  729. fclose($fp);
  730. // Open data file for output
  731. $fp = @fopen( $this->_dataFileName, "ab");
  732. if ($fp === false)
  733. {
  734. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  735. return false;
  736. }
  737. }
  738. // Get the rest of the compressed data
  739. $zdata = substr($sourceNameOrData, -$rest_size);
  740. }
  741. $bytes_left = $rest_size;
  742. }
  743. }
  744. }
  745. }
  746. else
  747. {
  748. // Copy the file contents, ignore directories
  749. $zdatafp = @fopen( $sourceNameOrData, "rb" );
  750. if( $zdatafp === FALSE )
  751. {
  752. $cube->addWarning( JText::sprintf('CUBE_WARN_UNREADABLEFILE', $sourceNameOrData));
  753. return false;
  754. }
  755. else
  756. {
  757. if(!$this->_useSplitZIP)
  758. {
  759. // For non Split ZIP, just dump the file very fast
  760. while( !feof($zdatafp) ) {
  761. $zdata = fread($zdatafp, JPPACK_CHUNK);
  762. $this->_fwrite( $fp, $zdata );
  763. if($this->getError()) return;
  764. }
  765. }
  766. else
  767. {
  768. // Split ZIP - Do we have enough space to host the whole file?
  769. clearstatcache();
  770. $current_part_size = @filesize($this->_dataFileName);
  771. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  772. if($free_space >= $unc_len)
  773. {
  774. // Yes, it will fit inside this part, do quick copy
  775. while( !feof($zdatafp) ) {
  776. $zdata = fread($zdatafp, JPPACK_CHUNK);
  777. $this->_fwrite( $fp, $zdata );
  778. if($this->getError()) return;
  779. }
  780. }
  781. else
  782. {
  783. // No, we'll have to split between parts. We'll loop until we run
  784. // out of space.
  785. $bytes_left = $unc_len;
  786. while( ($bytes_left > 0) && (!feof($zdatafp)) )
  787. {
  788. // No, we'll have to split between parts. Write the first part
  789. // Find optimal chunk size
  790. clearstatcache();
  791. $current_part_size = @filesize($this->_dataFileName);
  792. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  793. $chunk_size_primary = min(JPPACK_CHUNK, $free_space);
  794. if($chunk_size_primary <= 0) $chunk_size_primary = max(JPPACK_CHUNK, $free_space);
  795. // Calculate if we have to read some more data (smaller chunk size)
  796. // and how many times we must read w/ the primary chunk size
  797. $chunk_size_secondary = $free_space % $chunk_size_primary;
  798. $loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
  799. // Read and write with the primary chunk size
  800. for( $i = 1; $i <= $loop_times; $i++ )
  801. {
  802. $zdata = fread($zdatafp, $chunk_size_primary);
  803. $this->_fwrite( $fp, $zdata );
  804. if($this->getError()) return;
  805. }
  806. // Read and write w/ secondary chunk size, if non-zero
  807. if($chunk_size_secondary > 0)
  808. {
  809. $zdata = fread($zdatafp, $chunk_size_secondary);
  810. $this->_fwrite( $fp, $zdata );
  811. if($this->getError()) return;
  812. }
  813. // Create new ZIP part, but only if we'll have more data to write
  814. if(!feof($zdatafp))
  815. {
  816. // Create new ZIP part
  817. if(!$this->_createNewPart())
  818. {
  819. // Die if we couldn't create the new part
  820. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  821. return false;
  822. }
  823. else
  824. {
  825. // Close the old data file
  826. fclose($fp);
  827. // Open data file for output
  828. $fp = @fopen( $this->_dataFileName, "ab");
  829. if ($fp === false)
  830. {
  831. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  832. return false;
  833. }
  834. }
  835. }
  836. } // end while
  837. }
  838. }
  839. fclose( $zdatafp );
  840. }
  841. }
  842. }
  843. elseif($isSymlink)
  844. {
  845. $this->_fwrite($fp, @readlink($sourceNameOrData) );
  846. }
  847. // Done with data file.
  848. fclose( $fp );
  849. // Open the central directory file for append
  850. $fp = @fopen( $this->_ctrlDirFileName, "ab");
  851. if ($fp === false)
  852. {
  853. $this->setError("Could not open Central Directory temporary file for append!");
  854. return false;
  855. }
  856. $this->_fwrite( $fp, $this->_ctrlDirHeader );
  857. if(!$isSymlink)
  858. {
  859. $this->_fwrite( $fp, "\x00\x00" ); /* Version made by. */
  860. $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract */
  861. $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag */
  862. $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
  863. }
  864. else
  865. {
  866. // Symlinks get special treatment
  867. $this->_fwrite( $fp, "\x14\x03" ); /* Version made by. */
  868. $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract */
  869. $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag */
  870. $this->_fwrite( $fp, "\x00\x00" ); /* Compression method. */
  871. }
  872. $this->_fwrite( $fp, $hexdtime ); /* Last mod time/date. */
  873. $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
  874. $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
  875. $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
  876. $this->_fwrite( $fp, pack('v', strlen($storedName)) ); /* Length of filename. */
  877. $this->_fwrite( $fp, pack('v', 0 ) ); /* Extra field length. */
  878. $this->_fwrite( $fp, pack('v', 0 ) ); /* File comment length. */
  879. $this->_fwrite( $fp, pack('v', $starting_disk_number_for_this_file ) ); /* Disk number start. */
  880. $this->_fwrite( $fp, pack('v', 0 ) ); /* Internal file attributes. */
  881. if(!$isSymlink)
  882. {
  883. $this->_fwrite( $fp, pack('V', $isDir ? 0x41FF0010 : 0xFE49FFE0) ); /* External file attributes - 'archive' bit set. */
  884. }
  885. else
  886. {
  887. // For SymLinks we store UNIX file attributes
  888. $this->_fwrite( $fp, "\x20\x80\xFF\xA1" ); /* External file attributes for Symlink. */
  889. }
  890. $this->_fwrite( $fp, pack('V', $old_offset) ); /* Relative offset of local header. */
  891. $this->_fwrite( $fp, $storedName ); /* File name. */
  892. /* Optional extra field, file comment goes here. */
  893. // Finished with Central Directory
  894. fclose( $fp );
  895. // Finaly, increase the file counter by one
  896. $this->_totalFileEntries++;
  897. // ... and return TRUE = success
  898. return TRUE;
  899. }
  900. // ------------------------------------------------------------------------
  901. // Archiver-specific utility functions
  902. // ------------------------------------------------------------------------
  903. /**
  904. * Converts a UNIX timestamp to a 4-byte DOS date and time format
  905. * (date in high 2-bytes, time in low 2-bytes allowing magnitude
  906. * comparison).
  907. *
  908. * @access private
  909. *
  910. * @param integer $unixtime The current UNIX timestamp.
  911. *
  912. * @return integer The current date in a 4-byte DOS format.
  913. */
  914. function _unix2DOSTime($unixtime = null)
  915. {
  916. $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
  917. if ($timearray['year'] < 1980) {
  918. $timearray['year'] = 1980;
  919. $timearray['mon'] = 1;
  920. $timearray['mday'] = 1;
  921. $timearray['hours'] = 0;
  922. $timearray['minutes'] = 0;
  923. $timearray['seconds'] = 0;
  924. }
  925. return (($timearray['year'] - 1980) << 25) |
  926. ($timearray['mon'] << 21) |
  927. ($timearray['mday'] << 16) |
  928. ($timearray['hours'] << 11) |
  929. ($timearray['minutes'] << 5) |
  930. ($timearray['seconds'] >> 1);
  931. }
  932. function _createNewPart($finalPart = false)
  933. {
  934. $this->_totalFragments++;
  935. $this->_currentFragment = $this->_totalFragments;
  936. if($finalPart)
  937. {
  938. $this->_dataFileName = $this->_dataFileNameBase.'.zip';
  939. }
  940. else
  941. {
  942. $this->_dataFileName = $this->_dataFileNameBase.'.z'.sprintf('%02d', $this->_currentFragment);
  943. }
  944. JoomlapackLogger::WriteLog(_JP_LOG_INFO, 'Creating new ZIP part #'.$this->_currentFragment.', file '.$this->_dataFileName);
  945. // Inform CUBE that we have chenged the multipart number
  946. $cube =& JoomlapackCUBE::getInstance();
  947. $cube->updateMultipart($this->_totalFragments);
  948. // Try to remove any existing file
  949. @unlink($this->_dataFileName);
  950. // Touch the new file
  951. return @touch($this->_dataFileName);
  952. }
  953. }
  954. // ===================================================================================================
  955. /**
  956. * A handy class to abstract the calculation of CRC32 of files under various
  957. * server conditions and versions of PHP.
  958. * @access private
  959. */
  960. class CRC32CalcClass
  961. {
  962. var $startTime = 0;
  963. /**
  964. * Returns the CRC32 of a file, selecting the more appropriate algorithm.
  965. *
  966. * @param string $filename Absolute path to the file being processed
  967. * @param integer $JoomlapackPackerZIP_CHUNK_SIZE Obsoleted
  968. * @return integer The CRC32 in numerical form
  969. */
  970. function crc32_file( $filename, $JoomlapackPackerZIP_CHUNK_SIZE )
  971. {
  972. static $configuration;
  973. if(!$configuration)
  974. {
  975. jpimport('models.registry', true);
  976. $configuration =& JoomlapackModelRegistry::getInstance();
  977. }
  978. // Keep-alive before CRC32 calculation
  979. if($configuration->get("enableMySQLKeepalive", false))
  980. {
  981. list($usec, $sec) = explode(" ", microtime());
  982. $endTime = ((float)$usec + (float)$sec);
  983. if($endTime - $this->startTime > 0.5)
  984. {
  985. $this->startTime = $endTime;
  986. JoomlapackCUBETables::WriteVar('dummy', 1);
  987. }
  988. }
  989. if( function_exists("hash_file") )
  990. {
  991. $res = $this->crc32_file_php512( $filename );
  992. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP512]" );
  993. }
  994. else if ( function_exists("file_get_contents") && ( @filesize($filename) <= $JoomlapackPackerZIP_CHUNK_SIZE ) ) {
  995. $res = $this->crc32_file_getcontents( $filename );
  996. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [GETCONTENTS]" );
  997. } else {
  998. $res = $this->crc32_file_php4($filename, $JoomlapackPackerZIP_CHUNK_SIZE);
  999. JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP4]" );
  1000. }
  1001. if ($res === FALSE) {
  1002. $cube =& JoomlapackCUBE::getInstance();
  1003. $cube->addWarning( "File $filename - NOT READABLE: CRC32 IS WRONG!" );
  1004. }
  1005. return $res;
  1006. }
  1007. /**
  1008. * Very efficient CRC32 calculation for PHP 5.1.2 and greater, requiring
  1009. * the 'hash' PECL extension
  1010. *
  1011. * @param string $filename Absolute filepath
  1012. * @return integer The CRC32
  1013. * @access private
  1014. */
  1015. function crc32_file_php512($filename)
  1016. {
  1017. // Fix 2.4: CRC32 checksum was wrongfully inverted. Why?!
  1018. return hexdec( @hash_file('crc32b', $filename, false ) );
  1019. }
  1020. /**
  1021. * A compatible CRC32 calculation using file_get_contents, utilizing immense
  1022. * ammounts of RAM
  1023. *
  1024. * @param string $filename
  1025. * @return integer
  1026. * @access private
  1027. */
  1028. function crc32_file_getcontents($filename)
  1029. {
  1030. return crc32( @file_get_contents($filename) );
  1031. }
  1032. /**
  1033. * There used to be a workaround for large files under PHP4. However, it never
  1034. * really worked, so it is removed and a warning is posted instead.
  1035. *
  1036. * @param string $filename
  1037. * @param integer $JoomlapackPackerZIP_CHUNK_SIZE
  1038. * @return integer
  1039. */
  1040. function crc32_file_php4($filename, $JoomlapackPackerZIP_CHUNK_SIZE)
  1041. {
  1042. $cube =& JoomlapackCUBE::getInstance();
  1043. $cube->addWarning( "Function hash_file not detected processing the 'large'' file $filename; it will appear as seemingly corrupt in the archive. Only the CRC32 is invalid, though. Please read our forum announcement for explanation of this message." );
  1044. return 0;
  1045. }
  1046. }