PHP Unzip bug makes WordPress Updates hang

I have been having problems with WordPress Updates for a while. No error messages, just the WordPress Update, or WordPress Plugin Update, hangs.

Then I found out that the WordPress backup plugin I am using, BackWPUp, failed if I configured it to use any compression.

Then I found out that the problems I had been having with random-seeming 500 Server Errors was from the WP-Super-Cache plugin configured with, hmm, compression.

WordPress Updates would create new folders, but no files in them.

I wrote a PHP program, outside of WordPress, to test if the problem was in WordPress, or in the version of PHP used on my server.

The problem is in PHP. The PHP unzip routines lock up. No error message on screen, in the PHP log, or in the system log.

WordPress 3.6.0 and 3.6.1, Multi-Site, Apache, PHP Version 5.2.17, [HTTP_ACCEPT_ENCODING] => gzip, deflate

PHP itself has a bug in Zip/Unzip in old versions. Hangs without any error message displayed or in logs. Affects WordPress Update, and Plugin/Theme Updates, and any plugins that use compression (e.g. WP-Super-Cache or BackWPup)

PHP Change Log http://www.php.net/ChangeLog-5.php#5.3.4 says
PHP Version 5.3.4 released 09-Dec-2010 – Fixed crash in zip extract method

Note: PHP versions are numbered so version 5.3.10 is higher than 5.3.9 (5.3.1 is lower)


These PHP Unzip methods hang:
$zip->extractTo($path.'/folder') or copy("zip://".$zipFullPath.'#'.$filename, $destPath.$destName)

I have not found a direct PHP method of testing it, beyond observing the program hangs. Neither command returns a result, they just hang.

The .zip file is being opened, I can read the number of files in it, and read the file names. The .zip routines report the file is valid (as does 7-Zip on Windows), and the file is downloaded directly from WordPress.org, and other files hang the same way.

I recommend WordPress adding a test for PHP unzip routines working, and if not, use a WordPress substitute (the PHP unzip code from a newer version of PHP?).

Many of the “WordPress Update Hangs” problems reported for years, might disappear with this change.

Looks like this PHP bug might have been fixed in PHP Version 5.3.4 09-Dec-2010 – “Fixed crash in zip extract method” www.php.net/ChangeLog-5.php#5.3.4

My hosting account (LunarPages) already had PHP 5.3.24 (released 11-April-2013) installed, changing one line in my .htaccess file switched me to the newer version:
AddHandler application/x-httpd-php5 .php5 .php4 .php .php3 .php2 .phtml




Unzip (for WordPress, etc. Updates)





George's UnZip

IP: $ip, PHP Version:" .phpversion()."";

$file = $_GET["wpfile"];
// get the absolute path to $file
$path = pathinfo(realpath($file), PATHINFO_DIRNAME);
$zipFullPath = $path.'/'.pathinfo(realpath($file), PATHINFO_FILENAME).'.'.pathinfo(realpath($file), PATHINFO_EXTENSION);
$zipFolder = 'wordpress'; /* folder in the zip file, normally would extract to public_html/wordpress, I want to extract to public_html */
$folderNameLen = strlen($zipFolder) + 1;

$startDestFolder = '/';

echo "

Extracting the /$zipFolder/ folder of the zip file to $path

"; $groupName = get_current_user(); $groupID = getmyuid(); echo "

Group ID of current process: $groupID, Name: $groupName

"; $zip = new ZipArchive; $result = $zip->open($file, ZIPARCHIVE::CHECKCONS); if ($result === TRUE) { echo "

".$zip->numFiles." files total in zip file

"; $zip->close(); } else { echo "

$file could not be opened

"; exit; } // If the archive is broken(or just another file renamed to *.zip) the function will return error on httpd under windows, so it's good to check if the archive is ok with ZIPARCHIVE::CHECKCONS if ($result === TRUE) { $zip = new ZipArchive; $result = $zip->open($file); echo "

"; switch($result) { case TRUE : { echo " Opened Successfully, no errors in Zip file"; break; } case ZIPARCHIVE::ER_EXISTS : { echo " ER_EXISTS"; break; } case ZIPARCHIVE::ER_INCONS : { echo " ER_INCONS"; break; } case ZIPARCHIVE::ER_INVAL : { echo " ER_INVAL"; break; } case ZIPARCHIVE::ER_MEMORY : { echo " ER_MEMORY"; break; } case ZIPARCHIVE::ER_NOENT : { echo " ER_NOENT"; break; } case ZIPARCHIVE::ER_NOZIP : { echo " ER_NOZIP"; break; } case ZIPARCHIVE::ER_OPEN : { echo " ER_OPEN"; break; } case ZIPARCHIVE::ER_READ : { echo " ER_READ"; break; } case ZIPARCHIVE::ER_SEEK : { echo " ER_SEEK"; break; } default: echo " Unknown error"; } echo "

"; echo "

