/joomla/administrator/components/com_joomlapack/classes/engine/packer/zip.php
PHP | 1142 lines | 793 code | 80 blank | 269 comment | 127 complexity | 5c99c76f3f4da045aa2a0f3c74d9da47 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1
- <?php
- /**
- * @package JoomlaPack
- * @copyright Copyright (C) 2006-2009 JoomlaPack Developers. All rights reserved.
- * @version $Id$
- * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
- * @since 1.2.1
- *
- * JoomlaPack is free software. This version may have been modified pursuant
- * to the GNU General Public License, and as distributed it includes or
- * is derivative of works licensed under the GNU General Public License or
- * other free or open source software licenses.
- *
- * ZIP Creation Module
- *
- * Creates a ZIP file based on the contents of a given file list.
- * Based upon the Compress library of the Horde Project [http://www.horde.org],
- * modified to fit JoomlaPack needs. This class is safe to serialize and deserialize
- * between subsequent calls.
- *
- * JoomlaPack modifications : eficiently read data from files, selective compression,
- * defensive memory management to avoid memory exhaustion errors, separated central
- * directory and data section creation, split archives, symlink storage
- *
- * ----------------------------------------------------------------------------
- *
- * Original code credits, from Horde library:
- *
- * The ZIP compression code is partially based on code from:
- * Eric Mueller <eric@themepark.com>
- * http://www.zend.com/codex.php?id=535&single=1
- *
- * Deins125 <webmaster@atlant.ru>
- * http://www.zend.com/codex.php?id=470&single=1
- *
- * The ZIP compression date code is partially based on code from
- * Peter Listiak <mlady@users.sourceforge.net>
- *
- * Copyright 2000-2006 Chuck Hagenbuch <chuck@horde.org>
- * Copyright 2002-2006 Michael Cochrane <mike@graftonhall.co.nz>
- * Copyright 2003-2006 Michael Slusarz <slusarz@horde.org>
- *
- * Additional Credits:
- *
- * Contains code from pclZip library [http://www.phpconcept.net/pclzip/index.en.php]
- *
- * Modifications for JoomlaPack:
- * Copyright 2007-2009 Nicholas K. Dionysopoulos <nikosdion@gmail.com>
- */
- defined('_JEXEC') or die('Restricted access');
-
- $config =& JoomlapackModelRegistry::getInstance();
- define("_JoomlapackPackerZIP_FORCE_FOPEN", $config->get('mnZIPForceOpen')); // Don't force use of fopen() to read uncompressed data in memory
- define("_JoomlapackPackerZIP_COMPRESSION_THRESHOLD", $config->get('mnZIPCompressionThreshold')); // Don't compress files over this size
- define("_JoomlapackPackerZIP_DIRECTORY_READ_CHUNK", $config->get('mnZIPDirReadChunk')); // How much data to read at once when finalizing ZIP archives
-
- class JoomlapackPackerZIP extends JoomlapackCUBEArchiver {
- /**
- * ZIP compression methods. JoomlaPack supports 0x0 (none) and 0x8 (deflated)
- *
- * @var array
- */
- var $_methods = array(
- 0x0 => 'None',
- 0x1 => 'Shrunk',
- 0x2 => 'Super Fast',
- 0x3 => 'Fast',
- 0x4 => 'Normal',
- 0x5 => 'Maximum',
- 0x6 => 'Imploded',
- 0x8 => 'Deflated'
- );
-
- /**
- * Beginning of central directory record.
- *
- * @var string
- */
- var $_ctrlDirHeader = "\x50\x4b\x01\x02";
-
- /**
- * End of central directory record.
- *
- * @var string
- */
- var $_ctrlDirEnd = "\x50\x4b\x05\x06";
-
- /**
- * Beginning of file contents.
- *
- * @var string
- */
- var $_fileHeader = "\x50\x4b\x03\x04";
-
- /**
- * The name of the temporary file holding the ZIP's Central Directory
- *
- * @var string
- */
- var $_ctrlDirFileName;
-
- /**
- * The name of the file holding the ZIP's data, which becomes the final archive
- *
- * @var string
- */
- var $_dataFileName;
-
- /**
- * The total number of files and directories stored in the ZIP archive
- *
- * @var integer
- */
- var $_totalFileEntries;
-
- /**
- * The chunk size for CRC32 calculations
- *
- * @var integer
- */
- var $JoomlapackPackerZIP_CHUNK_SIZE;
-
- // Variables for split ZIP
- /** @var bool Should I use Split ZIP? */
- var $_useSplitZIP = false;
-
- /** @var int Maximum fragment size, in bytes */
- var $_fragmentSize = 0;
-
- /** @var int Current fragment number */
- var $_currentFragment = 1;
-
- /** @var int Total number of fragments */
- var $_totalFragments = 1;
-
- /** @var string Archive full path without extension */
- var $_dataFileNameBase = '';
-
- // Variables for symlink target storage
-
- /** @var bool Should I store symlinks as such (no dereferencing?) */
- var $_symlink_store_target = false;
- // ------------------------------------------------------------------------
- // Implementation of abstract methods
- // ------------------------------------------------------------------------
- /**
- * Class constructor - initializes internal operating parameters
- *
- * @return JoomlapackPackerZIP The class instance
- */
- function JoomlapackPackerZIP()
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: New instance");
-
- // Get chunk override
- $registry =& JoomlapackModelRegistry::getInstance();
- if( $registry->get('mnArchiverChunk', 0) > 0 )
- {
- $this->JoomlapackPackerZIP_CHUNK_SIZE = JPPACK_CHUNK;
- }
- else
- {
- // Try to use as much memory as it's possible for CRC32 calculation
- $memLimit = ini_get("memory_limit");
- if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
- if ( ($memLimit == "") ) {
- // No memory limit, use 2Mb chunks (fairly large, right?)
- $this->JoomlapackPackerZIP_CHUNK_SIZE = 2097152;
- } elseif ( function_exists("memory_get_usage") ) {
- // 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
- $memLimit = $this->_return_bytes( $memLimit );
- $availableRAM = $memLimit - memory_get_usage();
-
- if ($availableRAM <= 0) {
- // Some PHP implemenations also return the size of the httpd footprint!
- if ( ($memLimit - 6291456) > 0 ) {
- $this->JoomlapackPackerZIP_CHUNK_SIZE = $memLimit - 6291456;
- } else {
- $this->JoomlapackPackerZIP_CHUNK_SIZE = 2097152;
- }
- } else {
- $this->JoomlapackPackerZIP_CHUNK_SIZE = $availableRAM * 0.5;
- }
- } else {
- // PHP can't report memory usage, use a conservative 512Kb
- $this->JoomlapackPackerZIP_CHUNK_SIZE = 524288;
- }
- }
-
- // NEW 2.3: Should we enable Split ZIP feature?
- $fragmentsize = $registry->get('splitpartsize', 0);
- if($fragmentsize >= 65536)
- {
- // If the fragment size is AT LEAST 64Kb, enable Split ZIP
- $this->_useSplitZIP = true;
- $this->_fragmentSize = $fragmentsize;
- // Enable CUBE that we have a multipart archive
- $cube =& JoomlapackCUBE::getInstance();
- $cube->updateMultipart(1); // Indicate that we have at least 1 part
- }
-
- // NEW 2.3: Should I use Symlink Target Storage?
- $dereferencesymlinks = $registry->get('dereferencesymlinks', true);
- if(!$dereferencesymlinks)
- {
- // We are told not to dereference symlinks. Are we on Windows?
- if (function_exists('php_uname'))
- {
- $isWindows = stristr(php_uname(), 'windows');
- }
- else
- {
- $isWindows = (DS == '\\');
- }
- // If we are not on Windows, enable symlink target storage
- $this->_symlink_store_target = !$isWindows;
- }
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "Chunk size for CRC is now " . $this->JoomlapackPackerZIP_CHUNK_SIZE . " bytes");
- }
- /**
- * Initialises the archiver class, creating the archive from an existent
- * installer's JPA archive.
- *
- * @param string $sourceJPAPath Absolute path to an installer's JPA archive
- * @param string $targetArchivePath Absolute path to the generated archive
- * @param array $options A named key array of options (optional). This is currently not supported
- * @access public
- */
- function initialize( $sourceJPAPath, $targetArchivePath, $options = array() )
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: initialize - archive $targetArchivePath");
- // Get names of temporary files
- $configuration =& JoomlapackModelRegistry::getInstance();
- $this->_ctrlDirFileName = tempnam( $configuration->getTemporaryDirectory(), 'jpzcd' );
- $this->_dataFileName = $targetArchivePath;
-
- // If we use splitting, initialize
- if($this->_useSplitZIP)
- {
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, "JoomlapackPackerZIP :: Split ZIP creation enabled");
- $this->_dataFileNameBase = dirname($targetArchivePath).DS.basename($targetArchivePath,'.zip');
- $this->_dataFileName = $this->_dataFileNameBase.'.z01';
- }
- jimport('joomla.filesystem.file');
- $tempname = JFile::getName($this->_ctrlDirFileName);
- JoomlapackCUBETempfiles::registerTempFile($tempname);
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: CntDir Tempfile = " . $this->_ctrlDirFileName);
- // Create temporary file
- if(!@touch( $this->_ctrlDirFileName ))
- {
- $this->setError(JText::_('CUBE_ZIPARCHIVER_CANTWRITETEMP'));
- return false;
- }
- // Try to kill the archive if it exists
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "JoomlapackPackerZIP :: Killing old archive");
- $fp = fopen( $this->_dataFileName, "wb" );
- if (!($fp === false)) {
- ftruncate( $fp,0 );
- fclose( $fp );
- } else {
- @unlink( $this->_dataFileName );
- }
- if(!@touch( $this->_dataFileName ))
- {
- $this->setError(JText::_('CUBE_ZIPARCHIVER_CANTWRITEZIP'));
- return false;
- }
-
- // On split archives, include the "Split ZIP" header, for PKZIP 2.50+ compatibility
- if($this->_useSplitZIP)
- {
- file_put_contents($this->_dataFileName, "\x50\x4b\x07\x08");
- // Also update the statistics table that we are a multipart archive...
- $cube =& JoomlapackCUBE::getInstance();
- $cube->updateMultipart(1);
- }
- parent::initialize($sourceJPAPath, $targetArchivePath, $options);
- }
- /**
- * Creates the ZIP file out of its pieces.
- * Official ZIP file format: http://www.pkware.com/appnote.txt
- *
- * @return boolean TRUE on success, FALSE on failure
- */
- function finalize()
- {
- // 1. Get size of central directory
- clearstatcache();
- $cdSize = @filesize( $this->_ctrlDirFileName );
- // 2. Append Central Directory to data file and remove the CD temp file afterwards
- $dataFP = fopen( $this->_dataFileName, "ab" );
- $cdFP = fopen( $this->_ctrlDirFileName, "rb" );
-
- if( $dataFP === false )
- {
- $this->setError('Could not open ZIP data file '.$this->_dataFileName.' for reading');
- return false;
- }
-
- if ( $cdFP === false ) {
- // Already glued, return
- fclose( $dataFP );
- return false;
- }
-
- if(!$this->_useSplitZIP)
- {
- while( !feof($cdFP) )
- {
- $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
- $this->_fwrite( $dataFP, $chunk );
- if($this->getError()) return;
- }
- unset( $chunk );
- fclose( $cdFP );
- }
- else
- // Special considerations for Split ZIP
- {
- // Calcuate size of Central Directory + EOCD records
- $total_cd_eocd_size = $cdSize + 22 + strlen($this->_comment);
- // Free space on the part
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if( ($free_space < $total_cd_eocd_size) && ($total_cd_eocd_size > 65536) )
- {
- // Not enough space on archive for CD + EOCD, will go on separate part
- // Create new final part
- if(!$this->_createNewPart(true))
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
- return false;
- }
- else
- {
- // Close the old data file
- fclose($dataFP);
- // Open data file for output
- $dataFP = @fopen( $this->_dataFileName, "ab");
- if ($dataFP === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- // Write the CD record
- while( !feof($cdFP) )
- {
- $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
- $this->_fwrite( $dataFP, $chunk );
- if($this->getError()) return;
- }
- unset( $chunk );
- fclose( $cdFP );
- }
- }
- else
- {
- // Glue the CD + EOCD on the same part if they fit, or anyway if they are less than 64Kb.
- // NOTE: WE *MUST NOT* CREATE FRAGMENTS SMALLER THAN 64Kb!!!!
- while( !feof($cdFP) )
- {
- $chunk = fread( $cdFP, _JoomlapackPackerZIP_DIRECTORY_READ_CHUNK );
- $this->_fwrite( $dataFP, $chunk );
- if($this->getError()) return;
- }
- unset( $chunk );
- fclose( $cdFP );
- }
- }
-
- JoomlapackCUBETempfiles::unregisterAndDeleteTempFile($this->_ctrlDirFileName);
- // 3. Write the rest of headers to the end of the ZIP file
- fclose( $dataFP );
- clearstatcache();
- $dataSize = @filesize( $this->_dataFileName ) - $cdSize;
- $dataFP = fopen( $this->_dataFileName, "ab" );
- if($dataFP === false)
- {
- $this->setError('Could not open '.$this->_dataFileName.' for append');
- return false;
- }
- $this->_fwrite( $dataFP, $this->_ctrlDirEnd );
- if($this->getError()) return;
- if($this->_useSplitZIP)
- {
- // Split ZIP files, enter relevant disk number information
- $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Number of this disk. */
- $this->_fwrite( $dataFP, pack('v', $this->_totalFragments - 1) ); /* Disk with central directory start. */
- }
- else
- {
- // Non-split ZIP files, the disk numbers MUST be 0
- $this->_fwrite( $dataFP, pack('V', 0) );
- }
- $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries "on this disk". */
- $this->_fwrite( $dataFP, pack('v', $this->_totalFileEntries) ); /* Total # of entries overall. */
- $this->_fwrite( $dataFP, pack('V', $cdSize) ); /* Size of central directory. */
- $this->_fwrite( $dataFP, pack('V', $dataSize) ); /* Offset to start of central dir. */
- $sizeOfComment = strlen($this->_comment);
- // 2.0.b2 -- Write a ZIP file comment
- $this->_fwrite( $dataFP, pack('v', $sizeOfComment) ); /* ZIP file comment length. */
- $this->_fwrite( $dataFP, $this->_comment );
- fclose( $dataFP );
- //sleep(2);
-
- // If Split ZIP and there is no .zip file, rename the last fragment to .ZIP
- if($this->_useSplitZIP)
- {
- $extension = substr($this->_dataFileName, -3);
- if($extension != '.zip')
- {
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, 'Renaming last ZIP part to .ZIP extension');
- $newName = $this->_dataFileNameBase.'.zip';
- if(!@rename($this->_dataFileName, $newName))
- {
- $this->setError('Could not rename last ZIP part to .ZIP extension.');
- return false;
- }
- $this->_dataFileName = $newName;
- }
- }
- // If Split ZIP and only one fragment, change the signature
- if($this->_useSplitZIP && ($this->_totalFragments == 1) )
- {
- $fp = fopen($this->_dataFileName, 'r+b');
- $this->_fwrite($fp, "\x50\x4b\x30\x30" );
- }
- // @todo If Split ZIP, update CUBE with total number of fragments
-
- if( function_exists('chmod') )
- {
- @chmod($this->_dataFileName, 0755);
- }
- return true;
- }
- /**
- * The most basic file transaction: add a single entry (file or directory) to
- * the archive.
- *
- * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
- * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
- * @param string $targetName The (relative) file name under which to store the file in the archive
- * @return True on success, false otherwise
- * @since 1.2.1
- * @access protected
- * @abstract
- */
- function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
- {
- static $configuration;
- $cube =& JoomlapackCUBE::getInstance();
-
- // Note down the starting disk number for Split ZIP archives
- if($this->_useSplitZIP)
- {
- $starting_disk_number_for_this_file = $this->_currentFragment - 1;
- }
- else
- {
- $starting_disk_number_for_this_file = 0;
- }
-
- if(!$configuration)
- {
- jpimport('models.registry', true);
- $configuration =& JoomlapackModelRegistry::getInstance();
- }
-
- // See if it's a directory
- $isDir = $isVirtual ? false : is_dir($sourceNameOrData);
- // See if it's a symlink (w/out dereference)
- $isSymlink = false;
- if($this->_symlink_store_target)
- {
- $isSymlink = is_link($sourceNameOrData);
- }
- // Get real size before compression
- if($isVirtual)
- {
- $fileSize = strlen($sourceNameOrData);
- } else {
- if($isSymlink)
- {
- $fileSize = strlen( @readlink($sourceNameOrData) );
- }
- else
- {
- $fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
- }
- }
- // Get last modification time to store in archive
- $ftime = $isVirtual ? time() : @filemtime( $sourceNameOrData );
- // Decide if we will compress
- if ($isDir || $isSymlink) {
- $compressionMethod = 0; // don't compress directories...
- } else {
- // Do we have plenty of memory left?
- $memLimit = ini_get("memory_limit");
- if (($memLimit == "") || ($fileSize >= _JoomlapackPackerZIP_COMPRESSION_THRESHOLD)) {
- // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
- $compressionMethod = ($fileSize <= _JoomlapackPackerZIP_COMPRESSION_THRESHOLD) ? 8 : 0;
- } elseif ( function_exists("memory_get_usage") ) {
- // 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
- $memLimit = $this->_return_bytes( $memLimit );
- $availableRAM = $memLimit - memory_get_usage();
- $compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 8 : 0;
- } else {
- // PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
- $compressionMethod = ($fileSize <= 524288) ? 8 : 0;;
- }
- }
- $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;
- $storedName = $targetName;
- if($isVirtual) JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, ' Virtual add:'.$storedName.' ('.$fileSize.') - '.$compressionMethod);
- /* "Local file header" segment. */
- $unc_len = &$fileSize; // File size
- if (!$isDir) {
- // Get CRC for regular files, not dirs
- if($isVirtual)
- {
- $crc = crc32($sourceNameOrData);
- }
- else
- {
- $crcCalculator = new CRC32CalcClass;
- $crc = $crcCalculator->crc32_file( $sourceNameOrData, $this->JoomlapackPackerZIP_CHUNK_SIZE ); // This is supposed to be the fast way to calculate CRC32 of a (large) file.
- unset( $crcCalculator );
- // If the file was unreadable, $crc will be false, so we skip the file
- if ($crc === false) {
- $cube->addWarning( 'Could not calculate CRC32 for '.$sourceNameOrData);
- return false;
- }
- }
- } else if($isSymlink) {
- $crc = crc32( @readlink($sourceNameOrData) );
- } else {
- // Dummy CRC for dirs
- $crc = 0;
- $storedName .= "/";
- $unc_len = 0;
- }
- // If we have to compress, read the data in memory and compress it
- if ($compressionMethod == 8) {
- // Get uncompressed data
- if( $isVirtual )
- {
- $udata =& $sourceNameOrData;
- }
- else
- {
- if( function_exists("file_get_contents") && (_JoomlapackPackerZIP_FORCE_FOPEN == false) ) {
- $udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
- } else {
- // Argh... the hard way!
- $udatafp = @fopen( $sourceNameOrData, "rb" );
- if( !($udatafp === false) ) {
- $udata = "";
- while( !feof($udatafp) ) {
- if($configuration->get("enableMySQLKeepalive", false))
- {
- list($usec, $sec) = explode(" ", microtime());
- $endTime = ((float)$usec + (float)$sec);
-
- if($endTime - $this->startTime > 0.5)
- {
- $this->startTime = $endTime;
- JoomlapackCUBETables::WriteVar('dummy', 1);
- }
- }
- $udata .= fread($udatafp, JPPACK_CHUNK);
- }
- fclose( $udatafp );
- } else {
- $udata = false;
- }
- }
- }
- if ($udata === FALSE) {
- // Unreadable file, skip it. Normally, we should have exited on CRC code above
- $cube->addWarning( JText::sprintf('CUBE_WARN_UNREADABLEFILE', $sourceNameOrData));
- return false;
- } else {
- // Proceed with compression
- $zdata = @gzcompress($udata);
- if ($zdata === false) {
- // If compression fails, let it behave like no compression was available
- $c_len = &$unc_len;
- $compressionMethod = 0;
- } else {
- unset( $udata );
- $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
- $c_len = strlen($zdata);
- }
- }
- } else {
- $c_len = $unc_len;
- }
- /* Get the hex time. */
- $dtime = dechex($this->_unix2DosTime($ftime));
- if( strlen($dtime) < 8 ) $dtime = "00000000";
- $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) .
- chr(hexdec($dtime[4] . $dtime[5])) .
- chr(hexdec($dtime[2] . $dtime[3])) .
- chr(hexdec($dtime[0] . $dtime[1]));
- // Get current data file size
- clearstatcache();
- $old_offset = @filesize( $this->_dataFileName );
-
- // If it's a split ZIP file, we've got to make sure that the header can fit in the part
- if($this->_useSplitZIP)
- {
- // Get header size, taking into account any extra header necessary
- $header_size = 30 + strlen($storedName);
- // Compare to free part space
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space <= $header_size)
- {
- // Not enough space on current part, create new part
- if(!$this->_createNewPart())
- {
- $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
- return false;
- }
- }
- }
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- $this->_fwrite( $fp, $this->_fileHeader ); /* Begin creating the ZIP data. */
- if(!$isSymlink)
- {
- $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract. */
- }
- else
- {
- $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract. */
- }
- $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag. */
- $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
- $this->_fwrite( $fp, $hexdtime ); /* Last modification time/date. */
- $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
- $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
- $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
- $this->_fwrite( $fp, pack('v', strlen($storedName)) ); /* Length of filename. */
- $this->_fwrite( $fp, pack('v', 0) ); /* Extra field length. */
- $this->_fwrite( $fp, $storedName ); /* File name. */
-
- /* "File data" segment. */
- if ($compressionMethod == 8) {
- // Just dump the compressed data
- if(!$this->_useSplitZIP)
- {
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
- else
- {
- // Split ZIP. Check if we need to split the part in the middle of the data.
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= strlen($zdata) )
- {
- // Write in one part
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
- else
- {
- $bytes_left = strlen($zdata);
-
- while($bytes_left > 0)
- {
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
-
- // Split between parts - Write a part
- $this->_fwrite( $fp, $zdata, $free_space );
- if($this->getError()) return;
-
- // Get the rest of the data
- $bytes_left = strlen($zdata) - $free_space;
-
- if($bytes_left > 0)
- {
- // Create new part
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
- return false;
- }
- else
- {
- // Close the old data file
- fclose($fp);
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- }
- $zdata = substr($zdata, -$bytes_left);
- }
- }
- }
- }
- unset( $zdata );
- } elseif ( !($isDir || $isSymlink) ) {
- // Virtual file, just write the data!
- if( $isVirtual )
- {
- // Just dump the data
- if(!$this->_useSplitZIP)
- {
- $this->_fwrite( $fp, $sourceNameOrData );
- if($this->getError()) return;
- }
- else
- {
- // Split ZIP. Check if we need to split the part in the middle of the data.
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= strlen($sourceNameOrData) )
- {
- // Write in one part
- $this->_fwrite( $fp, $sourceNameOrData );
- if($this->getError()) return;
- }
- else
- {
- $bytes_left = strlen($sourceNameOrData);
-
- while($bytes_left > 0)
- {
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- // Split between parts - Write first part
- $this->_fwrite( $fp, $sourceNameOrData, $free_space );
- if($this->getError()) return;
- // Get the rest of the data
- $rest_size = strlen($sourceNameOrData) - $free_space;
- if($rest_size > 0)
- {
- // Create new part if required
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
- return false;
- }
- else
- {
- // Close the old data file
- fclose($fp);
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- }
- // Get the rest of the compressed data
- $zdata = substr($sourceNameOrData, -$rest_size);
- }
- $bytes_left = $rest_size;
- }
- }
- }
- }
- else
- {
- // Copy the file contents, ignore directories
- $zdatafp = @fopen( $sourceNameOrData, "rb" );
- if( $zdatafp === FALSE )
- {
- $cube->addWarning( JText::sprintf('CUBE_WARN_UNREADABLEFILE', $sourceNameOrData));
- return false;
- }
- else
- {
- if(!$this->_useSplitZIP)
- {
- // For non Split ZIP, just dump the file very fast
- while( !feof($zdatafp) ) {
- $zdata = fread($zdatafp, JPPACK_CHUNK);
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
- }
- else
- {
- // Split ZIP - Do we have enough space to host the whole file?
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
- if($free_space >= $unc_len)
- {
- // Yes, it will fit inside this part, do quick copy
- while( !feof($zdatafp) ) {
- $zdata = fread($zdatafp, JPPACK_CHUNK);
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
- }
- else
- {
-
- // No, we'll have to split between parts. We'll loop until we run
- // out of space.
- $bytes_left = $unc_len;
-
- while( ($bytes_left > 0) && (!feof($zdatafp)) )
- {
- // No, we'll have to split between parts. Write the first part
- // Find optimal chunk size
- clearstatcache();
- $current_part_size = @filesize($this->_dataFileName);
- $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
-
- $chunk_size_primary = min(JPPACK_CHUNK, $free_space);
- if($chunk_size_primary <= 0) $chunk_size_primary = max(JPPACK_CHUNK, $free_space);
- // Calculate if we have to read some more data (smaller chunk size)
- // and how many times we must read w/ the primary chunk size
- $chunk_size_secondary = $free_space % $chunk_size_primary;
- $loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
- // Read and write with the primary chunk size
- for( $i = 1; $i <= $loop_times; $i++ )
- {
- $zdata = fread($zdatafp, $chunk_size_primary);
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
- // Read and write w/ secondary chunk size, if non-zero
- if($chunk_size_secondary > 0)
- {
- $zdata = fread($zdatafp, $chunk_size_secondary);
- $this->_fwrite( $fp, $zdata );
- if($this->getError()) return;
- }
-
- // Create new ZIP part, but only if we'll have more data to write
- if(!feof($zdatafp))
- {
- // Create new ZIP part
- if(!$this->_createNewPart())
- {
- // Die if we couldn't create the new part
- $this->setError('Could not create new ZIP part file '.basename($this->_dataFileName));
- return false;
- }
- else
- {
- // Close the old data file
- fclose($fp);
- // Open data file for output
- $fp = @fopen( $this->_dataFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open archive file {$this->_dataFileName} for append!");
- return false;
- }
- }
- }
-
- } // end while
-
- }
- }
- fclose( $zdatafp );
- }
- }
- }
- elseif($isSymlink)
- {
- $this->_fwrite($fp, @readlink($sourceNameOrData) );
- }
- // Done with data file.
- fclose( $fp );
- // Open the central directory file for append
- $fp = @fopen( $this->_ctrlDirFileName, "ab");
- if ($fp === false)
- {
- $this->setError("Could not open Central Directory temporary file for append!");
- return false;
- }
- $this->_fwrite( $fp, $this->_ctrlDirHeader );
- if(!$isSymlink)
- {
- $this->_fwrite( $fp, "\x00\x00" ); /* Version made by. */
- $this->_fwrite( $fp, "\x14\x00" ); /* Version needed to extract */
- $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag */
- $this->_fwrite( $fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00" ); /* Compression method. */
- }
- else
- {
- // Symlinks get special treatment
- $this->_fwrite( $fp, "\x14\x03" ); /* Version made by. */
- $this->_fwrite( $fp, "\x0a\x03" ); /* Version needed to extract */
- $this->_fwrite( $fp, "\x00\x00" ); /* General purpose bit flag */
- $this->_fwrite( $fp, "\x00\x00" ); /* Compression method. */
- }
-
- $this->_fwrite( $fp, $hexdtime ); /* Last mod time/date. */
- $this->_fwrite( $fp, pack('V', $crc) ); /* CRC 32 information. */
- $this->_fwrite( $fp, pack('V', $c_len) ); /* Compressed filesize. */
- $this->_fwrite( $fp, pack('V', $unc_len) ); /* Uncompressed filesize. */
- $this->_fwrite( $fp, pack('v', strlen($storedName)) ); /* Length of filename. */
- $this->_fwrite( $fp, pack('v', 0 ) ); /* Extra field length. */
- $this->_fwrite( $fp, pack('v', 0 ) ); /* File comment length. */
- $this->_fwrite( $fp, pack('v', $starting_disk_number_for_this_file ) ); /* Disk number start. */
- $this->_fwrite( $fp, pack('v', 0 ) ); /* Internal file attributes. */
- if(!$isSymlink)
- {
- $this->_fwrite( $fp, pack('V', $isDir ? 0x41FF0010 : 0xFE49FFE0) ); /* External file attributes - 'archive' bit set. */
- }
- else
- {
- // For SymLinks we store UNIX file attributes
- $this->_fwrite( $fp, "\x20\x80\xFF\xA1" ); /* External file attributes for Symlink. */
- }
- $this->_fwrite( $fp, pack('V', $old_offset) ); /* Relative offset of local header. */
- $this->_fwrite( $fp, $storedName ); /* File name. */
- /* Optional extra field, file comment goes here. */
- // Finished with Central Directory
- fclose( $fp );
- // Finaly, increase the file counter by one
- $this->_totalFileEntries++;
- // ... and return TRUE = success
- return TRUE;
- }
- // ------------------------------------------------------------------------
- // Archiver-specific utility functions
- // ------------------------------------------------------------------------
- /**
- * Converts a UNIX timestamp to a 4-byte DOS date and time format
- * (date in high 2-bytes, time in low 2-bytes allowing magnitude
- * comparison).
- *
- * @access private
- *
- * @param integer $unixtime The current UNIX timestamp.
- *
- * @return integer The current date in a 4-byte DOS format.
- */
- function _unix2DOSTime($unixtime = null)
- {
- $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
- if ($timearray['year'] < 1980) {
- $timearray['year'] = 1980;
- $timearray['mon'] = 1;
- $timearray['mday'] = 1;
- $timearray['hours'] = 0;
- $timearray['minutes'] = 0;
- $timearray['seconds'] = 0;
- }
- return (($timearray['year'] - 1980) << 25) |
- ($timearray['mon'] << 21) |
- ($timearray['mday'] << 16) |
- ($timearray['hours'] << 11) |
- ($timearray['minutes'] << 5) |
- ($timearray['seconds'] >> 1);
- }
-
- function _createNewPart($finalPart = false)
- {
- $this->_totalFragments++;
- $this->_currentFragment = $this->_totalFragments;
- if($finalPart)
- {
- $this->_dataFileName = $this->_dataFileNameBase.'.zip';
- }
- else
- {
- $this->_dataFileName = $this->_dataFileNameBase.'.z'.sprintf('%02d', $this->_currentFragment);
- }
- JoomlapackLogger::WriteLog(_JP_LOG_INFO, 'Creating new ZIP part #'.$this->_currentFragment.', file '.$this->_dataFileName);
- // Inform CUBE that we have chenged the multipart number
- $cube =& JoomlapackCUBE::getInstance();
- $cube->updateMultipart($this->_totalFragments);
- // Try to remove any existing file
- @unlink($this->_dataFileName);
- // Touch the new file
- return @touch($this->_dataFileName);
- }
- }
- // ===================================================================================================
-
- /**
- * A handy class to abstract the calculation of CRC32 of files under various
- * server conditions and versions of PHP.
- * @access private
- */
- class CRC32CalcClass
- {
- var $startTime = 0;
-
- /**
- * Returns the CRC32 of a file, selecting the more appropriate algorithm.
- *
- * @param string $filename Absolute path to the file being processed
- * @param integer $JoomlapackPackerZIP_CHUNK_SIZE Obsoleted
- * @return integer The CRC32 in numerical form
- */
- function crc32_file( $filename, $JoomlapackPackerZIP_CHUNK_SIZE )
- {
- static $configuration;
-
- if(!$configuration)
- {
- jpimport('models.registry', true);
- $configuration =& JoomlapackModelRegistry::getInstance();
- }
-
- // Keep-alive before CRC32 calculation
- if($configuration->get("enableMySQLKeepalive", false))
- {
- list($usec, $sec) = explode(" ", microtime());
- $endTime = ((float)$usec + (float)$sec);
-
- if($endTime - $this->startTime > 0.5)
- {
- $this->startTime = $endTime;
- JoomlapackCUBETables::WriteVar('dummy', 1);
- }
- }
-
- if( function_exists("hash_file") )
- {
- $res = $this->crc32_file_php512( $filename );
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP512]" );
- }
- else if ( function_exists("file_get_contents") && ( @filesize($filename) <= $JoomlapackPackerZIP_CHUNK_SIZE ) ) {
- $res = $this->crc32_file_getcontents( $filename );
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [GETCONTENTS]" );
- } else {
- $res = $this->crc32_file_php4($filename, $JoomlapackPackerZIP_CHUNK_SIZE);
- JoomlapackLogger::WriteLog(_JP_LOG_DEBUG, "File $filename - CRC32 = " . dechex($res) . " [PHP4]" );
- }
-
- if ($res === FALSE) {
- $cube =& JoomlapackCUBE::getInstance();
- $cube->addWarning( "File $filename - NOT READABLE: CRC32 IS WRONG!" );
- }
- return $res;
- }
-
- /**
- * Very efficient CRC32 calculation for PHP 5.1.2 and greater, requiring
- * the 'hash' PECL extension
- *
- * @param string $filename Absolute filepath
- * @return integer The CRC32
- * @access private
- */
- function crc32_file_php512($filename)
- {
- // Fix 2.4: CRC32 checksum was wrongfully inverted. Why?!
- return hexdec( @hash_file('crc32b', $filename, false ) );
- }
-
- /**
- * A compatible CRC32 calculation using file_get_contents, utilizing immense
- * ammounts of RAM
- *
- * @param string $filename
- * @return integer
- * @access private
- */
- function crc32_file_getcontents($filename)
- {
- return crc32( @file_get_contents($filename) );
- }
-
- /**
- * There used to be a workaround for large files under PHP4. However, it never
- * really worked, so it is removed and a warning is posted instead.
- *
- * @param string $filename
- * @param integer $JoomlapackPackerZIP_CHUNK_SIZE
- * @return integer
- */
- function crc32_file_php4($filename, $JoomlapackPackerZIP_CHUNK_SIZE)
- {
- $cube =& JoomlapackCUBE::getInstance();
- $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." );
- return 0;
- }
- }