2011-05-06 17:55:49 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Composer\Downloader ;
2012-10-21 15:56:57 +00:00
use Composer\Config ;
2012-11-10 22:17:36 +00:00
use Composer\Cache ;
2013-08-14 15:42:11 +00:00
use Composer\EventDispatcher\EventDispatcher ;
2016-02-25 13:05:26 +00:00
use Composer\Package\PackageInterface ;
2016-11-19 20:50:15 +00:00
use Composer\Util\IniHelper ;
2016-02-03 21:39:16 +00:00
use Composer\Util\Platform ;
2012-01-18 15:37:55 +00:00
use Composer\Util\ProcessExecutor ;
2016-01-10 20:06:10 +00:00
use Composer\Util\RemoteFilesystem ;
2012-01-18 16:11:26 +00:00
use Composer\IO\IOInterface ;
2016-02-25 13:05:26 +00:00
use Symfony\Component\Process\ExecutableFinder ;
2012-03-29 13:08:47 +00:00
use ZipArchive ;
2011-05-06 17:55:49 +00:00
/**
* @ author Jordi Boggiano < j . boggiano @ seld . be >
*/
2012-02-17 22:10:02 +00:00
class ZipDownloader extends ArchiveDownloader
2011-05-06 17:55:49 +00:00
{
2017-05-15 11:41:36 +00:00
protected static $hasSystemUnzip ;
2017-03-30 07:24:48 +00:00
private static $hasZipArchive ;
private static $isWindows ;
2012-01-18 15:37:55 +00:00
protected $process ;
2017-03-14 22:43:48 +00:00
private $zipArchiveObject ;
2012-01-18 15:37:55 +00:00
2016-01-10 20:06:10 +00:00
public function __construct ( IOInterface $io , Config $config , EventDispatcher $eventDispatcher = null , Cache $cache = null , ProcessExecutor $process = null , RemoteFilesystem $rfs = null )
2012-01-18 15:37:55 +00:00
{
2013-08-10 00:43:40 +00:00
$this -> process = $process ? : new ProcessExecutor ( $io );
2016-01-10 20:06:10 +00:00
parent :: __construct ( $io , $config , $eventDispatcher , $cache , $rfs );
2012-01-18 15:37:55 +00:00
}
2016-02-25 13:05:26 +00:00
/**
* { @ inheritDoc }
*/
2016-12-06 22:41:46 +00:00
public function download ( PackageInterface $package , $path , $output = true )
2016-02-25 13:05:26 +00:00
{
2016-02-25 14:04:29 +00:00
if ( null === self :: $hasSystemUnzip ) {
2016-02-25 13:05:26 +00:00
$finder = new ExecutableFinder ;
2016-02-25 14:04:29 +00:00
self :: $hasSystemUnzip = ( bool ) $finder -> find ( 'unzip' );
2016-02-25 13:05:26 +00:00
}
2017-02-13 14:54:55 +00:00
if ( null === self :: $hasZipArchive ) {
self :: $hasZipArchive = class_exists ( 'ZipArchive' );
}
2017-03-14 22:43:48 +00:00
if ( null === self :: $isWindows ) {
self :: $isWindows = Platform :: isWindows ();
}
2017-02-13 14:54:55 +00:00
if ( ! self :: $hasZipArchive && ! self :: $hasSystemUnzip ) {
2016-03-11 09:37:00 +00:00
// php.ini path is added to the error message to help users find the correct file
2016-11-19 20:50:15 +00:00
$iniMessage = IniHelper :: getMessage ();
2016-03-11 09:37:00 +00:00
$error = " The zip extension and unzip command are both missing, skipping. \n " . $iniMessage ;
throw new \RuntimeException ( $error );
2016-02-25 13:05:26 +00:00
}
2016-12-06 22:41:46 +00:00
return parent :: download ( $package , $path , $output );
2016-02-25 13:05:26 +00:00
}
2017-02-13 13:00:48 +00:00
/**
* extract $file to $path with " unzip " command
2017-02-13 12:43:36 +00:00
*
2017-02-13 14:54:55 +00:00
* @ param string $file File to extract
* @ param string $path Path where to extract file
2017-03-14 22:43:48 +00:00
* @ param bool $isLastChance If true it is called as a fallback and should throw an exception
* @ return bool Success status
2017-02-13 12:43:36 +00:00
*/
2017-03-14 22:43:48 +00:00
protected function extractWithSystemUnzip ( $file , $path , $isLastChance )
2011-05-06 17:55:49 +00:00
{
2017-03-30 07:24:48 +00:00
if ( ! self :: $hasZipArchive ) {
2017-03-14 22:43:48 +00:00
// Force Exception throwing if the Other alternative is not available
$isLastChance = true ;
}
2017-03-30 07:24:48 +00:00
if ( ! self :: $hasSystemUnzip && ! $isLastChance ) {
2017-03-14 22:43:48 +00:00
// This was call as the favorite extract way, but is not available
// We switch to the alternative
return $this -> extractWithZipArchive ( $file , $path , true );
}
2013-01-31 09:57:59 +00:00
$processError = null ;
2017-02-13 14:54:55 +00:00
// When called after a ZipArchive failed, perhaps there is some files to overwrite
2017-03-14 22:43:48 +00:00
$overwrite = $isLastChance ? '-o' : '' ;
2017-02-13 14:54:55 +00:00
$command = 'unzip -qq ' . $overwrite . ' ' . ProcessExecutor :: escape ( $file ) . ' -d ' . ProcessExecutor :: escape ( $path );
2013-01-31 09:57:59 +00:00
2017-02-13 13:00:48 +00:00
try {
if ( 0 === $this -> process -> execute ( $command , $ignoredOutput )) {
2017-03-14 22:43:48 +00:00
return true ;
2014-05-20 08:15:44 +00:00
}
2013-01-31 09:57:59 +00:00
2017-03-14 22:43:48 +00:00
$processError = new \RuntimeException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput ());
2017-02-13 13:00:48 +00:00
} catch ( \Exception $e ) {
2017-03-14 22:43:48 +00:00
$processError = $e ;
}
if ( $isLastChance ) {
throw $processError ;
2017-02-13 14:54:55 +00:00
}
2017-03-30 07:24:48 +00:00
$this -> io -> writeError ( ' ' . $processError -> getMessage ());
$this -> io -> writeError ( ' Unzip with unzip command failed, falling back to ZipArchive class' );
return $this -> extractWithZipArchive ( $file , $path , true );
2017-02-13 13:00:48 +00:00
}
/**
* extract $file to $path with ZipArchive
*
2017-03-14 22:43:48 +00:00
* @ param string $file File to extract
* @ param string $path Path where to extract file
* @ param bool $isLastChance If true it is called as a fallback and should throw an exception
* @ return bool Success status
2017-02-13 13:00:48 +00:00
*/
2017-03-14 22:43:48 +00:00
protected function extractWithZipArchive ( $file , $path , $isLastChance )
2017-02-13 13:00:48 +00:00
{
2017-03-30 07:24:48 +00:00
if ( ! self :: $hasSystemUnzip ) {
2017-03-14 22:43:48 +00:00
// Force Exception throwing if the Other alternative is not available
$isLastChance = true ;
}
2011-05-06 17:55:49 +00:00
2017-03-30 07:24:48 +00:00
if ( ! self :: $hasZipArchive && ! $isLastChance ) {
2017-03-14 22:43:48 +00:00
// This was call as the favorite extract way, but is not available
// We switch to the alternative
return $this -> extractWithSystemUnzip ( $file , $path , true );
2017-02-13 14:54:55 +00:00
}
2017-03-14 22:43:48 +00:00
$processError = null ;
$zipArchive = $this -> zipArchiveObject ? : new ZipArchive ();
2017-02-13 14:54:55 +00:00
try {
2017-03-14 22:43:48 +00:00
if ( true === ( $retval = $zipArchive -> open ( $file ))) {
$extractResult = $zipArchive -> extractTo ( $path );
if ( true === $extractResult ) {
$zipArchive -> close ();
2017-03-30 07:24:48 +00:00
2017-03-14 22:43:48 +00:00
return true ;
}
2017-03-30 07:24:48 +00:00
$processError = new \RuntimeException ( rtrim ( " There was an error extracting the ZIP file, it is either corrupted or using an invalid format. \n " ));
2017-03-14 22:43:48 +00:00
} else {
$processError = new \UnexpectedValueException ( rtrim ( $this -> getErrorMessage ( $retval , $file ) . " \n " ), $retval );
}
2017-05-16 20:44:25 +00:00
} catch ( \ErrorException $e ) {
$processError = new \RuntimeException ( 'The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems): ' . $e -> getMessage (), 0 , $e );
2017-03-14 22:43:48 +00:00
} catch ( \Exception $e ) {
$processError = $e ;
2011-05-06 17:55:49 +00:00
}
2011-09-28 22:48:17 +00:00
2017-03-14 22:43:48 +00:00
if ( $isLastChance ) {
throw $processError ;
2012-08-17 20:06:58 +00:00
}
2017-03-30 07:24:48 +00:00
$this -> io -> writeError ( ' ' . $processError -> getMessage ());
$this -> io -> writeError ( ' Unzip with ZipArchive class failed, falling back to unzip command' );
return $this -> extractWithSystemUnzip ( $file , $path , true );
2017-02-13 13:00:48 +00:00
}
/**
* extract $file to $path
*
* @ param string $file File to extract
* @ param string $path Path where to extract file
*/
2017-03-14 22:43:48 +00:00
public function extract ( $file , $path )
2017-02-13 13:00:48 +00:00
{
2017-03-14 22:43:48 +00:00
// Each extract calls its alternative if not available or fails
if ( self :: $isWindows ) {
$this -> extractWithZipArchive ( $file , $path , false );
2017-02-13 14:54:55 +00:00
} else {
2017-03-14 22:43:48 +00:00
$this -> extractWithSystemUnzip ( $file , $path , false );
}
2011-05-06 17:55:49 +00:00
}
2012-03-29 12:19:41 +00:00
2012-03-29 12:22:26 +00:00
/**
2012-03-29 13:08:47 +00:00
* Give a meaningful error message to the user .
2012-03-29 12:22:26 +00:00
*
2012-05-22 10:07:08 +00:00
* @ param int $retval
* @ param string $file
2012-03-29 13:08:47 +00:00
* @ return string
2012-03-29 12:22:26 +00:00
*/
2012-03-29 13:08:47 +00:00
protected function getErrorMessage ( $retval , $file )
2012-03-29 12:19:41 +00:00
{
switch ( $retval ) {
2012-03-29 13:08:47 +00:00
case ZipArchive :: ER_EXISTS :
return sprintf ( " File '%s' already exists. " , $file );
case ZipArchive :: ER_INCONS :
return sprintf ( " Zip archive '%s' is inconsistent. " , $file );
case ZipArchive :: ER_INVAL :
return sprintf ( " Invalid argument (%s) " , $file );
case ZipArchive :: ER_MEMORY :
return sprintf ( " Malloc failure (%s) " , $file );
case ZipArchive :: ER_NOENT :
return sprintf ( " No such zip file: '%s' " , $file );
case ZipArchive :: ER_NOZIP :
return sprintf ( " '%s' is not a zip archive. " , $file );
case ZipArchive :: ER_OPEN :
return sprintf ( " Can't open zip file: %s " , $file );
case ZipArchive :: ER_READ :
return sprintf ( " Zip read error (%s) " , $file );
case ZipArchive :: ER_SEEK :
return sprintf ( " Zip seek error (%s) " , $file );
default :
return sprintf ( " '%s' is not a valid zip archive, got error code: %s " , $file , $retval );
2012-03-29 12:19:41 +00:00
}
}
2011-09-17 13:12:45 +00:00
}