About to extract result via System Unzip, to $path/asdf (permission 744 works) :

"; $execute = "unzip -u -a $zipFullPath $zipFolder/* -d asdf "; /* ascii convert, update, directory for output*/ echo "

System command: $execute

"; $escaped_command = escapeshellcmd($execute); system($escaped_command,$retval); echo "

Retval: $retval

"; /* echo "

About to extract same file via PHP zip->extractTo, to $path/asdf :

"; // $result = $zip->extractTo($path.'/asdf'); $result = $zip->extractTo($path); echo "

Full extract result to $path/asdf : $result

"; */ function getFileOwnership($file){ global $docroot; $owner = fileowner($file); $stat = stat($file); if($stat && (stristr($docroot,"xampp") == FALSE) ) { $group = posix_getgrgid($stat[5]); $user = posix_getpwuid($stat[4]); return compact('user', 'group'); } else { return false; } } echo "

About to extract same file via PHP Unzip & single file copy, to $path:

"; echo "

(permission 755 fails, 775 gives 500 Server Error)

"; echo "

Note: Should extract same number of files, on some versions of PHP hangs before extracts a single file. Notice if says 'extracted', and notice if it says 'Done extracting'.

"; // extract it to the path we determined above $files = array(); for($i = 0; $i < $zip->numFiles; $i++) { $entry = $zip->getNameIndex($i); //Use strpos() to check if the entry name contains the directory we want to extract if (strpos($entry, "$zipFolder/") == 0 ) { /* remove starting wordpress folder, could be subfolders also called wordpress */ //Add the entry to our array if it in in our desired directory $newEntry = substr_replace($entry, '', 0 , $folderNameLen); if ($newEntry == '') continue; // $files[] = $newEntry; // $result = $zip->extractTo($path, array($zip->getNameIndex($i))); /* can't use newEntry, must use getNameIndex, in array */ $filename = $zip->getNameIndex($i); $fileinfo = pathinfo($filename); $destFolder = substr_replace($fileinfo['dirname'], '', 0 , $folderNameLen) . '/'; $destFolder = str_replace('//','/', $destFolder); $destName = $fileinfo['basename']; /* if ($destFolder !== $startDestFolder) { */ // echo " DestFolder:$destFolder, Entry $entry, $zipFullPath#$entry to Path:$path Folder:$destFolder Name:$destName "; if (strrpos($entry,'/',0) === strlen($entry) - 1 ) { $destFolder = $fileinfo['dirname'].'/'.$fileinfo['basename']; $destFolder = substr_replace($destFolder, '', 0 , $folderNameLen); $newPath = $path.'/'.$destFolder; $newPath = str_replace('//','/', $newPath); if ( file_exists($newPath) && filetype($newPath) == "dir" ) { echo " directory $newPath already made"; } else { if (!mkdir($newPath, 0755, TRUE) ) { echo " Failed to create folders $newPath"; } else { echo " New Folder $newPath Created"; } } echo " $newPath Permissions: ". substr(sprintf('%o', fileperms($newPath)), -4); } else { $newPath = $path.'/'.$destFolder; $newPath = str_replace('//','/', $newPath); $fileOwner = getFileOwnership($newPath); $fileOwnerUserName = $fileOwner['user']['name']; $fileOwnerUserID = $fileOwner['user']['gid']; $fileOwnerGroupName = $fileOwner['group']['name']; $fileOwnerGroupID = $fileOwner['group']['gid']; echo " Extracting $zipFullPath#$entry
to Path:$newPath
(Permissions: ". substr(sprintf('%o', fileperms($newPath)), -4). ") "; echo " Ownership: $fileOwnerUserName Group: $fileOwnerGroupName ID: $fileOwnerUserID GroupID: $fileOwnerGroupID"; echo " Name:$destName"; $result = copy("zip://".$zipFullPath.'#'.$entry, $newPath.$destName); if ($result === TRUE) { echo " extracted "; $stat = $zip->statIndex($i); $oldCrc = $stat['crc']; $newCrc = hexdec(hash_file("crc32b", $newPath.$destName)); // Have to test both cases as the unsigned CRC from within the zip might appear negative as a signed int. if($newCrc !== $oldCrc && ($oldCrc + 4294967296) !== $newCrc) { echo "The files don't match!"; } else { echo " (match)"; } } else { echo " $entry NOT extracted to ".$newPath.$destName; } } } echo "

"; } echo "

Done extraction to $path

"; } echo "

\n"; $result = $zip->close(); if ($result === TRUE) { echo "

Extraction successful, $file extracted to $path

"; } else { echo "

Error with $file. Error code: $result

"; } /* PHP Change Log http://www.php.net/ChangeLog-5.php#5.5.4 PHP Version 5.3.4 released 09-Dec-2010 - Fixed crash in zip extract method (possible CWE-170). PHP Version 5.2.17 released 06-Jan-2011 */ ?>




Posted

in

,

by

Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.