ZipDownloader.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Downloader;
  12. use Composer\Config;
  13. use Composer\Cache;
  14. use Composer\EventDispatcher\EventDispatcher;
  15. use Composer\Package\PackageInterface;
  16. use Composer\Util\Platform;
  17. use Composer\Util\ProcessExecutor;
  18. use Composer\Util\RemoteFilesystem;
  19. use Composer\IO\IOInterface;
  20. use Symfony\Component\Process\ExecutableFinder;
  21. use ZipArchive;
  22. /**
  23. * @author Jordi Boggiano <j.boggiano@seld.be>
  24. */
  25. class ZipDownloader extends ArchiveDownloader
  26. {
  27. protected $process;
  28. protected static $hasSystemUnzip;
  29. public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
  30. {
  31. $this->process = $process ?: new ProcessExecutor($io);
  32. parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
  33. }
  34. /**
  35. * {@inheritDoc}
  36. */
  37. public function download(PackageInterface $package, $path)
  38. {
  39. if (null === self::$hasSystemUnzip) {
  40. $finder = new ExecutableFinder;
  41. self::$hasSystemUnzip = (bool) $finder->find('unzip');
  42. }
  43. if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) {
  44. // php.ini path is added to the error message to help users find the correct file
  45. $iniPath = php_ini_loaded_file();
  46. if ($iniPath) {
  47. $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
  48. } else {
  49. $iniMessage = 'A php.ini file does not exist. You will have to create one.';
  50. }
  51. $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;
  52. throw new \RuntimeException($error);
  53. }
  54. return parent::download($package, $path);
  55. }
  56. protected function extract($file, $path)
  57. {
  58. $processError = null;
  59. if (self::$hasSystemUnzip && !(class_exists('ZipArchive') && Platform::isWindows())) {
  60. $command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path);
  61. if (!Platform::isWindows()) {
  62. $command .= ' && chmod -R u+w ' . ProcessExecutor::escape($path);
  63. }
  64. try {
  65. if (0 === $this->process->execute($command, $ignoredOutput)) {
  66. return;
  67. }
  68. $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
  69. } catch (\Exception $e) {
  70. $processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage();
  71. }
  72. if (!class_exists('ZipArchive')) {
  73. throw new \RuntimeException($processError);
  74. }
  75. }
  76. $zipArchive = new ZipArchive();
  77. if (true !== ($retval = $zipArchive->open($file))) {
  78. throw new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n".$processError), $retval);
  79. }
  80. if (true !== $zipArchive->extractTo($path)) {
  81. throw new \RuntimeException(rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n".$processError));
  82. }
  83. $zipArchive->close();
  84. }
  85. /**
  86. * Give a meaningful error message to the user.
  87. *
  88. * @param int $retval
  89. * @param string $file
  90. * @return string
  91. */
  92. protected function getErrorMessage($retval, $file)
  93. {
  94. switch ($retval) {
  95. case ZipArchive::ER_EXISTS:
  96. return sprintf("File '%s' already exists.", $file);
  97. case ZipArchive::ER_INCONS:
  98. return sprintf("Zip archive '%s' is inconsistent.", $file);
  99. case ZipArchive::ER_INVAL:
  100. return sprintf("Invalid argument (%s)", $file);
  101. case ZipArchive::ER_MEMORY:
  102. return sprintf("Malloc failure (%s)", $file);
  103. case ZipArchive::ER_NOENT:
  104. return sprintf("No such zip file: '%s'", $file);
  105. case ZipArchive::ER_NOZIP:
  106. return sprintf("'%s' is not a zip archive.", $file);
  107. case ZipArchive::ER_OPEN:
  108. return sprintf("Can't open zip file: %s", $file);
  109. case ZipArchive::ER_READ:
  110. return sprintf("Zip read error (%s)", $file);
  111. case ZipArchive::ER_SEEK:
  112. return sprintf("Zip seek error (%s)", $file);
  113. default:
  114. return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval);
  115. }
  116. }
  117. }