FileDownloader.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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\Console\Helper\WrapperInterface;
  13. use Composer\Package\PackageInterface;
  14. /**
  15. * Base downloader for file packages
  16. *
  17. * @author Kirill chEbba Chebunin <iam@chebba.org>
  18. * @author Jordi Boggiano <j.boggiano@seld.be>
  19. * @author François Pluchino <francois.pluchino@opendisplay.com>
  20. */
  21. abstract class FileDownloader implements DownloaderInterface
  22. {
  23. protected $wrapper;
  24. protected $bytesMax;
  25. /**
  26. * Constructor.
  27. *
  28. * @param WrapperInterface $wrapper The Wrapper instance
  29. */
  30. public function __construct(WrapperInterface $wrapper)
  31. {
  32. $this->wrapper = $wrapper;
  33. }
  34. /**
  35. * {@inheritDoc}
  36. */
  37. public function getInstallationSource()
  38. {
  39. return 'dist';
  40. }
  41. /**
  42. * {@inheritDoc}
  43. */
  44. public function download(PackageInterface $package, $path)
  45. {
  46. // init the progress bar
  47. $this->bytesMax = 0;
  48. $url = $package->getDistUrl();
  49. $checksum = $package->getDistSha1Checksum();
  50. if (!is_dir($path)) {
  51. if (file_exists($path)) {
  52. throw new \UnexpectedValueException($path.' exists and is not a directory');
  53. }
  54. if (!mkdir($path, 0777, true)) {
  55. throw new \UnexpectedValueException($path.' does not exist and could not be created');
  56. }
  57. }
  58. $fileName = rtrim($path.'/'.md5(time().rand()).'.'.pathinfo($url, PATHINFO_EXTENSION), '.');
  59. $this->wrapper->getOutput()->writeln(" - Package <comment>" . $package->getName() . "</comment> (<info>" . $package->getPrettyVersion() . "</info>)");
  60. if (!extension_loaded('openssl') && (0 === strpos($url, 'https:') || 0 === strpos($url, 'http://github.com'))) {
  61. // bypass https for github if openssl is disabled
  62. if (preg_match('{^https?://(github.com/[^/]+/[^/]+/(zip|tar)ball/[^/]+)$}i', $url, $match)) {
  63. $url = 'http://nodeload.'.$match[1];
  64. } else {
  65. throw new \RuntimeException('You must enable the openssl extension to download files via https');
  66. }
  67. }
  68. // Handle system proxy
  69. $ctx = stream_context_create();
  70. if (isset($_SERVER['HTTP_PROXY'])) {
  71. // http(s):// is not supported in proxy
  72. $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']);
  73. if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) {
  74. throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
  75. }
  76. $ctx = stream_context_create(array(
  77. 'http' => array(
  78. 'proxy' => $proxy,
  79. 'request_fulluri' => true,
  80. ),
  81. ));
  82. }
  83. stream_context_set_params($ctx, array("notification" => array($this, 'callbackDownload')));
  84. copy($url, $fileName, $ctx);
  85. $this->wrapper->overwriteln(" Downloading: <comment>OK</comment>", 80);
  86. if (!file_exists($fileName)) {
  87. throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
  88. .' directory is writable and you have internet connectivity');
  89. }
  90. if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
  91. throw new \UnexpectedValueException('The checksum verification of the archive failed (downloaded from '.$url.')');
  92. }
  93. $this->wrapper->getOutput()->writeln(' Unpacking archive');
  94. $this->extract($fileName, $path);
  95. $this->wrapper->getOutput()->writeln(' Cleaning up');
  96. unlink($fileName);
  97. // If we have only a one dir inside it suppose to be a package itself
  98. $contentDir = glob($path . '/*');
  99. if (1 === count($contentDir)) {
  100. $contentDir = $contentDir[0];
  101. foreach (array_merge(glob($contentDir . '/.*'), glob($contentDir . '/*')) as $file) {
  102. if (trim(basename($file), '.')) {
  103. rename($file, $path . '/' . basename($file));
  104. }
  105. }
  106. rmdir($contentDir);
  107. }
  108. $this->wrapper->overwrite('');
  109. $this->wrapper->getOutput()->writeln('');
  110. }
  111. /**
  112. * {@inheritDoc}
  113. */
  114. public function update(PackageInterface $initial, PackageInterface $target, $path)
  115. {
  116. $fs = new Util\Filesystem();
  117. $fs->removeDirectory($path);
  118. $this->download($target, $path);
  119. }
  120. /**
  121. * {@inheritDoc}
  122. */
  123. public function remove(PackageInterface $package, $path)
  124. {
  125. $fs = new Util\Filesystem();
  126. $fs->removeDirectory($path);
  127. }
  128. /**
  129. * Download notification action.
  130. *
  131. * @param integer $notificationCode The notification code
  132. * @param integer $severity The severity level
  133. * @param string $message The message
  134. * @param integer $messageCode The message code
  135. * @param integer $bytesTransferred The loaded size
  136. * @param integer $bytesMax The total size
  137. */
  138. protected function callbackDownload($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax)
  139. {
  140. switch ($notificationCode) {
  141. case STREAM_NOTIFY_AUTH_REQUIRED:
  142. break;
  143. case STREAM_NOTIFY_FILE_SIZE_IS:
  144. if ($this->bytesMax < $bytesMax) {
  145. $this->bytesMax = $bytesMax;
  146. }
  147. break;
  148. case STREAM_NOTIFY_PROGRESS:
  149. if ($this->bytesMax > 0) {
  150. $progression = 0;
  151. if ($this->bytesMax > 0) {
  152. $progression = ($bytesTransferred / $this->bytesMax * 100);
  153. }
  154. $levels = array(0, 5, 10, 15, 20, 25, 30, 35, 40, 35, 50, 55, 60,
  155. 65, 70, 75, 80, 85, 90, 95, 100);
  156. $progression = round($progression, 0);
  157. if (in_array($progression, $levels)) {
  158. $this->wrapper->overwrite(" Downloading: <comment>$progression%</comment>", 80);
  159. }
  160. }
  161. break;
  162. case STREAM_NOTIFY_AUTH_RESULT:
  163. break;
  164. default:
  165. break;
  166. }
  167. }
  168. /**
  169. * Extract file to directory
  170. *
  171. * @param string $file Extracted file
  172. * @param string $path Directory
  173. *
  174. * @throws \UnexpectedValueException If can not extract downloaded file to path
  175. */
  176. protected abstract function extract($file, $path);
  177. }