PageRenderTime 64ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/administrator/components/com_akeeba/akeeba/engines/archiver/zip.php

https://bitbucket.org/kraymitchell/apex
PHP | 1239 lines | 891 code | 111 blank | 237 comment | 181 complexity | 26a9c63660a976c45a48b4c5cca37216 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * Akeeba Engine
  4. * The modular PHP5 site backup engine
  5. * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
  6. * @license GNU GPL version 3 or, at your option, any later version
  7. * @package akeebaengine
  8. *
  9. */
  10. // Protection against direct access
  11. defined('AKEEBAENGINE') or die('Restricted access');
  12. class AEArchiverZip extends AEAbstractArchiver {
  13. /** @var string Beginning of central directory record. */
  14. private $_ctrlDirHeader = "\x50\x4b\x01\x02";
  15. /** @var string End of central directory record. */
  16. private $_ctrlDirEnd = "\x50\x4b\x05\x06";
  17. /** @var string Beginning of file contents. */
  18. private $_fileHeader = "\x50\x4b\x03\x04";
  19. /** @var string The name of the temporary file holding the ZIP's Central Directory */
  20. private $_ctrlDirFileName;
  21. /** @var string The name of the file holding the ZIP's data, which becomes the final archive */
  22. private $_dataFileName;
  23. /** @var integer The total number of files and directories stored in the ZIP archive */
  24. private $_totalFileEntries;
  25. /** @var integer The total size of data in the archive. Note: On 32-bit versions of PHP, this will overflow for archives over 2Gb! */
  26. private $_totalDataSize = 0;
  27. /** @var integer The chunk size for CRC32 calculations */
  28. private $AkeebaPackerZIP_CHUNK_SIZE;
  29. /** @var bool Should I use Split ZIP? */
  30. private $_useSplitZIP = false;
  31. /** @var int Maximum fragment size, in bytes */
  32. private $_fragmentSize = 0;
  33. /** @var int Current fragment number */
  34. private $_currentFragment = 1;
  35. /** @var int Total number of fragments */
  36. private $_totalFragments = 1;
  37. /** @var string Archive full path without extension */
  38. private $_dataFileNameBase = '';
  39. /** @var bool Should I store symlinks as such (no dereferencing?) */
  40. private $_symlink_store_target = false;
  41. /**
  42. * Extend the bootstrap code to add some define's used by the ZIP format engine
  43. * @see backend/akeeba/abstract/AEAbstractArchiver#__bootstrap_code()
  44. */
  45. protected function __bootstrap_code()
  46. {
  47. if(!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
  48. {
  49. $config = AEFactory::getConfiguration();
  50. define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size
  51. define("_AKEEBA_DIRECTORY_READ_CHUNK", $config->get('engine.archiver.zip.cd_glue_chunk_size')); // How much data to read at once when finalizing ZIP archives
  52. }
  53. parent::__bootstrap_code();
  54. }
  55. /**
  56. * Class constructor - initializes internal operating parameters
  57. */
  58. public function __construct()
  59. {
  60. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverZip :: New instance");
  61. // Get chunk override
  62. $registry = AEFactory::getConfiguration();
  63. if( $registry->get('engine.archiver.common.chunk_size', 0) > 0 )
  64. {
  65. $this->AkeebaPackerZIP_CHUNK_SIZE = AKEEBA_CHUNK;
  66. }
  67. else
  68. {
  69. // Try to use as much memory as it's possible for CRC32 calculation
  70. $memLimit = ini_get("memory_limit");
  71. if(strstr($memLimit, 'M')) {
  72. $memLimit = (int)$memLimit * 1048576;
  73. } elseif(strstr($totalRAM, 'K')) {
  74. $memLimit = (int)$memLimit * 1024;
  75. } elseif(strstr($memLimit, 'G')) {
  76. $memLimit = (int)$memLimit * 1073741824;
  77. } else {
  78. $memLimit = (int)$memLimit;
  79. }
  80. if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
  81. if ( ($memLimit == "") ) {
  82. // No memory limit, use 2Mb chunks (fairly large, right?)
  83. $this->AkeebaPackerZIP_CHUNK_SIZE = 2097152;
  84. } elseif ( function_exists("memory_get_usage") ) {
  85. // 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
  86. $memLimit = $this->_return_bytes( $memLimit );
  87. $availableRAM = $memLimit - memory_get_usage();
  88. if ($availableRAM <= 0) {
  89. // Some PHP implemenations also return the size of the httpd footprint!
  90. if ( ($memLimit - 6291456) > 0 ) {
  91. $this->AkeebaPackerZIP_CHUNK_SIZE = $memLimit - 6291456;
  92. } else {
  93. $this->AkeebaPackerZIP_CHUNK_SIZE = 2097152;
  94. }
  95. } else {
  96. $this->AkeebaPackerZIP_CHUNK_SIZE = $availableRAM * 0.5;
  97. }
  98. } else {
  99. // PHP can't report memory usage, use a conservative 512Kb
  100. $this->AkeebaPackerZIP_CHUNK_SIZE = 524288;
  101. }
  102. }
  103. // NEW 2.3: Should we enable Split ZIP feature?
  104. $fragmentsize = $registry->get('engine.archiver.common.part_size', 0);
  105. if($fragmentsize >= 65536)
  106. {
  107. // If the fragment size is AT LEAST 64Kb, enable Split ZIP
  108. $this->_useSplitZIP = true;
  109. $this->_fragmentSize = $fragmentsize;
  110. // Indicate that we have at least 1 part
  111. $statistics = AEFactory::getStatistics();
  112. $statistics->updateMultipart(1);
  113. }
  114. // NEW 2.3: Should I use Symlink Target Storage?
  115. $dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks', true);
  116. if(!$dereferencesymlinks)
  117. {
  118. // We are told not to dereference symlinks. Are we on Windows?
  119. if (function_exists('php_uname'))
  120. {
  121. $isWindows = stristr(php_uname(), 'windows');
  122. }
  123. else
  124. {
  125. $isWindows = (DIRECTORY_SEPARATOR == '\\');
  126. }
  127. // If we are not on Windows, enable symlink target storage
  128. $this->_symlink_store_target = !$isWindows;
  129. }
  130. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Chunk size for CRC is now " . $this->AkeebaPackerZIP_CHUNK_SIZE . " bytes");
  131. parent::__construct();
  132. }
  133. /**
  134. * Initialises the archiver class, creating the archive from an existent
  135. * installer's JPA archive.
  136. *
  137. * @param string $sourceJPAPath Absolute path to an installer's JPA archive
  138. * @param string $targetArchivePath Absolute path to the generated archive
  139. * @param array $options A named key array of options (optional). This is currently not supported
  140. */
  141. public function initialize( $targetArchivePath, $options = array() )
  142. {
  143. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverZip :: initialize - archive $targetArchivePath");
  144. // Get names of temporary files
  145. $configuration = AEFactory::getConfiguration();
  146. $this->_ctrlDirFileName = tempnam( $configuration->get('akeeba.basic.output_directory'), 'akzcd' );
  147. $this->_dataFileName = $targetArchivePath;
  148. // If we use splitting, initialize
  149. if($this->_useSplitZIP)
  150. {
  151. AEUtilLogger::WriteLog(_AE_LOG_INFO, "AEArchiverZip :: Split ZIP creation enabled");
  152. $this->_dataFileNameBase = dirname($targetArchivePath).'/'.basename($targetArchivePath,'.zip');
  153. $this->_dataFileName = $this->_dataFileNameBase.'.z01';
  154. }
  155. $this->_ctrlDirFileName = basename($this->_ctrlDirFileName);
  156. $pos = strrpos($this->_ctrlDirFileName, '/');
  157. if($pos !== false) {
  158. $this->_ctrlDirFileName = substr($this->_ctrlDirFileName, $pos+1);
  159. }
  160. $pos = strrpos($this->_ctrlDirFileName, '\\');
  161. if($pos !== false) {
  162. $this->_ctrlDirFileName = substr($this->_ctrlDirFileName, $pos+1);
  163. }
  164. $this->_ctrlDirFileName = AEUtilTempfiles::registerTempFile($this->_ctrlDirFileName);
  165. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverZip :: CntDir Tempfile = " . $this->_ctrlDirFileName);
  166. // Create temporary file
  167. if(!@touch( $this->_ctrlDirFileName ))
  168. {
  169. $this->setError("Could not open temporary file for ZIP archiver. Please check your temporary directory's permissions!");
  170. return false;
  171. }
  172. if(function_exists('chmod')) {
  173. chmod($this->_ctrlDirFileName, 0666);
  174. }
  175. // Try to kill the archive if it exists
  176. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverZip :: Killing old archive");
  177. $fp = fopen( $this->_dataFileName, "wb" );
  178. if (!($fp === false)) {
  179. ftruncate( $fp,0 );
  180. fclose( $fp );
  181. } else {
  182. @unlink( $this->_dataFileName );
  183. }
  184. if(!@touch( $this->_dataFileName ))
  185. {
  186. $this->setError("Could not open archive file for ZIP archiver. Please check your output directory's permissions!");
  187. return false;
  188. }
  189. if(function_exists('chmod')) {
  190. chmod($this->_dataFileName, 0666);
  191. }
  192. // On split archives, include the "Split ZIP" header, for PKZIP 2.50+ compatibility
  193. if($this->_useSplitZIP)
  194. {
  195. file_put_contents($this->_dataFileName, "\x50\x4b\x07\x08");
  196. // Also update the statistics table that we are a multipart archive...
  197. $statistics = AEFactory::getStatistics();
  198. $statistics->updateMultipart(1);
  199. }
  200. }
  201. /**
  202. * Creates the ZIP file out of its pieces.
  203. * Official ZIP file format: http://www.pkware.com/appnote.txt
  204. *
  205. * @return boolean TRUE on success, FALSE on failure
  206. */
  207. public function finalize()
  208. {
  209. // 1. Get size of central directory
  210. clearstatcache();
  211. $this->_totalDataSize += @filesize( $this->_dataFileName );
  212. $cdSize = @filesize( $this->_ctrlDirFileName );
  213. // 2. Append Central Directory to data file and remove the CD temp file afterwards
  214. $dataFP = fopen( $this->_dataFileName, "ab" );
  215. $cdFP = fopen( $this->_ctrlDirFileName, "rb" );
  216. if( $dataFP === false )
  217. {
  218. $this->setError('Could not open ZIP data file '.$this->_dataFileName.' for reading');
  219. return false;
  220. }
  221. if ( $cdFP === false ) {
  222. // Already glued, return
  223. fclose( $dataFP );
  224. return false;
  225. }
  226. if(!$this->_useSplitZIP)
  227. {
  228. while( !feof($cdFP) )
  229. {
  230. $chunk = fread( $cdFP, _AKEEBA_DIRECTORY_READ_CHUNK );
  231. $this->_fwrite( $dataFP, $chunk );
  232. if($this->getError()) return;
  233. }
  234. unset( $chunk );
  235. fclose( $cdFP );
  236. }
  237. else
  238. // Special considerations for Split ZIP
  239. {
  240. // Calcuate size of Central Directory + EOCD records
  241. $comment_length = function_exists('mb_strlen') ? mb_strlen($this->_comment, '8bit') : strlen($this->_comment);
  242. $total_cd_eocd_size = $cdSize + 22 + $comment_length;
  243. // Free space on the part
  244. clearstatcache();
  245. $current_part_size = @filesize($this->_dataFileName);
  246. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  247. if( ($free_space < $total_cd_eocd_size) && ($total_cd_eocd_size > 65536) )
  248. {
  249. // Not enough space on archive for CD + EOCD, will go on separate part
  250. // Create new final part
  251. if(!$this->_createNewPart(true))
  252. {
  253. // Die if we couldn't create the new part
  254. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  255. return false;
  256. }
  257. else
  258. {
  259. // Close the old data file
  260. fclose($dataFP);
  261. // Open data file for output
  262. $dataFP = @fopen( $this->_dataFileName, "ab");
  263. if ($dataFP === false)
  264. {
  265. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  266. return false;
  267. }
  268. // Write the CD record
  269. while( !feof($cdFP) )
  270. {
  271. $chunk = fread( $cdFP, _AKEEBA_DIRECTORY_READ_CHUNK );
  272. $this->_fwrite( $dataFP, $chunk );
  273. if($this->getError()) return;
  274. }
  275. unset( $chunk );
  276. fclose( $cdFP );
  277. }
  278. }
  279. else
  280. {
  281. // Glue the CD + EOCD on the same part if they fit, or anyway if they are less than 64Kb.
  282. // NOTE: WE *MUST NOT* CREATE FRAGMENTS SMALLER THAN 64Kb!!!!
  283. while( !feof($cdFP) )
  284. {
  285. $chunk = fread( $cdFP, _AKEEBA_DIRECTORY_READ_CHUNK );
  286. $this->_fwrite( $dataFP, $chunk );
  287. if($this->getError()) return;
  288. }
  289. unset( $chunk );
  290. fclose( $cdFP );
  291. }
  292. }
  293. AEUtilTempfiles::unregisterAndDeleteTempFile($this->_ctrlDirFileName);
  294. // 3. Write the rest of headers to the end of the ZIP file
  295. fclose( $dataFP );
  296. clearstatcache();
  297. $dataFP = fopen( $this->_dataFileName, "ab" );
  298. if($dataFP === false)
  299. {
  300. $this->setError('Could not open '.$this->_dataFileName.' for append');
  301. return false;
  302. }
  303. $this->_fwrite( $dataFP, $this->_ctrlDirEnd );
  304. if($this->getError()) return;
  305. if($this->_useSplitZIP)
  306. {
  307. // Split ZIP files, enter relevant disk number information
  308. $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Number of this disk. */
  309. $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Disk with central directory start. */
  310. }
  311. else
  312. {
  313. // Non-split ZIP files, the disk numbers MUST be 0
  314. $this->_fwrite( $dataFP, pack('V', 0) );
  315. }
  316. $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries "on this disk". */
  317. $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries overall. */
  318. $this->_fwrite( $dataFP, pack('V', $cdSize) ); /* Size of central directory. */
  319. $this->_fwrite( $dataFP, pack('V', $this->_totalDataSize) ); /* Offset to start of central dir. */
  320. $sizeOfComment = $comment_length = function_exists('mb_strlen') ? mb_strlen($this->_comment, '8bit') : strlen($this->_comment);
  321. // 2.0.b2 -- Write a ZIP file comment
  322. $this->_fwrite( $dataFP, pack('v', $sizeOfComment) ); /* ZIP file comment length. */
  323. $this->_fwrite( $dataFP, $this->_comment );
  324. fclose( $dataFP );
  325. //sleep(2);
  326. // If Split ZIP and there is no .zip file, rename the last fragment to .ZIP
  327. if($this->_useSplitZIP)
  328. {
  329. $extension = substr($this->_dataFileName, -3);
  330. if($extension != '.zip')
  331. {
  332. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Renaming last ZIP part to .ZIP extension');
  333. $newName = $this->_dataFileNameBase.'.zip';
  334. if(!@rename($this->_dataFileName, $newName))
  335. {
  336. $this->setError('Could not rename last ZIP part to .ZIP extension.');
  337. return false;
  338. }
  339. $this->_dataFileName = $newName;
  340. }
  341. }
  342. // If Split ZIP and only one fragment, change the signature
  343. if($this->_useSplitZIP && ($this->_totalFragments == 1) )
  344. {
  345. $fp = fopen($this->_dataFileName, 'r+b');
  346. $this->_fwrite($fp, "\x50\x4b\x30\x30" );
  347. }
  348. if( function_exists('chmod') )
  349. {
  350. @chmod($this->_dataFileName, 0755);
  351. }
  352. return true;
  353. }
  354. /**
  355. * Returns a string with the extension (including the dot) of the files produced
  356. * by this class.
  357. * @return string
  358. */
  359. public function getExtension()
  360. {
  361. return '.zip';
  362. }
  363. /**
  364. * The most basic file transaction: add a single entry (file or directory) to
  365. * the archive.
  366. *
  367. * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
  368. * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
  369. * @param string $targetName The (relative) file name under which to store the file in the archive
  370. * @return True on success, false otherwise
  371. */
  372. protected function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
  373. {
  374. static $configuration;
  375. // Note down the starting disk number for Split ZIP archives
  376. if($this->_useSplitZIP) {
  377. $starting_disk_number_for_this_file = $this->_currentFragment - 1;
  378. } else {
  379. $starting_disk_number_for_this_file = 0;
  380. }
  381. if(!$configuration) {
  382. $configuration = AEFactory::getConfiguration();
  383. }
  384. if(!$configuration->get('volatile.engine.archiver.processingfile',false)) {
  385. // See if it's a directory
  386. $isDir = $isVirtual ? false : is_dir($sourceNameOrData);
  387. // See if it's a symlink (w/out dereference)
  388. $isSymlink = false;
  389. if($this->_symlink_store_target && !$isVirtual) {
  390. $isSymlink = is_link($sourceNameOrData);
  391. }
  392. // Get real size before compression
  393. if($isVirtual) {
  394. $fileSize = function_exists('mb_strlen') ? mb_strlen($sourceNameOrData, '8bit') : strlen($sourceNameOrData);
  395. } else {
  396. if($isSymlink) {
  397. $fileSize = function_exists('mb_strlen') ? mb_strlen(@readlink($sourceNameOrData),'8bit') : strlen(@readlink($sourceNameOrData));
  398. } else {
  399. $fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
  400. }
  401. }
  402. // Get last modification time to store in archive
  403. $ftime = $isVirtual ? time() : @filemtime( $sourceNameOrData );
  404. // Decide if we will compress
  405. if ($isDir || $isSymlink) {
  406. $compressionMethod = 0; // don't compress directories...
  407. } else {
  408. // Do we have plenty of memory left?
  409. $memLimit = ini_get("memory_limit");
  410. if(strstr($memLimit, 'M')) {
  411. $memLimit = (int)$memLimit * 1048576;
  412. } elseif(strstr($memLimit, 'K')) {
  413. $memLimit = (int)$memLimit * 1024;
  414. } elseif(strstr($memLimit, 'G')) {
  415. $memLimit = (int)$memLimit * 1073741824;
  416. } else {
  417. $memLimit = (int)$memLimit;
  418. }
  419. if (($memLimit == "") || ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD)) {
  420. // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
  421. $compressionMethod = ($fileSize <= _AKEEBA_COMPRESSION_THRESHOLD) ? 8 : 0;
  422. } elseif ( function_exists("memory_get_usage") ) {
  423. // 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
  424. $memLimit = $this->_return_bytes( $memLimit );
  425. $availableRAM = $memLimit - memory_get_usage();
  426. $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 8 : 0;
  427. } else {
  428. // PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
  429. $compressionMethod = ($fileSize <= 524288) ? 8 : 0;;
  430. }
  431. }
  432. $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;
  433. $storedName = $targetName;
  434. if($isVirtual) AEUtilLogger::WriteLog(_AE_LOG_DEBUG, ' Virtual add:'.$storedName.' ('.$fileSize.') - '.$compressionMethod);
  435. /* "Local file header" segment. */
  436. $unc_len = $fileSize; // File size
  437. if (!$isDir) {
  438. // Get CRC for regular files, not dirs
  439. if($isVirtual)
  440. {
  441. $crc = crc32($sourceNameOrData);
  442. }
  443. else
  444. {
  445. $crcCalculator = new AECRC32CalcClass;
  446. $crc = $crcCalculator->crc32_file( $sourceNameOrData, $this->AkeebaPackerZIP_CHUNK_SIZE ); // This is supposed to be the fast way to calculate CRC32 of a (large) file.
  447. unset( $crcCalculator );
  448. // If the file was unreadable, $crc will be false, so we skip the file
  449. if ($crc === false) {
  450. $this->setWarning( 'Could not calculate CRC32 for '.$sourceNameOrData);
  451. return false;
  452. }
  453. }
  454. } else if($isSymlink) {
  455. $crc = crc32( @readlink($sourceNameOrData) );
  456. } else {
  457. // Dummy CRC for dirs
  458. $crc = 0;
  459. $storedName .= "/";
  460. $unc_len = 0;
  461. }
  462. // If we have to compress, read the data in memory and compress it
  463. if ($compressionMethod == 8) {
  464. // Get uncompressed data
  465. if( $isVirtual )
  466. {
  467. $udata =& $sourceNameOrData;
  468. }
  469. else
  470. {
  471. $udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
  472. }
  473. if ($udata === FALSE) {
  474. // Unreadable file, skip it. Normally, we should have exited on CRC code above
  475. $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions' );
  476. return false;
  477. } else {
  478. // Proceed with compression
  479. $zdata = @gzcompress($udata);
  480. if ($zdata === false) {
  481. // If compression fails, let it behave like no compression was available
  482. $c_len = $unc_len;
  483. $compressionMethod = 0;
  484. } else {
  485. unset( $udata );
  486. $zdata = substr(substr($zdata, 0, -4), 2);
  487. $c_len = (function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata));
  488. }
  489. }
  490. } else {
  491. $c_len = $unc_len;
  492. }
  493. /* Get the hex time. */
  494. $dtime = dechex($this->_unix2DosTime($ftime));
  495. if( (function_exists('mb_strlen') ? mb_strlen($dtime,'8bit') : strlen($dtime)) < 8 ) $dtime = "00000000";
  496. $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) .
  497. chr(hexdec($dtime[4] . $dtime[5])) .
  498. chr(hexdec($dtime[2] . $dtime[3])) .
  499. chr(hexdec($dtime[0] . $dtime[1]));
  500. // Get current data file size
  501. //clearstatcache();
  502. //$old_offset = @filesize( $this->_dataFileName );
  503. // If it's a split ZIP file, we've got to make sure that the header can fit in the part
  504. if($this->_useSplitZIP) {
  505. // Get header size, taking into account any extra header necessary
  506. $header_size = 30 + (function_exists('mb_strlen') ? mb_strlen($storedName,'8bit') : strlen($storedName));
  507. // Compare to free part space
  508. clearstatcache();
  509. $current_part_size = @filesize($this->_dataFileName);
  510. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  511. if($free_space <= $header_size)
  512. {
  513. // Not enough space on current part, create new part
  514. if(!$this->_createNewPart())
  515. {
  516. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  517. return false;
  518. }
  519. }
  520. }
  521. // Open data file for output
  522. $fp = @fopen( $this->_dataFileName, "ab");
  523. if ($fp === false)
  524. {
  525. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  526. return false;
  527. }
  528. $seek_result = @fseek($fp, 0, SEEK_END);
  529. $old_offset = ($seek_result == -1) ? false : @ftell($fp);
  530. if($old_offset === false) {
  531. @clearstatcache();
  532. $old_offset = @filesize($this->_dataFileName);
  533. }
  534. // Get the file name length in bytes
  535. if(function_exists('mb_strlen')) {
  536. $fn_length = mb_strlen($storedName, '8bit');
  537. } else {
  538. $fn_length = strlen($storedName);
  539. }
  540. $this->_fwrite( $fp, $this->_fileHeader ); /* Begin creating the ZIP data. */
  541. if(!$isSymlink) {
  542. $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract. */
  543. }
  544. else {
  545. $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract. */
  546. }
  547. $this->_fwrite( $fp, pack('v',2048) ); /* General purpose bit flag. Bit 11 set = use UTF-8 encoding for filenames & comments */
  548. $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
  549. $this->_fwrite( $fp, $hexdtime ); /* Last modification time/date. */
  550. $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
  551. if(!isset($c_len)) $c_len = $unc_len;
  552. $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
  553. $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
  554. $this->_fwrite( $fp, pack('v', $fn_length) ); /* Length of filename. */
  555. $this->_fwrite( $fp, pack('v', 0) ); /* Extra field length. */
  556. $this->_fwrite( $fp, $storedName ); /* File name. */
  557. // Cache useful information about the file
  558. if(!$isDir && !$isSymlink && !$isVirtual)
  559. {
  560. $configuration->set('volatile.engine.archiver.unc_len', $unc_len);
  561. $configuration->set('volatile.engine.archiver.hexdtime', $hexdtime);
  562. $configuration->set('volatile.engine.archiver.crc', $crc);
  563. $configuration->set('volatile.engine.archiver.c_len', $c_len);
  564. $configuration->set('volatile.engine.archiver.fn_length', $fn_length);
  565. $configuration->set('volatile.engine.archiver.old_offset', $old_offset);
  566. $configuration->set('volatile.engine.archiver.storedName', $storedName);
  567. $configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
  568. }
  569. }
  570. else
  571. {
  572. // Since we are continuing archiving, it's an uncompressed regular file. Set up the variables.
  573. $compressionMethod = 1;
  574. $isDir = false;
  575. $isSymlink = false;
  576. $unc_len = $configuration->get('volatile.engine.archiver.unc_len');
  577. $hexdtime = $configuration->get('volatile.engine.archiver.hexdtime');
  578. $crc = $configuration->get('volatile.engine.archiver.crc');
  579. $c_len = $configuration->get('volatile.engine.archiver.c_len');
  580. $fn_length = $configuration->get('volatile.engine.archiver.fn_length');
  581. $old_offset = $configuration->get('volatile.engine.archiver.old_offset');
  582. $storedName = $configuration->get('volatile.engine.archiver.storedName');
  583. // Open data file for output
  584. $fp = @fopen( $this->_dataFileName, "ab");
  585. if ($fp === false)
  586. {
  587. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  588. return false;
  589. }
  590. }
  591. /* "File data" segment. */
  592. if ($compressionMethod == 8) {
  593. // Just dump the compressed data
  594. if(!$this->_useSplitZIP)
  595. {
  596. $this->_fwrite( $fp, $zdata );
  597. if($this->getError()) return;
  598. }
  599. else
  600. {
  601. // Split ZIP. Check if we need to split the part in the middle of the data.
  602. clearstatcache();
  603. $current_part_size = @filesize($this->_dataFileName);
  604. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  605. if($free_space >= (function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)) )
  606. {
  607. // Write in one part
  608. $this->_fwrite( $fp, $zdata );
  609. if($this->getError()) return;
  610. }
  611. else
  612. {
  613. $bytes_left = (function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata));
  614. while($bytes_left > 0)
  615. {
  616. clearstatcache();
  617. $current_part_size = @filesize($this->_dataFileName);
  618. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  619. // Split between parts - Write a part
  620. $this->_fwrite( $fp, $zdata, min((function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)),$free_space) );
  621. if($this->getError()) return;
  622. // Get the rest of the data
  623. $bytes_left = (function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)) - $free_space;
  624. if($bytes_left > 0)
  625. {
  626. // Create new part
  627. if(!$this->_createNewPart())
  628. {
  629. // Die if we couldn't create the new part
  630. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  631. return false;
  632. }
  633. else
  634. {
  635. // Close the old data file
  636. fclose($fp);
  637. // Open data file for output
  638. $fp = @fopen( $this->_dataFileName, "ab");
  639. if ($fp === false)
  640. {
  641. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  642. return false;
  643. }
  644. }
  645. $zdata = substr($zdata, -$bytes_left);
  646. }
  647. }
  648. }
  649. }
  650. unset( $zdata );
  651. } elseif ( !($isDir || $isSymlink) ) {
  652. // Virtual file, just write the data!
  653. if( $isVirtual )
  654. {
  655. // Just dump the data
  656. if(!$this->_useSplitZIP)
  657. {
  658. $this->_fwrite( $fp, $sourceNameOrData );
  659. if($this->getError()) return;
  660. }
  661. else
  662. {
  663. // Split ZIP. Check if we need to split the part in the middle of the data.
  664. clearstatcache();
  665. $current_part_size = @filesize($this->_dataFileName);
  666. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  667. if($free_space >= (function_exists('mb_strlen') ? mb_strlen($sourceNameOrData,'8bit') : strlen($sourceNameOrData)) )
  668. {
  669. // Write in one part
  670. $this->_fwrite( $fp, $sourceNameOrData );
  671. if($this->getError()) return;
  672. }
  673. else
  674. {
  675. $bytes_left = (function_exists('mb_strlen') ? mb_strlen($sourceNameOrData,'8bit') : strlen($sourceNameOrData));
  676. while($bytes_left > 0)
  677. {
  678. clearstatcache();
  679. $current_part_size = @filesize($this->_dataFileName);
  680. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  681. // Split between parts - Write first part
  682. $this->_fwrite( $fp, $sourceNameOrData, min( (function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)) ,$free_space) );
  683. if($this->getError()) return;
  684. // Get the rest of the data
  685. $rest_size = (function_exists('mb_strlen') ? mb_strlen($sourceNameOrData,'8bit') : strlen($sourceNameOrData)) - $free_space;
  686. if($rest_size > 0)
  687. {
  688. // Create new part if required
  689. if(!$this->_createNewPart())
  690. {
  691. // Die if we couldn't create the new part
  692. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  693. return false;
  694. }
  695. else
  696. {
  697. // Close the old data file
  698. fclose($fp);
  699. // Open data file for output
  700. $fp = @fopen( $this->_dataFileName, "ab");
  701. if ($fp === false)
  702. {
  703. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  704. return false;
  705. }
  706. }
  707. // Get the rest of the compressed data
  708. $zdata = substr($sourceNameOrData, -$rest_size);
  709. }
  710. $bytes_left = $rest_size;
  711. }
  712. }
  713. }
  714. }
  715. else
  716. {
  717. // IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data
  718. if($configuration->get('volatile.engine.archiver.processingfile',false))
  719. {
  720. $sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
  721. $unc_len = $configuration->get('volatile.engine.archiver.unc_len', 0);
  722. $resume = $configuration->get('volatile.engine.archiver.resume', 0);
  723. }
  724. // Copy the file contents, ignore directories
  725. $zdatafp = @fopen( $sourceNameOrData, "rb" );
  726. if( $zdatafp === FALSE )
  727. {
  728. $this->setWarning( 'Unreadable file '.$sourceNameOrData.'. Check permissions' );
  729. return false;
  730. }
  731. else
  732. {
  733. $timer = AEFactory::getTimer();
  734. // Seek to the resume point if required
  735. if($configuration->get('volatile.engine.archiver.processingfile',false))
  736. {
  737. // Seek to new offset
  738. $seek_result = @fseek($zdatafp, $resume);
  739. if( $seek_result === -1 )
  740. {
  741. // What?! We can't resume!
  742. $this->setError(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData));
  743. return false;
  744. }
  745. // Doctor the uncompressed size to match the remainder of the data
  746. $unc_len = $unc_len - $resume;
  747. }
  748. if(!$this->_useSplitZIP)
  749. {
  750. // For non Split ZIP, just dump the file very fast
  751. while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
  752. $zdata = fread($zdatafp, AKEEBA_CHUNK);
  753. $this->_fwrite( $fp, $zdata, min((function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)), AKEEBA_CHUNK) );
  754. $unc_len -= AKEEBA_CHUNK;
  755. if($this->getError()) return;
  756. }
  757. if(!feof($zdatafp) && ($unc_len != 0) )
  758. {
  759. // We have to break, or we'll time out!
  760. $resume = @ftell($zdatafp);
  761. $configuration->set('volatile.engine.archiver.resume', $resume);
  762. $configuration->set('volatile.engine.archiver.processingfile',true);
  763. return true;
  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) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
  776. $zdata = fread($zdatafp, AKEEBA_CHUNK);
  777. $this->_fwrite( $fp, $zdata, min((function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)), AKEEBA_CHUNK) );
  778. $unc_len -= AKEEBA_CHUNK;
  779. if($this->getError()) return;
  780. }
  781. if(!feof($zdatafp) && ($unc_len != 0))
  782. {
  783. // We have to break, or we'll time out!
  784. $resume = @ftell($zdatafp);
  785. $configuration->set('volatile.engine.archiver.resume', $resume);
  786. $configuration->set('volatile.engine.archiver.processingfile',true);
  787. return true;
  788. }
  789. }
  790. else
  791. {
  792. // No, we'll have to split between parts. We'll loop until we run
  793. // out of space.
  794. while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) )
  795. {
  796. // No, we'll have to split between parts. Write the first part
  797. // Find optimal chunk size
  798. clearstatcache();
  799. $current_part_size = @filesize($this->_dataFileName);
  800. $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
  801. $chunk_size_primary = min(AKEEBA_CHUNK, $free_space);
  802. if($chunk_size_primary <= 0) $chunk_size_primary = max(AKEEBA_CHUNK, $free_space);
  803. // Calculate if we have to read some more data (smaller chunk size)
  804. // and how many times we must read w/ the primary chunk size
  805. $chunk_size_secondary = $free_space % $chunk_size_primary;
  806. $loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
  807. // Read and write with the primary chunk size
  808. for( $i = 1; $i <= $loop_times; $i++ )
  809. {
  810. $zdata = fread($zdatafp, $chunk_size_primary);
  811. $this->_fwrite( $fp, $zdata, min((function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)), $chunk_size_primary) );
  812. $unc_len -= $chunk_size_primary;
  813. if($this->getError()) return;
  814. // Do we have enough time to proceed?
  815. if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
  816. // No, we have to break, or we'll time out!
  817. $resume = @ftell($zdatafp);
  818. $configuration->set('volatile.engine.archiver.resume', $resume);
  819. $configuration->set('volatile.engine.archiver.processingfile',true);
  820. return true;
  821. }
  822. }
  823. // Read and write w/ secondary chunk size, if non-zero
  824. if($chunk_size_secondary > 0)
  825. {
  826. $zdata = fread($zdatafp, $chunk_size_secondary);
  827. $this->_fwrite( $fp, $zdata, min((function_exists('mb_strlen') ? mb_strlen($zdata,'8bit') : strlen($zdata)), $chunk_size_secondary) );
  828. $unc_len -= $chunk_size_secondary;
  829. if($this->getError()) return;
  830. }
  831. // Do we have enough time to proceed?
  832. if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) {
  833. // No, we have to break, or we'll time out!
  834. $resume = @ftell($zdatafp);
  835. $configuration->set('volatile.engine.archiver.resume', $resume);
  836. $configuration->set('volatile.engine.archiver.processingfile',true);
  837. // ...and create a new part as well
  838. if(!$this->_createNewPart())
  839. {
  840. // Die if we couldn't create the new part
  841. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  842. return false;
  843. }
  844. // ...then, return
  845. return true;
  846. }
  847. // Create new ZIP part, but only if we'll have more data to write
  848. if(!feof($zdatafp) && ($unc_len > 0) )
  849. {
  850. // Create new ZIP part
  851. if(!$this->_createNewPart())
  852. {
  853. // Die if we couldn't create the new part
  854. $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
  855. return false;
  856. }
  857. else
  858. {
  859. // Close the old data file
  860. fclose($fp);
  861. // We have created the part. If the user asked for immediate post-proc, break step now.
  862. if($configuration->get('engine.postproc.common.after_part',0))
  863. {
  864. $resume = @ftell($zdatafp);
  865. $configuration->set('volatile.engine.archiver.resume', $resume);
  866. $configuration->set('volatile.engine.archiver.processingfile',true);
  867. $configuration->set('volatile.breakflag',true);
  868. @fclose($zdatafp);
  869. @fclose($fp);
  870. return true;
  871. }
  872. // Open data file for output
  873. $fp = @fopen( $this->_dataFileName, "ab");
  874. if ($fp === false)
  875. {
  876. $this->setError("Could not open archive file {$this->_dataFileName} for append!");
  877. return false;
  878. }
  879. }
  880. }
  881. } // end while
  882. }
  883. }
  884. fclose( $zdatafp );
  885. }
  886. }
  887. }
  888. elseif($isSymlink)
  889. {
  890. $this->_fwrite($fp, @readlink($sourceNameOrData) );
  891. }
  892. // Done with data file.
  893. fclose( $fp );
  894. // Open the central directory file for append
  895. $fp = @fopen( $this->_ctrlDirFileName, "ab");
  896. if ($fp === false)
  897. {
  898. $this->setError("Could not open Central Directory temporary file for append!");
  899. return false;
  900. }
  901. $this->_fwrite( $fp, $this->_ctrlDirHeader );
  902. if(!$isSymlink)
  903. {
  904. $this->_fwrite( $fp, "\x14\x00" ); /* Version made by (always set to 2.0). */
  905. $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract */
  906. $this->_fwrite( $fp, pack('v',2048) ); /* General purpose bit flag */
  907. $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
  908. }
  909. else
  910. {
  911. // Symlinks get special treatment
  912. $this->_fwrite( $fp, "\x14\x03" ); /* Version made by (version 2.0 with UNIX extensions). */
  913. $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract */
  914. $this->_fwrite( $fp, pack('v',2048) ); /* General purpose bit flag */
  915. $this->_fwrite( $fp, "\x00\x00" ); /* Compression method. */
  916. }
  917. $this->_fwrite( $fp, $hexdtime ); /* Last mod time/date. */
  918. $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
  919. $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
  920. if($compressionMethod == 0) {
  921. // When we are not compressing, $unc_len is being reduced to 0 while backing up.
  922. // With this trick, we always store the correct length, as in this case the compressed
  923. // and uncompressed length is always the same.
  924. $this->_fwrite( $fp, pack('V', $c_len) ); /* Uncompressed filesize. */
  925. } else {
  926. // When compressing, the uncompressed length differs from compressed length
  927. // and this line writes the correct value.
  928. $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
  929. }
  930. $this->_fwrite( $fp, pack('v', $fn_length) ); /* Length of filename. */
  931. $this->_fwrite( $fp, pack('v', 0 ) ); /* Extra field length. */
  932. $this->_fwrite( $fp, pack('v', 0 ) ); /* File comment length. */
  933. $this->_fwrite( $fp, pack('v', $starting_disk_number_for_this_file ) ); /* Disk number start. */
  934. $this->_fwrite( $fp, pack('v', 0 ) ); /* Internal file attributes. */
  935. if(!$isSymlink)
  936. {
  937. $this->_fwrite( $fp, pack('V', $isDir ? 0x41FF0010 : 0xFE49FFE0) ); /* External file attributes - 'archive' bit set. */
  938. }
  939. else
  940. {
  941. // For SymLinks we store UNIX file attributes
  942. $this->_fwrite( $fp, "\x20\x80\xFF\xA1" ); /* External file attributes for Symlink. */
  943. }
  944. $this->_fwrite( $fp, pack('V', $old_offset) ); /* Relative offset of local header. */
  945. $this->_fwrite( $fp, $storedName ); /* File name. */
  946. /* Optional extra field, file comment goes here. */
  947. // Finished with Central Directory
  948. fclose( $fp );
  949. // Finaly, increase the file counter by one
  950. $this->_totalFileEntries++;
  951. // Uncache data
  952. $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
  953. $configuration->set('volatile.engine.archiver.unc_len', null);
  954. $configuration->set('volatile.engine.archiver.resume', null);
  955. $configuration->set('volatile.engine.archiver.hexdtime', null);
  956. $configuration->set('volatile.engine.archiver.crc', null);
  957. $configuration->set('volatile.engine.archiver.c_len', null);
  958. $configuration->set('volatile.engine.archiver.fn_length', null);
  959. $configuration->set('volatile.engine.archiver.old_offset', null);
  960. $configuration->set('volatile.engine.archiver.storedName', null);
  961. $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
  962. $configuration->set('volatile.engine.archiver.processingfile',false);
  963. // ... and return TRUE = success
  964. return TRUE;
  965. }
  966. // ------------------------------------------------------------------------
  967. // Archiver-specific utility functions
  968. // ------------------------------------------------------------------------
  969. /**
  970. * Converts a UNIX timestamp to a 4-byte DOS date and time format
  971. * (date in high 2-bytes, time in low 2-bytes allowing magnitude
  972. * comparison).
  973. *
  974. * @param integer $unixtime The current UNIX timestamp.
  975. *
  976. * @return integer The current date in a 4-byte DOS format.
  977. */
  978. private function _unix2DOSTime($unixtime = null)
  979. {
  980. $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
  981. if ($timearray['year'] < 1980) {
  982. $timearray['year'] = 1980;
  983. $timearray['mon'] = 1;
  984. $timearray['mday'] = 1;
  985. $timearray['hours'] = 0;
  986. $timearray['minutes'] = 0;
  987. $timearray['seconds'] = 0;
  988. }
  989. return (($timearray['year'] - 1980) << 25) |
  990. ($timearray['mon'] << 21) |
  991. ($timearray['mday'] << 16) |
  992. ($timearray['hours'] << 11) |
  993. ($timearray['minutes'] << 5) |
  994. ($timearray['seconds'] >> 1);
  995. }
  996. private function _createNewPart($finalPart = false)
  997. {
  998. // Push the previous part if we have to post-process it immediately
  999. $configuration = AEFactory::getConfiguration();
  1000. if($configuration->get('engine.postproc.common.after_part',0))
  1001. {
  1002. $this->finishedPart[] = $this->_dataFileName;
  1003. }
  1004. // Add the part's size to our rolling sum
  1005. clearstatcache();
  1006. $this->_totalDataSize += filesize($this->_dataFileName);
  1007. $this->_totalFragments++;
  1008. $this->_currentFragment = $this->_totalFragments;
  1009. if($finalPart)
  1010. {
  1011. $this->_dataFileName = $this->_dataFileNameBase.'.zip';
  1012. }
  1013. else
  1014. {
  1015. $this->_dataFileName = $this->_dataFileNameBase.'.z'.sprintf('%02d', $this->_currentFragment);
  1016. }
  1017. AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new ZIP part #'.$this->_currentFragment.', file '.$this->_dataFileName);
  1018. // Inform CUBE that we have changed the multipart number
  1019. $statistics = AEFactory::getStatistics();
  1020. $statistics->updateMultipart($this->_totalFragments);
  1021. // Try to remove any existing file
  1022. @unlink($this->_dataFileName);
  1023. // Touch the new file
  1024. $result = @touch($this->_dataFileName);
  1025. if(function_exists('chmod')) {
  1026. chmod($this->_dataFileName, 0666);
  1027. }
  1028. return $result;
  1029. }
  1030. }
  1031. // ===================================================================================================
  1032. /**
  1033. * A handy class to abstract the calculation of CRC32 of files under various
  1034. * server conditions and versions of PHP.
  1035. */
  1036. class AECRC32CalcClass
  1037. {
  1038. /**
  1039. * Returns the CRC32 of a file, selecting the more appropriate algorithm.
  1040. *
  1041. * @param string $filename Absolute path to the file being processed
  1042. * @param integer $AkeebaPackerZIP_CHUNK_SIZE Obsoleted
  1043. * @return integer The CRC32 in numerical form
  1044. */
  1045. public function crc32_file( $filename, $AkeebaPackerZIP_CHUNK_SIZE )
  1046. {
  1047. static $configuration;
  1048. if(!$configuration)
  1049. {
  1050. $configuration = AEFactory::getConfiguration();
  1051. }
  1052. if( function_exists("hash_file") )
  1053. {
  1054. $res = $this->crc32_file_php512( $filename );
  1055. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP512]" );
  1056. }
  1057. else if ( function_exists("file_get_contents") && ( @filesize($filename) <= $AkeebaPackerZIP_CHUNK_SIZE ) ) {
  1058. $res = $this->crc32_file_getcontents( $filename );
  1059. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [GETCONTENTS]" );
  1060. } else {
  1061. $res = $this->crc32_file_php4($filename, $AkeebaPackerZIP_CHUNK_SIZE);
  1062. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP4]" );
  1063. }
  1064. if ($res === FALSE) {
  1065. $this->setWarning( "File $filename - NOT READABLE: CRC32 IS WRONG!" );
  1066. }
  1067. return $res;
  1068. }
  1069. /**
  1070. * Very efficient CRC32 calculation for PHP 5.1.2 and greater, requiring
  1071. * the 'hash' PECL extension
  1072. *
  1073. * @param string $filename Absolute filepath
  1074. * @return integer The CRC32
  1075. */
  1076. private function crc32_file_php512($filename)
  1077. {
  1078. // Detection of buggy PHP hosts
  1079. static $mustInvert = null;
  1080. if(is_null($mustInvert))
  1081. {
  1082. $test_crc = @hash('crc32b', 'test', false);
  1083. $mustInvert = ( strtolower($test_crc) == '0c7e7fd8'); // Normally, it's D87F7E0C :)
  1084. if($mustInvert) AEUtilLogger::WriteLog(_AE_LOG_WARNING,'Your server has a buggy PHP version which produces inverted CRC32 values. Attempting a workaround. ZIP files may appear as corrupt.');
  1085. }
  1086. $res = @hash_file('crc32b', $filename, false );
  1087. if($mustInvert)
  1088. {
  1089. // Workaround for buggy PHP versions (I think before 5.1.8) which produce inverted CRC32 sums
  1090. $res2 = substr($res,6,2) . substr($res,4,2) . substr($res,2,2) . substr($res,0,2);
  1091. $res = $res2;
  1092. }
  1093. $res = hexdec( $res );
  1094. return $res;
  1095. }
  1096. /**
  1097. * A compatible CRC32 calculation using file_get_contents, utilizing immense
  1098. * ammounts of RAM
  1099. *
  1100. * @param string $filename
  1101. * @return integer
  1102. */
  1103. private function crc32_file_getcontents($filename)
  1104. {
  1105. return crc32( @file_get_contents($filename) );
  1106. }
  1107. /**
  1108. * There used to be a workaround for large files under PHP4. However, it never
  1109. * really worked, so it is removed and a warning is posted instead.
  1110. *
  1111. * @param string $filename
  1112. * @param integer $AkeebaPackerZIP_CHUNK_SIZE
  1113. * @return integer
  1114. */
  1115. private function crc32_file_php4($filename, $AkeebaPackerZIP_CHUNK_SIZE)
  1116. {
  1117. $this->setWarning( "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." );
  1118. return 0;
  1119. }
  1120. }