/** * Writes an encrypted block to the archive * @param resource $fp The file pointer resource of the file to write to * @param string $data Raw binary data to encrypt and write */ private function _writeEncryptedBlock(&$fp, $data) { $decryptedSize = akstringlen($data); $data = AEUtilEncrypt::AESEncryptCBC($data, $this->password, 128); $encryptedSize = akstringlen($data); // Do we have enough space to store the 8 byte header? if ($this->_useSplitZIP) { // 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 <= 8) { @fclose($fp); // Not enough space on current part, create new part if (!$this->_createNewPart()) { $this->setError('Could not create new JPS 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; } } } else { $free_space = $encryptedSize + 8; } // Write the header $this->_fwrite($fp, pack('V', $encryptedSize) . pack('V', $decryptedSize)); if ($this->getError()) { return; } $free_space -= 8; // Do we have enough space to write the data in one part? if ($free_space >= $encryptedSize) { $this->_fwrite($fp, $data); if ($this->getError()) { return; } } else { // Split between parts - Write first part $firstPart = substr($data, 0, $free_space); $secondPart = substr($data, $free_space); if (md5($firstPart . $secondPart) != md5($data)) { die('DEBUG -- Multibyte character problems!'); die; } $this->_fwrite($fp, $firstPart, $free_space); if ($this->getError()) { @fclose($fp); return; } // Create new part if (!$this->_createNewPart()) { // Die if we couldn't create the new part $this->setError('Could not create new JPA part file ' . basename($this->_dataFileName)); @fclose($fp); 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; } } // Write the rest of the data $this->_fwrite($fp, $secondPart, $encryptedSize - $free_space); } }
/** * 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 */ protected function _addFile($isVirtual, &$sourceNameOrData, $targetName) { static $configuration; $isDir = false; $isSymlink = false; if (is_null($isVirtual)) { $isVirtual = false; } $compressionMethod = 0; if ($isVirtual) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding {$targetName} to archive (virtual data)"); } else { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding {$targetName} to archive (source: {$sourceNameOrData})"); } if (!$configuration) { $configuration =& AEFactory::getConfiguration(); } $timer =& AEFactory::getTimer(); // Initialize archive file pointer $fp = null; // Initialize inode change timestamp $filectime = 0; if (!$configuration->get('volatile.engine.archiver.processingfile', false)) { // Uncache data -- WHY DO THAT?! /** $configuration->set('volatile.engine.archiver.sourceNameOrData', null); $configuration->set('volatile.engine.archiver.unc_len', null); $configuration->set('volatile.engine.archiver.resume', null); $configuration->set('volatile.engine.archiver.processingfile',false); /**/ // 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 && !$isVirtual) { $isSymlink = is_link($sourceNameOrData); } // Get real size before compression if ($isVirtual) { $fileSize = akstringlen($sourceNameOrData); $filectime = time(); } else { if ($isSymlink) { $fileSize = akstringlen(@readlink($sourceNameOrData)); } else { // Is the file readable? if (!is_readable($sourceNameOrData) && !$isDir) { // Unreadable files won't be recorded in the archive file $this->setWarning('Unreadable file ' . $sourceNameOrData . '. Check permissions'); return false; } // Get the filesize $fileSize = $isDir ? 0 : @filesize($sourceNameOrData); $filectime = $isDir ? 0 : @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 (strstr($memLimit, 'M')) { $memLimit = (int) $memLimit * 1048576; } elseif (strstr($totalRAM, 'K')) { $memLimit = (int) $memLimit * 1024; } elseif (strstr($memLimit, 'G')) { $memLimit = (int) $memLimit * 1073741824; } else { $memLimit = (int) $memLimit; } if (is_numeric($memLimit) && $memLimit < 0) { $memLimit = ""; } // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb! if ($memLimit == "" || $fileSize >= _AKEEBA_COMPRESSION_THRESHOLD) { // No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out) $compressionMethod = $fileSize <= _AKEEBA_COMPRESSION_THRESHOLD ? 1 : 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 ? 1 : 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 ? 1 : 0; } } $compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0; $storedName = $targetName; /* "Entity Description BLock" segment. */ $unc_len =& $fileSize; // File size $storedName .= $isDir ? "/" : ""; if ($compressionMethod == 1) { if ($isVirtual) { $udata =& $sourceNameOrData; } else { // Get uncompressed data $udata = @file_get_contents($sourceNameOrData); // PHP > 4.3.0 saves us the trouble } if ($udata === FALSE) { // Unreadable file, skip it. $this->setWarning('Unreadable file ' . $sourceNameOrData . '. Check permissions'); 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, -4), 2); $c_len = akstringlen($zdata); } } } else { $c_len = $unc_len; // Test for unreadable files if (!$isVirtual && !$isSymlink && !$isDir) { $myfp = @fopen($sourceNameOrData, 'rb'); if ($myfp === false) { // Unreadable file, skip it. $this->setWarning('Unreadable file ' . $sourceNameOrData . '. Check permissions'); return false; } @fclose($myfp); } } $this->_compressedSize += $c_len; // Update global data $this->_uncompressedSize += $fileSize; // Update global data $this->_fileCount++; // Get file permissions $perms = 0755; if (!$isVirtual) { if (@file_exists($sourceNameOrData)) { if (@is_file($sourceNameOrData) || @is_link($sourceNameOrData)) { if (@is_readable($sourceNameOrData)) { $perms = @fileperms($sourceNameOrData); } } } } // Calculate Entity Description Block length $blockLength = 21 + akstringlen($storedName); if ($filectime > 0) { $blockLength += 8; } // If we need to store the file mod date // Get file type if (!$isDir && !$isSymlink) { $fileType = 1; } elseif ($isSymlink) { $fileType = 2; } elseif ($isDir) { $fileType = 0; } // If it's a split ZIP file, we've got to make sure that the header can fit in the part if ($this->_useSplitZIP) { // 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 <= $blockLength) { // Not enough space on current part, create new part if (!$this->_createNewPart()) { $this->setError('Could not create new JPA 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); // Entity Description Block header if ($this->getError()) { return false; } $this->_fwrite($fp, pack('v', $blockLength)); // Entity Description Block header length $this->_fwrite($fp, pack('v', akstringlen($storedName))); // Length of entity path $this->_fwrite($fp, $storedName); // Entity path $this->_fwrite($fp, pack('C', $fileType)); // Entity type $this->_fwrite($fp, pack('C', $compressionMethod)); // Compression method $this->_fwrite($fp, pack('V', $c_len)); // Compressed size $this->_fwrite($fp, pack('V', $unc_len)); // Uncompressed size $this->_fwrite($fp, pack('V', $perms)); // Entity permissions // Timestamp Extra Field, only for files if ($filectime > 0) { $this->_fwrite($fp, ""); // Extra Field Identifier $this->_fwrite($fp, pack('v', 8)); // Extra Field Length $this->_fwrite($fp, pack('V', $filectime)); // Timestamp } // Cache useful information about the file if (!$isDir && !$isSymlink && !$isVirtual) { $configuration->set('volatile.engine.archiver.unc_len', $unc_len); $configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData); } } else { // If we are continuing file packing we have an uncompressed, non-virtual file. // We need to set up these variables so as not to throw any PHP notices. $isDir = false; $isSymlink = false; $isVirtual = false; $compressionMethod = 0; // Create a file pointer to the archive file $fp = @fopen($this->_dataFileName, "ab"); if ($fp === false) { $this->setError("Could not open archive file '{$this->_dataFileName}' for append!"); return false; } } /* "File data" segment. */ if ($compressionMethod == 1) { if (!$this->_useSplitZIP) { // Just dump the compressed data $this->_fwrite($fp, $zdata); if ($this->getError()) { @fclose($fp); return false; } } 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 >= akstringlen($zdata)) { // Write in one part $this->_fwrite($fp, $zdata); if ($this->getError()) { @fclose($fp); return false; } } else { $bytes_left = akstringlen($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 first part $this->_fwrite($fp, $zdata, min(akstringlen($zdata), $free_space)); if ($this->getError()) { @fclose($fp); return false; } // Get the rest of the data $bytes_left = akstringlen($zdata) - $free_space; if ($bytes_left > 0) { // Create new part @fclose($fp); if (!$this->_createNewPart()) { // Die if we couldn't create the new part $this->setError('Could not create new JPA 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) { if ($isVirtual) { if (!$this->_useSplitZIP) { // Just dump the data $this->_fwrite($fp, $sourceNameOrData); if ($this->getError()) { @fclose($fp); return false; } } else { // Split JPA. 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 >= akstringlen($sourceNameOrData)) { // Write in one part $this->_fwrite($fp, $sourceNameOrData); if ($this->getError()) { return false; } } else { $bytes_left = akstringlen($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, min(akstringlen($sourceNameOrData), $free_space)); if ($this->getError()) { @fclose($fp); return false; } // Get the rest of the data $rest_size = akstringlen($sourceNameOrData) - $free_space; if ($rest_size > 0) { // Create new part if (!$this->_createNewPart()) { // Die if we couldn't create the new part $this->setError('Could not create new JPA part file ' . basename($this->_dataFileName)); @fclose($fp); 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($sourceNameOrData, -$rest_size); } $bytes_left = $rest_size; } // end while } } } else { // IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data // Load cached data if we're resumming file packing if ($configuration->get('volatile.engine.archiver.processingfile', false)) { $sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', ''); $unc_len = $configuration->get('volatile.engine.archiver.unc_len', 0); $resume = $configuration->get('volatile.engine.archiver.resume', 0); } // Copy the file contents, ignore directories $zdatafp = @fopen($sourceNameOrData, "rb"); if ($zdatafp === FALSE) { $this->setWarning('Unreadable file ' . $sourceNameOrData . '. Check permissions'); @fclose($fp); return false; } else { // Seek to the resume point if required if ($configuration->get('volatile.engine.archiver.processingfile', false)) { // Seek to new offset $seek_result = @fseek($zdatafp, $resume); if ($seek_result === -1) { // What?! We can't resume! $this->setError(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData)); @fclose($zdatafp); @fclose($fp); return false; } // Doctor the uncompressed size to match the remainder of the data $unc_len = $unc_len - $resume; } if (!$this->_useSplitZIP) { while (!feof($zdatafp) && $timer->getTimeLeft() > 0 && $unc_len > 0) { $zdata = fread($zdatafp, AKEEBA_CHUNK); $this->_fwrite($fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK)); $unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK); if ($this->getError()) { @fclose($zdatafp); @fclose($fp); return false; } } // WARNING!!! The extra $unc_len != 0 check is necessary as PHP won't reach EOF for 0-byte files. if (!feof($zdatafp) && $unc_len != 0) { // We have to break, or we'll time out! $resume = @ftell($zdatafp); $configuration->set('volatile.engine.archiver.resume', $resume); $configuration->set('volatile.engine.archiver.processingfile', true); @fclose($zdatafp); @fclose($fp); return true; } } else { // Split JPA - 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) && $timer->getTimeLeft() > 0 && $unc_len > 0) { $zdata = fread($zdatafp, AKEEBA_CHUNK); $this->_fwrite($fp, $zdata, min(akstringlen($zdata), AKEEBA_CHUNK)); //$unc_len -= min(akstringlen($zdata), AKEEBA_CHUNK); $unc_len -= AKEEBA_CHUNK; if ($this->getError()) { @fclose($zdatafp); @fclose($fp); return false; } } //if(!feof($zdatafp) && ($unc_len != 0)) if (!feof($zdatafp) && $unc_len > 0) { // We have to break, or we'll time out! $resume = @ftell($zdatafp); $configuration->set('volatile.engine.archiver.resume', $resume); $configuration->set('volatile.engine.archiver.processingfile', true); @fclose($zdatafp); @fclose($fp); return true; } } else { // No, we'll have to split between parts. We'll loop until we run // out of space. while (!feof($zdatafp) && $timer->getTimeLeft() > 0) { clearstatcache(); $current_part_size = @filesize($this->_dataFileName); $free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size); // Find optimal chunk size $chunk_size_primary = min(AKEEBA_CHUNK, $free_space); if ($chunk_size_primary <= 0) { $chunk_size_primary = max(AKEEBA_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, min(akstringlen($zdata), $chunk_size_primary)); //$unc_len -= min(akstringlen($zdata), $chunk_size_primary); $unc_len -= $chunk_size_primary; if ($this->getError()) { @fclose($zdatafp); @fclose($fp); return false; } // Do we have enough time to proceed? //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) { if (!feof($zdatafp) && $unc_len >= 0 && $timer->getTimeLeft() <= 0) { // No, we have to break, or we'll time out! $resume = @ftell($zdatafp); $configuration->set('volatile.engine.archiver.resume', $resume); $configuration->set('volatile.engine.archiver.processingfile', true); @fclose($zdatafp); @fclose($fp); return true; } } // 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, min(akstringlen($zdata), $chunk_size_secondary)); //$unc_len -= min(akstringlen($zdata), $chunk_size_secondary); $unc_len -= $chunk_size_secondary; if ($this->getError()) { @fclose($zdatafp); @fclose($fp); return false; } } // Do we have enough time to proceed? //if( (!feof($zdatafp)) && ($unc_len != 0) && ($timer->getTimeLeft() <= 0) ) { if (!feof($zdatafp) && $unc_len >= 0 && $timer->getTimeLeft() <= 0) { // No, we have to break, or we'll time out! $resume = @ftell($zdatafp); $configuration->set('volatile.engine.archiver.resume', $resume); $configuration->set('volatile.engine.archiver.processingfile', true); // ...and create a new part as well if (!$this->_createNewPart()) { // Die if we couldn't create the new part $this->setError('Could not create new JPA part file ' . basename($this->_dataFileName)); @fclose($zdatafp); @fclose($fp); return false; } // ...then, return @fclose($zdatafp); @fclose($fp); return true; } // Create new JPA part, but only if we'll have more data to write //if(!feof($zdatafp) && ($unc_len != 0) && ($unc_len > 0) ) if (!feof($zdatafp) && $unc_len > 0) { if (!$this->_createNewPart()) { // Die if we couldn't create the new part $this->setError('Could not create new JPA part file ' . basename($this->_dataFileName)); @fclose($zdatafp); @fclose($fp); return false; } else { // Close the old data file fclose($fp); // We have created the part. If the user asked for immediate post-proc, break step now. if ($configuration->get('engine.postproc.common.after_part', 0)) { $resume = @ftell($zdatafp); $configuration->set('volatile.engine.archiver.resume', $resume); $configuration->set('volatile.engine.archiver.processingfile', true); $configuration->set('volatile.breakflag', true); @fclose($zdatafp); @fclose($fp); return true; } // Open data file for output $fp = @fopen($this->_dataFileName, "ab"); if ($fp === false) { $this->setError("Could not open archive file {$this->_dataFileName} for append!"); @fclose($zdatafp); return false; } } } } // end while } } @fclose($zdatafp); } } } elseif ($isSymlink) { $this->_fwrite($fp, @readlink($sourceNameOrData)); } @fclose($fp); //AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "DEBUG -- Added $targetName to archive"); // Uncache data $configuration->set('volatile.engine.archiver.sourceNameOrData', null); $configuration->set('volatile.engine.archiver.unc_len', null); $configuration->set('volatile.engine.archiver.resume', null); $configuration->set('volatile.engine.archiver.processingfile', false); // ... and return TRUE = success return TRUE; }
/** * Process the file data of a link entry * @return bool */ private function processTypeLink() { // Does the file have any data, at all? if ($this->fileHeader->uncompressed == 0) { // No file data! $this->runState = AK_STATE_DATAREAD; return true; } // Read the mini header $binMiniHeader = fread($this->fp, 8); $reallyReadBytes = akstringlen($binMiniHeader); if ($reallyReadBytes < 8) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); // Retry reading the header $binMiniHeader = fread($this->fp, 8); $reallyReadBytes = akstringlen($binMiniHeader); // Still not enough data? If so, the archive is corrupt or missing parts. if ($reallyReadBytes < 8) { $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } else { // Nope. The archive is corrupt $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } // Read the encrypted data $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader); $toReadBytes = $miniHeader['encsize']; $data = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($data); if ($reallyReadBytes < $toReadBytes) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); // Read the rest of the data $toReadBytes -= $reallyReadBytes; $restData = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($data); if ($reallyReadBytes < $toReadBytes) { $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } $data .= $restData; } else { // Nope. The archive is corrupt $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } // Decrypt the data $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128); // Is the length of the decrypted data less than expected? $data_length = akstringlen($data); if ($data_length < $miniHeader['decsize']) { $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD')); return false; } // Trim the data $data = substr($data, 0, $miniHeader['decsize']); // Try to remove an existing file or directory by the same name if (file_exists($this->fileHeader->realFile)) { @unlink($this->fileHeader->realFile); @rmdir($this->fileHeader->realFile); } // Remove any trailing slash if (substr($this->fileHeader->realFile, -1) == '/') { $this->fileHeader->realFile = substr($this->fileHeader->realFile, 0, -1); } // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :( if (!AKFactory::get('kickstart.setup.dryrun', '0')) { @symlink($data, $this->fileHeader->realFile); } $this->runState = AK_STATE_DATAREAD; return true; // No matter if the link was created! }
private function processTypeFileCompressedSimple() { $timer = AKFactory::getTimer(); // Files are being processed in small chunks, to avoid timeouts if ($this->dataReadLength == 0 && !AKFactory::get('kickstart.setup.dryrun', '0')) { // Before processing file data, ensure permissions are adequate $this->setCorrectPermissions($this->fileHeader->file); } // Open the output file if (!AKFactory::get('kickstart.setup.dryrun', '0')) { // Open the output file $outfp = @fopen($this->fileHeader->realFile, 'wb'); // Can we write to the file? $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file); if ($outfp === false && !$ignore) { // An error occured $this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile)); return false; } } // Does the file have any data, at all? if ($this->fileHeader->uncompressed == 0) { // No file data! if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fclose($outfp); } } $this->runState = AK_STATE_DATAREAD; return true; } $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength; // Loop while there's data to write and enough time to do it while ($leftBytes > 0 && $timer->getTimeLeft() > 0) { // Read the mini header $binMiniHeader = fread($this->fp, 8); $reallyReadBytes = akstringlen($binMiniHeader); if ($reallyReadBytes < 8) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); // Retry reading the header $binMiniHeader = fread($this->fp, 8); $reallyReadBytes = akstringlen($binMiniHeader); // Still not enough data? If so, the archive is corrupt or missing parts. if ($reallyReadBytes < 8) { $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } else { // Nope. The archive is corrupt $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } // Read the encrypted data $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader); $toReadBytes = $miniHeader['encsize']; $data = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($data); if ($reallyReadBytes < $toReadBytes) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); // Read the rest of the data $toReadBytes -= $reallyReadBytes; $restData = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($restData); if ($reallyReadBytes < $toReadBytes) { $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } if (akstringlen($data) == 0) { $data = $restData; } else { $data .= $restData; } } else { // Nope. The archive is corrupt $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } // Decrypt the data $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128); // Is the length of the decrypted data less than expected? $data_length = akstringlen($data); if ($data_length < $miniHeader['decsize']) { $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD')); return false; } // Trim the data $data = substr($data, 0, $miniHeader['decsize']); // Decompress $data = gzinflate($data); $unc_len = akstringlen($data); // Write the decrypted data if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fwrite($outfp, $data, akstringlen($data)); } } // Update the read length $this->dataReadLength += $unc_len; $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength; } // Close the file pointer if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fclose($outfp); } } // Was this a pre-timeout bail out? if ($leftBytes > 0) { $this->runState = AK_STATE_DATA; } else { // Oh! We just finished! $this->runState = AK_STATE_DATAREAD; $this->dataReadLength = 0; } return true; }