modxcms/revolution — pull request #11355 by exside (2014-05-10 01:35:53+00:00)
Title: modFileHandler: Added method unpack() to extract ZIP files

I know the changes look f'd up...sorry...the only thing I really changed/added is the new unpack() method, the rest is cosmetic cleanup stuff, I just moved around the methods in a more organized way (in my opinion) + I fixed the very annoying syntax-highlighting bug in my editor (sublimetext, don't know if others have this too) which causes everything after that regex in sanitizePath() to be "unhighlighted", I just replaced single quotes with double ones...

the added method unpack is basically thought as outsourcing/replacement for the _unpack method here: https://github.com/modxcms/revolution/blob/develop/core/xpdo/transport/xpdotransport.class.php#L746 which has a @todo to be outsourced...@opengeek as it is part of xPDO I'm not so sure this is gonna work (PR which actually works coming soon) when it relies on something from MODX??

anyways, the unpack method basically does the same as the "old" _unpack() method by choosing from xPDOZip (which uses ZipArchive(), actually the new one (https://github.com/modxcms/revolution/pull/11354) or the pclzip library in case ZipArchive() is not available...BUT, it also falls back further to zip functions (like zip_open etc.) and finally looks for active system functions (shell_exec, system, exec) and if one is found it tries to unzip via cli...I tested all 4 approaches successfully on a LAMP server / MODX 2.2.14pl, including arbitrary zip files and also the package manager zip extraction...seems to work! Just to make sure: Does anybody know of other parts of MODX/XPDO where zip archives get extracted (other than in xpdo/xpdotransport.class.php)?

For testing / copy&pasting the method with debug loggers (to test the different approaches, just add some arbitrary string to classname/libname/functionname so it doesn't work)

```
/**
* Unpack a zip archive to a specified location.
*
* @uses compression.xPDOZip OR compression.PclZip
*
* @param string $this->getPath() An absolute file system location to a valid zip archive.
* @param string $to A file system location to extract the contents of the archive to.
* @param array $options an array of optional options, primarily for the xPDOZip class
* @return array|string|boolean An array of unpacked files, a string in case of cli functions or false on failure.
*/
public function unpack($to = '', $options = array()) {

$results = false;

// extract the archive in the same directory if no $to path is specified
if (empty($to)) {
$to = $this->getParentDirectory(true);
}

if (class_exists('ZipArchive') && $this->fileHandler->modx->getService('zip', 'compression.xPDOZip', XPDO_CORE_PATH, array_merge($options, array('file' => $this->path)))) {
$this->fileHandler->modx->log(modX::LOG_LEVEL_ERROR, '[' . __METHOD__ . '] Using xPDOZip to extract ' . $this->path . ' to ' . $to);
$results = $this->fileHandler->modx->zip->unpack($to);
$this->fileHandler->modx->zip->close();
} else if (class_exists('PclZip') || include_once(XPDO_CORE_PATH . 'compression/pclzip.lib.php')) {
$this->fileHandler->modx->log(modX::LOG_LEVEL_ERROR, '[' . __METHOD__ . '] Using pclzip to extract ' . $this->path . ' to ' . $to);
$archive = new PclZip($this->path);

if ($archive) {
$results = array_map('current', $archive->extract(PCLZIP_OPT_PATH, $to));
}
} else if (is_callable('zip_open')) {
$this->fileHandler->modx->log(modX::LOG_LEVEL_ERROR, '[' . __METHOD__ . '] Using zip functions to extract ' . $this->path . ' to ' . $to);
$archive = zip_open($this->path);

if (!is_dir($to)) {
$this->fileHandler->modx->cacheManager->writeTree($to);
}

while ($zip_entry = zip_read($archive)) {
$filename = zip_entry_name($zip_entry);
$target = $to . DIRECTORY_SEPARATOR . substr($filename, 0, strrpos($filename, '/'));
$filesize = zip_entry_filesize($zip_entry);

if (file_exists($target) || mkdir($target)) {
$results[] = $to . DIRECTORY_SEPARATOR . $filename;

if ($filesize > 0) {
$contents = zip_entry_read($zip_entry, $filesize);
file_put_contents($to . DIRECTORY_SEPARATOR . $filename, $contents);
}
}
}
} else {
// check for windows systems running PHP, as they do not seem to have a native unzip functionality
if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$sysfns = array(
'shell_exec' => false,
'system' => false,
'exec' => false,
);

$disabled = array_map('trim', explode(',', ini_get('disable_functions')));

foreach ($sysfns as $func => $value) {
$sysfns[$func] = !in_array($func, $disabled);
}

$sysfns = array_filter($sysfns); // remove not available system functions

$this->fileHandler->modx->log(modX::LOG_LEVEL_ERROR, '[' . __METHOD__ . '] Available system functions ' . print_r($sysfns,1));

if (!empty($sysfns)) {
$sysfn = key($sysfns); // get the first available system function
$this->fileHandler->modx->log(modX::LOG_LEVEL_ERROR, '[' . __METHOD__ . '] Using command line ' . $sysfn . ' to extract ' . $this->path . ' to ' . $to);
$command = implode(' ', array(
'unzip',
$this->path
));

// sanitize and escape command
$command = escapeshellcmd(preg_replace('#(\s){2,}#is', ' ', str_replace(array("\n", "'"), array('', '"'), $command)));

if (!is_dir($to)) {
$this->fileHandler->modx->cacheManager->writeTree($to);
}

chdir($to); // switch to target directory

$results = $sysfn($command); // execute command
}
}
}

return $results;
}
```