ZipDownloader.php 4.5 KB

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