/** * 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 = (double) $usec + (double) $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, ""); /* Version needed to extract. */ } else { $this->_fwrite($fp, "\n"); /* Version needed to extract. */ } $this->_fwrite($fp, ""); /* General purpose bit flag. */ $this->_fwrite($fp, $compressionMethod == 8 ? "" : ""); /* 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, ""); /* Version made by. */ $this->_fwrite($fp, ""); /* Version needed to extract */ $this->_fwrite($fp, ""); /* General purpose bit flag */ $this->_fwrite($fp, $compressionMethod == 8 ? "" : ""); /* Compression method. */ } else { // Symlinks get special treatment $this->_fwrite($fp, ""); /* Version made by. */ $this->_fwrite($fp, "\n"); /* Version needed to extract */ $this->_fwrite($fp, ""); /* General purpose bit flag */ $this->_fwrite($fp, ""); /* 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 : 0.0)); /* External file attributes - 'archive' bit set. */ } else { // For SymLinks we store UNIX file attributes $this->_fwrite($fp, " €ÿ¡"); /* 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; }
/** * 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) { // See if it's a directory $isDir = $isVirtual ? false : is_dir($sourceNameOrData); // Get real size before compression if ($isVirtual) { $fileSize = strlen($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) { $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) { $this->setWarning('Could not calculate CRC32 for ' . $sourceNameOrData); return false; } } } 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)) { $udata .= fread($udatafp, 524288); } fclose($udatafp); } else { $udata = false; } } } if ($udata === FALSE) { // Unreadable file, skip it. Normally, we should have exited on CRC code above $this->setWarning('Unreadable file ' . $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)); $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); // 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. */ $this->_fwrite($fp, ""); /* Version needed to extract. */ $this->_fwrite($fp, ""); /* General purpose bit flag. */ $this->_fwrite($fp, $compressionMethod == 8 ? "" : ""); /* 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 $this->_fwrite($fp, $zdata); if ($this->hasError()) { return; } unset($zdata); } elseif (!$isDir) { if ($isVirtual) { $this->_fwrite($fp, $sourceNameOrData); if ($this->hasError()) { return; } } else { // Copy the file contents, ignore directories $zdatafp = @fopen($sourceNameOrData, "rb"); while (!feof($zdatafp)) { $zdata = fread($zdatafp, 524288); $this->_fwrite($fp, $zdata); if ($this->hasError()) { return; } } fclose($zdatafp); } } // 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); $this->_fwrite($fp, ""); /* Version made by. */ $this->_fwrite($fp, ""); /* Version needed to extract */ $this->_fwrite($fp, ""); /* General purpose bit flag */ $this->_fwrite($fp, $compressionMethod == 8 ? "" : ""); /* 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', 0)); /* Disk number start. */ $this->_fwrite($fp, pack('v', 0)); /* Internal file attributes. */ $this->_fwrite($fp, pack('V', $isDir ? 0x41ff0010 : 0xfe49ffe0)); /* External file attributes - 'archive' bit set. */ $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; }