commit
9f18b54cb6
|
@ -34,7 +34,8 @@
|
|||
"symfony/console": "^2.7 || ^3.0 || ^4.0",
|
||||
"symfony/filesystem": "^2.7 || ^3.0 || ^4.0",
|
||||
"symfony/finder": "^2.7 || ^3.0 || ^4.0",
|
||||
"symfony/process": "^2.7 || ^3.0 || ^4.0"
|
||||
"symfony/process": "^2.7 || ^3.0 || ^4.0",
|
||||
"react/promise": "^1.2 || ^2.7"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "2.8.38"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e46280c4cfd37bf3ec8be36095feb20e",
|
||||
"content-hash": "b078b12b2912d599e0c6904f64def484",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -342,6 +342,50 @@
|
|||
],
|
||||
"time": "2018-11-20T15:27:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "eefff597e67ff66b719f8171480add3c91474a1e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/eefff597e67ff66b719f8171480add3c91474a1e",
|
||||
"reference": "eefff597e67ff66b719f8171480add3c91474a1e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"React\\Promise": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/React/Promise/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||
"time": "2016-03-07T13:46:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "seld/jsonlint",
|
||||
"version": "1.7.1",
|
||||
|
|
|
@ -928,4 +928,9 @@ repository options.
|
|||
Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the
|
||||
composer home, cache, and data directories.
|
||||
|
||||
### COMPOSER_DISABLE_NETWORK
|
||||
|
||||
If set to `1`, disables network access (best effort). This can be used for debugging or
|
||||
to run Composer on a plane or a starship with poor connectivity.
|
||||
|
||||
← [Libraries](02-libraries.md) | [Schema](04-schema.md) →
|
||||
|
|
|
@ -176,8 +176,8 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface
|
|||
|
||||
if ($protocol === 's3') {
|
||||
$awsClient = new AwsClient($this->io, $this->composer->getConfig());
|
||||
$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient);
|
||||
$event->setRemoteFilesystem($s3RemoteFilesystem);
|
||||
$s3Downloader = new S3Downloader($this->io, $event->getHttpDownloader()->getOptions(), $awsClient);
|
||||
$event->setHttpdownloader($s3Downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ Composer fires the following named events during its execution process:
|
|||
- **command**: occurs before any Composer Command is executed on the CLI. It
|
||||
provides you with access to the input and output objects of the program.
|
||||
- **pre-file-download**: occurs before files are downloaded and allows
|
||||
you to manipulate the `RemoteFilesystem` object prior to downloading files
|
||||
you to manipulate the `HttpDownloader` object prior to downloading files
|
||||
based on the URL to be downloaded.
|
||||
- **pre-command-run**: occurs before a command is executed and allows you to
|
||||
manipulate the `InputInterface` object's options and arguments to tweak
|
||||
|
|
|
@ -22,6 +22,7 @@ use Composer\Script\ScriptEvents;
|
|||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -104,8 +105,9 @@ EOT
|
|||
$archiveManager = $composer->getArchiveManager();
|
||||
} else {
|
||||
$factory = new Factory;
|
||||
$downloadManager = $factory->createDownloadManager($io, $config);
|
||||
$archiveManager = $factory->createArchiveManager($config, $downloadManager);
|
||||
$httpDownloader = $factory->createHttpDownloader($io, $config);
|
||||
$downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader);
|
||||
$archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader));
|
||||
}
|
||||
|
||||
if ($packageName) {
|
||||
|
|
|
@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder;
|
|||
use Composer\Json\JsonFile;
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
||||
/**
|
||||
|
@ -161,7 +162,6 @@ EOT
|
|||
}
|
||||
|
||||
$composer = Factory::create($io, null, $disablePlugins);
|
||||
$composer->getDownloadManager()->setOutputProgress(!$noProgress);
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
|
@ -345,15 +345,17 @@ EOT
|
|||
$package = $package->getAliasOf();
|
||||
}
|
||||
|
||||
$dm = $this->createDownloadManager($io, $config);
|
||||
$factory = new Factory();
|
||||
|
||||
$httpDownloader = $factory->createHttpDownloader($io, $config);
|
||||
$dm = $factory->createDownloadManager($io, $config, $httpDownloader);
|
||||
$dm->setPreferSource($preferSource)
|
||||
->setPreferDist($preferDist)
|
||||
->setOutputProgress(!$noProgress);
|
||||
->setPreferDist($preferDist);
|
||||
|
||||
$projectInstaller = new ProjectInstaller($directory, $dm);
|
||||
$im = $this->createInstallationManager();
|
||||
$im = $factory->createInstallationManager(new Loop($httpDownloader));
|
||||
$im->addInstaller($projectInstaller);
|
||||
$im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
|
||||
$im->execute(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
|
||||
$im->notifyInstalls($io);
|
||||
|
||||
// collect suggestions
|
||||
|
@ -369,16 +371,4 @@ EOT
|
|||
|
||||
return $installedFromVcs;
|
||||
}
|
||||
|
||||
protected function createDownloadManager(IOInterface $io, Config $config)
|
||||
{
|
||||
$factory = new Factory();
|
||||
|
||||
return $factory->createDownloadManager($io, $config);
|
||||
}
|
||||
|
||||
protected function createInstallationManager()
|
||||
{
|
||||
return new InstallationManager();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use Composer\Plugin\PluginEvents;
|
|||
use Composer\Util\ConfigValidator;
|
||||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
use Composer\SelfUpdate\Keys;
|
||||
use Composer\SelfUpdate\Versions;
|
||||
|
@ -35,8 +35,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class DiagnoseCommand extends BaseCommand
|
||||
{
|
||||
/** @var RemoteFilesystem */
|
||||
protected $rfs;
|
||||
/** @var HttpDownloader */
|
||||
protected $httpDownloader;
|
||||
|
||||
/** @var ProcessExecutor */
|
||||
protected $process;
|
||||
|
@ -85,7 +85,7 @@ EOT
|
|||
$config->merge(array('config' => array('secure-http' => false)));
|
||||
$config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);
|
||||
|
||||
$this->rfs = Factory::createRemoteFilesystem($io, $config);
|
||||
$this->httpDownloader = Factory::createHttpDownloader($io, $config);
|
||||
$this->process = new ProcessExecutor($io);
|
||||
|
||||
$io->write('Checking platform settings: ', false);
|
||||
|
@ -226,7 +226,7 @@ EOT
|
|||
}
|
||||
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false);
|
||||
$this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
|
||||
} catch (TransportException $e) {
|
||||
if (false !== strpos($e->getMessage(), 'cafile')) {
|
||||
$result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
|
||||
|
@ -253,11 +253,11 @@ EOT
|
|||
|
||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||
try {
|
||||
$json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true);
|
||||
$json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->parseJson();
|
||||
$hash = reset($json['provider-includes']);
|
||||
$hash = $hash['sha256'];
|
||||
$path = str_replace('%hash%', $hash, key($json['provider-includes']));
|
||||
$provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false);
|
||||
$provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/'.$path)->getBody();
|
||||
|
||||
if (hash('sha256', $provider) !== $hash) {
|
||||
return 'It seems that your proxy is modifying http traffic on the fly';
|
||||
|
@ -285,10 +285,10 @@ EOT
|
|||
|
||||
$url = 'http://repo.packagist.org/packages.json';
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $url, false);
|
||||
$this->httpDownloader->get($url);
|
||||
} catch (TransportException $e) {
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
$this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
@ -319,10 +319,10 @@ EOT
|
|||
|
||||
$url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
|
||||
try {
|
||||
$this->rfs->getContents('github.com', $url, false);
|
||||
$this->httpDownloader->get($url);
|
||||
} catch (TransportException $e) {
|
||||
try {
|
||||
$this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
$this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ EOT
|
|||
try {
|
||||
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';
|
||||
|
||||
return $this->rfs->getContents($domain, $url, false, array(
|
||||
return $this->httpDownloader->get($url, array(
|
||||
'retry-auth-failure' => false,
|
||||
)) ? true : 'Unexpected error';
|
||||
} catch (\Exception $e) {
|
||||
|
@ -374,8 +374,7 @@ EOT
|
|||
}
|
||||
|
||||
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
|
||||
$json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false));
|
||||
$data = json_decode($json, true);
|
||||
$data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->parseJson();
|
||||
|
||||
return $data['resources']['core'];
|
||||
}
|
||||
|
@ -428,7 +427,7 @@ EOT
|
|||
return $result;
|
||||
}
|
||||
|
||||
$versionsUtil = new Versions($config, $this->rfs);
|
||||
$versionsUtil = new Versions($config, $this->httpDownloader);
|
||||
$latest = $versionsUtil->getLatest();
|
||||
|
||||
if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
|
||||
|
|
|
@ -85,7 +85,6 @@ EOT
|
|||
}
|
||||
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
|
|
@ -126,7 +126,6 @@ EOT
|
|||
// Update packages
|
||||
$this->resetComposer();
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
|
|
@ -167,7 +167,6 @@ EOT
|
|||
// Update packages
|
||||
$this->resetComposer();
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
|
|
@ -76,9 +76,9 @@ EOT
|
|||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
$remoteFilesystem = Factory::createRemoteFilesystem($io, $config);
|
||||
$httpDownloader = Factory::createHttpDownloader($io, $config);
|
||||
|
||||
$versionsUtil = new Versions($config, $remoteFilesystem);
|
||||
$versionsUtil = new Versions($config, $httpDownloader);
|
||||
|
||||
// switch channel if requested
|
||||
foreach (array('stable', 'preview', 'snapshot') as $channel) {
|
||||
|
@ -155,9 +155,9 @@ EOT
|
|||
|
||||
$io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $versionsUtil->getChannel()));
|
||||
$remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
|
||||
$signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false);
|
||||
$signature = $httpDownloader->get($remoteFilename.'.sig')->getBody();
|
||||
$io->writeError(' ', false);
|
||||
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
|
||||
$httpDownloader->copy($remoteFilename, $tempFilename);
|
||||
$io->writeError('');
|
||||
|
||||
if (!file_exists($tempFilename) || !$signature) {
|
||||
|
|
|
@ -317,8 +317,8 @@ EOT
|
|||
} else {
|
||||
$type = 'available';
|
||||
}
|
||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||
foreach ($repo->getProviderNames() as $name) {
|
||||
if ($repo instanceof ComposerRepository) {
|
||||
foreach ($repo->getPackageNames() as $name) {
|
||||
if (!$packageFilter || preg_match($packageFilter, $name)) {
|
||||
$packages[$type][$name] = $name;
|
||||
}
|
||||
|
@ -553,7 +553,7 @@ EOT
|
|||
$matches[$index] = $package->getId();
|
||||
}
|
||||
|
||||
$pool = $repositorySet->createPool();
|
||||
$pool = $repositorySet->createPoolForPackage($package->getName());
|
||||
|
||||
// select preferred package according to policy rules
|
||||
if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) {
|
||||
|
|
|
@ -89,7 +89,7 @@ EOT
|
|||
|
||||
// list packages
|
||||
foreach ($installedRepo->getCanonicalPackages() as $package) {
|
||||
$downloader = $dm->getDownloaderForInstalledPackage($package);
|
||||
$downloader = $dm->getDownloaderForPackage($package);
|
||||
$targetDir = $im->getInstallPath($package);
|
||||
|
||||
if ($downloader instanceof ChangeReportInterface) {
|
||||
|
|
|
@ -120,8 +120,6 @@ EOT
|
|||
}
|
||||
}
|
||||
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ class Compiler
|
|||
->in(__DIR__.'/../../vendor/composer/ca-bundle/')
|
||||
->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
|
||||
->in(__DIR__.'/../../vendor/psr/')
|
||||
->in(__DIR__.'/../../vendor/react/')
|
||||
->sort($finderSort)
|
||||
;
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class Composer
|
|||
const VERSION = '@package_version@';
|
||||
const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
|
||||
const RELEASE_DATE = '@release_date@';
|
||||
const SOURCE_VERSION = '2.0-source';
|
||||
|
||||
/**
|
||||
* @var Package\RootPackageInterface
|
||||
|
|
|
@ -217,6 +217,7 @@ class Solver
|
|||
|
||||
$this->setupInstalledMap();
|
||||
|
||||
$this->io->writeError('Generating rules', true, IOInterface::DEBUG);
|
||||
$this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
|
||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
|
||||
$this->checkForRootRequireProblems($ignorePlatformReqs);
|
||||
|
|
|
@ -30,33 +30,50 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
* @throws \RuntimeException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function download(PackageInterface $package, $path, $output = true)
|
||||
public function install(PackageInterface $package, $path, $output = true)
|
||||
{
|
||||
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
|
||||
$retries = 3;
|
||||
while ($retries--) {
|
||||
$fileName = parent::download($package, $path, $output);
|
||||
if ($output) {
|
||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
|
||||
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
|
||||
$fileName = $this->getFileName($package, $path);
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
||||
try {
|
||||
$this->extract($package, $fileName, $temporaryDir);
|
||||
} catch (\Exception $e) {
|
||||
// remove cache if the file was corrupted
|
||||
parent::clearLastCacheWrite($package);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
||||
try {
|
||||
$this->extract($fileName, $temporaryDir);
|
||||
} catch (\Exception $e) {
|
||||
// remove cache if the file was corrupted
|
||||
parent::clearLastCacheWrite($package);
|
||||
throw $e;
|
||||
$this->filesystem->unlink($fileName);
|
||||
|
||||
$renameAsOne = false;
|
||||
if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
|
||||
$renameAsOne = true;
|
||||
}
|
||||
|
||||
$contentDir = $this->getFolderContent($temporaryDir);
|
||||
$singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir));
|
||||
|
||||
if ($renameAsOne) {
|
||||
// if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
|
||||
if ($singleDirAtTopLevel) {
|
||||
$extractedDir = (string) reset($contentDir);
|
||||
} else {
|
||||
$extractedDir = $temporaryDir;
|
||||
}
|
||||
|
||||
$this->filesystem->unlink($fileName);
|
||||
|
||||
$contentDir = $this->getFolderContent($temporaryDir);
|
||||
|
||||
$this->filesystem->rename($extractedDir, $path);
|
||||
} else {
|
||||
// only one dir in the archive, extract its contents out of it
|
||||
if (1 === count($contentDir) && is_dir(reset($contentDir))) {
|
||||
if ($singleDirAtTopLevel) {
|
||||
$contentDir = $this->getFolderContent((string) reset($contentDir));
|
||||
}
|
||||
|
||||
|
@ -65,35 +82,24 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
$file = (string) $file;
|
||||
$this->filesystem->rename($file, $path . '/' . basename($file));
|
||||
}
|
||||
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
|
||||
}
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
|
||||
// retry downloading if we have an invalid zip file
|
||||
if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
|
||||
$this->io->writeError('');
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...');
|
||||
} else {
|
||||
$this->io->writeError(' Invalid zip file, retrying...');
|
||||
}
|
||||
usleep(500000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
break;
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
|
||||
}
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
if (file_exists($fileName)) {
|
||||
$this->filesystem->unlink($fileName);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
|
||||
return rtrim($path.'_'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +119,7 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
*
|
||||
* @throws \UnexpectedValueException If can not extract downloaded file to path
|
||||
*/
|
||||
abstract protected function extract($file, $path);
|
||||
abstract protected function extract(PackageInterface $package, $file, $path);
|
||||
|
||||
/**
|
||||
* Returns the folder content, excluding dotfiles
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Downloader;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Downloaders manager.
|
||||
|
@ -24,6 +25,7 @@ use Composer\Util\Filesystem;
|
|||
class DownloadManager
|
||||
{
|
||||
private $io;
|
||||
private $httpDownloader;
|
||||
private $preferDist = false;
|
||||
private $preferSource = false;
|
||||
private $packagePreferences = array();
|
||||
|
@ -33,9 +35,9 @@ class DownloadManager
|
|||
/**
|
||||
* Initializes download manager.
|
||||
*
|
||||
* @param IOInterface $io The Input Output Interface
|
||||
* @param bool $preferSource prefer downloading from source
|
||||
* @param Filesystem|null $filesystem custom Filesystem object
|
||||
* @param IOInterface $io The Input Output Interface
|
||||
* @param bool $preferSource prefer downloading from source
|
||||
* @param Filesystem|null $filesystem custom Filesystem object
|
||||
*/
|
||||
public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
|
||||
{
|
||||
|
@ -83,22 +85,6 @@ class DownloadManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to output download progress information for all registered
|
||||
* downloaders
|
||||
*
|
||||
* @param bool $outputProgress
|
||||
* @return DownloadManager
|
||||
*/
|
||||
public function setOutputProgress($outputProgress)
|
||||
{
|
||||
foreach ($this->downloaders as $downloader) {
|
||||
$downloader->setOutputProgress($outputProgress);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets installer downloader for a specific installation type.
|
||||
*
|
||||
|
@ -140,7 +126,7 @@ class DownloadManager
|
|||
* wrong type
|
||||
* @return DownloaderInterface|null
|
||||
*/
|
||||
public function getDownloaderForInstalledPackage(PackageInterface $package)
|
||||
public function getDownloaderForPackage(PackageInterface $package)
|
||||
{
|
||||
$installationSource = $package->getInstallationSource();
|
||||
|
||||
|
@ -154,7 +140,7 @@ class DownloadManager
|
|||
$downloader = $this->getDownloader($package->getSourceType());
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
'Package '.$package.' seems not been installed properly'
|
||||
'Package '.$package.' does not have an installation source set'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -171,63 +157,95 @@ class DownloadManager
|
|||
return $downloader;
|
||||
}
|
||||
|
||||
public function getDownloaderType(DownloaderInterface $downloader)
|
||||
{
|
||||
return array_search($downloader, $this->downloaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads package into target dir.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param bool $preferSource prefer installation from source
|
||||
* @param PackageInterface $prevPackage previous package instance in case of updates
|
||||
*
|
||||
* @return PromiseInterface
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
|
||||
|
||||
$sources = $this->getAvailableSources($package, $prevPackage);
|
||||
|
||||
$io = $this->io;
|
||||
$self = $this;
|
||||
|
||||
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) {
|
||||
$source = array_shift($sources);
|
||||
if ($retry) {
|
||||
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||
}
|
||||
$package->setInstallationSource($source);
|
||||
|
||||
$downloader = $self->getDownloaderForPackage($package);
|
||||
if (!$downloader) {
|
||||
return \React\Promise\resolve();
|
||||
}
|
||||
|
||||
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
if (!$sources) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$io->writeError(
|
||||
' <warning>Failed to download '.
|
||||
$package->getPrettyName().
|
||||
' from ' . $source . ': '.
|
||||
$e->getMessage().'</warning>'
|
||||
);
|
||||
|
||||
return $download(true);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
};
|
||||
|
||||
try {
|
||||
$result = $downloader->download($package, $targetDir);
|
||||
} catch (\Exception $e) {
|
||||
return $handleError($e);
|
||||
}
|
||||
if (!$result instanceof PromiseInterface) {
|
||||
return \React\Promise\resolve($result);
|
||||
}
|
||||
|
||||
$res = $result->then(function ($res) {
|
||||
return $res;
|
||||
}, $handleError);
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
return $download();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs package into target dir.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
*
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function download(PackageInterface $package, $targetDir, $preferSource = null)
|
||||
public function install(PackageInterface $package, $targetDir)
|
||||
{
|
||||
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
|
||||
$sourceType = $package->getSourceType();
|
||||
$distType = $package->getDistType();
|
||||
|
||||
$sources = array();
|
||||
if ($sourceType) {
|
||||
$sources[] = 'source';
|
||||
}
|
||||
if ($distType) {
|
||||
$sources[] = 'dist';
|
||||
}
|
||||
|
||||
if (empty($sources)) {
|
||||
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
|
||||
}
|
||||
|
||||
if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
|
||||
$sources = array_reverse($sources);
|
||||
}
|
||||
|
||||
$this->filesystem->ensureDirectoryExists($targetDir);
|
||||
|
||||
foreach ($sources as $i => $source) {
|
||||
if (isset($e)) {
|
||||
$this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||
}
|
||||
$package->setInstallationSource($source);
|
||||
try {
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
if ($downloader) {
|
||||
$downloader->download($package, $targetDir);
|
||||
}
|
||||
break;
|
||||
} catch (\RuntimeException $e) {
|
||||
if ($i === count($sources) - 1) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->io->writeError(
|
||||
' <warning>Failed to download '.
|
||||
$package->getPrettyName().
|
||||
' from ' . $source . ': '.
|
||||
$e->getMessage().'</warning>'
|
||||
);
|
||||
}
|
||||
$downloader = $this->getDownloaderForPackage($package);
|
||||
if ($downloader) {
|
||||
$downloader->install($package, $targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,31 +260,23 @@ class DownloadManager
|
|||
*/
|
||||
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
|
||||
{
|
||||
$downloader = $this->getDownloaderForInstalledPackage($initial);
|
||||
$downloader = $this->getDownloaderForPackage($target);
|
||||
$initialDownloader = $this->getDownloaderForPackage($initial);
|
||||
|
||||
// no downloaders present means update from metapackage to metapackage, nothing to do
|
||||
if (!$initialDownloader && !$downloader) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
|
||||
if (!$downloader) {
|
||||
$initialDownloader->remove($initial, $targetDir);
|
||||
return;
|
||||
}
|
||||
|
||||
$installationSource = $initial->getInstallationSource();
|
||||
|
||||
if ('dist' === $installationSource) {
|
||||
$initialType = $initial->getDistType();
|
||||
$targetType = $target->getDistType();
|
||||
} else {
|
||||
$initialType = $initial->getSourceType();
|
||||
$targetType = $target->getSourceType();
|
||||
}
|
||||
|
||||
// upgrading from a dist stable package to a dev package, force source reinstall
|
||||
if ($target->isDev() && 'dist' === $installationSource) {
|
||||
$downloader->remove($initial, $targetDir);
|
||||
$this->download($target, $targetDir);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$initialType = $this->getDownloaderType($initialDownloader);
|
||||
$targetType = $this->getDownloaderType($downloader);
|
||||
if ($initialType === $targetType) {
|
||||
$target->setInstallationSource($installationSource);
|
||||
try {
|
||||
$downloader->update($initial, $target, $targetDir);
|
||||
|
||||
|
@ -282,8 +292,12 @@ class DownloadManager
|
|||
}
|
||||
}
|
||||
|
||||
$downloader->remove($initial, $targetDir);
|
||||
$this->download($target, $targetDir, 'source' === $installationSource);
|
||||
// if downloader type changed, or update failed and user asks for reinstall,
|
||||
// we wipe the dir and do a new install instead of updating it
|
||||
if ($initialDownloader) {
|
||||
$initialDownloader->remove($initial, $targetDir);
|
||||
}
|
||||
$this->install($target, $targetDir);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,7 +308,7 @@ class DownloadManager
|
|||
*/
|
||||
public function remove(PackageInterface $package, $targetDir)
|
||||
{
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
$downloader = $this->getDownloaderForPackage($package);
|
||||
if ($downloader) {
|
||||
$downloader->remove($package, $targetDir);
|
||||
}
|
||||
|
@ -322,4 +336,48 @@ class DownloadManager
|
|||
|
||||
return $package->isDev() ? 'source' : 'dist';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getAvailableSources(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$sourceType = $package->getSourceType();
|
||||
$distType = $package->getDistType();
|
||||
|
||||
// add source before dist by default
|
||||
$sources = array();
|
||||
if ($sourceType) {
|
||||
$sources[] = 'source';
|
||||
}
|
||||
if ($distType) {
|
||||
$sources[] = 'dist';
|
||||
}
|
||||
|
||||
if (empty($sources)) {
|
||||
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
|
||||
}
|
||||
|
||||
if (
|
||||
$prevPackage
|
||||
// if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
|
||||
&& in_array($prevPackage->getInstallationSource(), $sources, true)
|
||||
// unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
|
||||
&& !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
|
||||
) {
|
||||
$prevSource = $prevPackage->getInstallationSource();
|
||||
usort($sources, function ($a, $b) use ($prevSource) {
|
||||
return $a === $prevSource ? -1 : 1;
|
||||
});
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
// reverse sources in case dist is the preferred source for this package
|
||||
if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
|
||||
$sources = array_reverse($sources);
|
||||
}
|
||||
|
||||
return $sources;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Downloader interface.
|
||||
|
@ -29,13 +30,20 @@ interface DownloaderInterface
|
|||
*/
|
||||
public function getInstallationSource();
|
||||
|
||||
/**
|
||||
* This should do any network-related tasks to prepare for install/update
|
||||
*
|
||||
* @return PromiseInterface|null
|
||||
*/
|
||||
public function download(PackageInterface $package, $path);
|
||||
|
||||
/**
|
||||
* Downloads specific package into specific folder.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
*/
|
||||
public function download(PackageInterface $package, $path);
|
||||
public function install(PackageInterface $package, $path);
|
||||
|
||||
/**
|
||||
* Updates specific package in specific folder from initial to target version.
|
||||
|
@ -53,12 +61,4 @@ interface DownloaderInterface
|
|||
* @param string $path download path
|
||||
*/
|
||||
public function remove(PackageInterface $package, $path);
|
||||
|
||||
/**
|
||||
* Sets whether to output download progress information or not
|
||||
*
|
||||
* @param bool $outputProgress
|
||||
* @return DownloaderInterface
|
||||
*/
|
||||
public function setOutputProgress($outputProgress);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,9 @@ use Composer\Plugin\PluginEvents;
|
|||
use Composer\Plugin\PreFileDownloadEvent;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Url as UrlUtil;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* Base downloader for files
|
||||
|
@ -39,11 +40,13 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
{
|
||||
protected $io;
|
||||
protected $config;
|
||||
protected $rfs;
|
||||
protected $httpDownloader;
|
||||
protected $filesystem;
|
||||
protected $cache;
|
||||
protected $outputProgress = true;
|
||||
private $lastCacheWrites = array();
|
||||
/**
|
||||
* @private this is only public for php 5.3 support in closures
|
||||
*/
|
||||
public $lastCacheWrites = array();
|
||||
private $eventDispatcher;
|
||||
|
||||
/**
|
||||
|
@ -51,17 +54,17 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
*
|
||||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The config
|
||||
* @param HttpDownloader $httpDownloader The remote filesystem
|
||||
* @param EventDispatcher $eventDispatcher The event dispatcher
|
||||
* @param Cache $cache Optional cache instance
|
||||
* @param RemoteFilesystem $rfs The remote filesystem
|
||||
* @param Cache $cache Cache instance
|
||||
* @param Filesystem $filesystem The filesystem
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $filesystem = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
$this->cache = $cache;
|
||||
|
||||
|
@ -87,121 +90,154 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
throw new \InvalidArgumentException('The given package is missing url information');
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
|
||||
}
|
||||
|
||||
$retries = 3;
|
||||
$urls = $package->getDistUrls();
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
$fileName = $this->doDownload($package, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
|
||||
} elseif (count($urls)) {
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
|
||||
}
|
||||
|
||||
if (!count($urls)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
foreach ($urls as $index => $url) {
|
||||
$processedUrl = $this->processUrl($package, $url);
|
||||
$urls[$index] = array(
|
||||
'base' => $url,
|
||||
'processed' => $processedUrl,
|
||||
'cacheKey' => $this->getCacheKey($package, $processedUrl)
|
||||
);
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
protected function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$this->filesystem->emptyDirectory($path);
|
||||
|
||||
$fileName = $this->getFileName($package, $path);
|
||||
|
||||
$processedUrl = $this->processUrl($package, $url);
|
||||
$hostname = parse_url($processedUrl, PHP_URL_HOST);
|
||||
$io = $this->io;
|
||||
$cache = $this->cache;
|
||||
$httpDownloader = $this->httpDownloader;
|
||||
$eventDispatcher = $this->eventDispatcher;
|
||||
$filesystem = $this->filesystem;
|
||||
$self = $this;
|
||||
|
||||
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl);
|
||||
if ($this->eventDispatcher) {
|
||||
$this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
|
||||
}
|
||||
$rfs = $preFileDownloadEvent->getRemoteFilesystem();
|
||||
$accept = null;
|
||||
$reject = null;
|
||||
$download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) {
|
||||
$url = reset($urls);
|
||||
|
||||
if ($eventDispatcher) {
|
||||
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']);
|
||||
$eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
|
||||
}
|
||||
|
||||
try {
|
||||
$checksum = $package->getDistSha1Checksum();
|
||||
$cacheKey = $this->getCacheKey($package, $processedUrl);
|
||||
$cacheKey = $url['cacheKey'];
|
||||
|
||||
// use from cache if it is present and has a valid checksum or we have no checksum to check against
|
||||
if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
|
||||
$this->io->writeError('Loading from cache', false);
|
||||
if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) {
|
||||
if ($output) {
|
||||
$io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache");
|
||||
}
|
||||
$result = \React\Promise\resolve($fileName);
|
||||
} else {
|
||||
// download if cache restore failed
|
||||
if (!$this->outputProgress) {
|
||||
$this->io->writeError('Downloading', false);
|
||||
if ($output) {
|
||||
$io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||
}
|
||||
|
||||
// try to download 3 times then fail hard
|
||||
$retries = 3;
|
||||
while ($retries--) {
|
||||
try {
|
||||
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
|
||||
break;
|
||||
} catch (TransportException $e) {
|
||||
// if we got an http response with a proper code, then requesting again will probably not help, abort
|
||||
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
|
||||
throw $e;
|
||||
}
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE);
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->outputProgress) {
|
||||
$this->io->writeError(' (<comment>100%</comment>)', false);
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
$this->lastCacheWrites[$package->getName()] = $cacheKey;
|
||||
$this->cache->copyFrom($cacheKey, $fileName);
|
||||
}
|
||||
$result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
|
||||
->then($accept, $reject);
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
|
||||
.' directory is writable and you have internet connectivity');
|
||||
return $result->then(function ($result) use ($fileName, $checksum, $url) {
|
||||
// in case of retry, the first call's Promise chain finally calls this twice at the end,
|
||||
// once with $result being the returned $fileName from $accept, and then once for every
|
||||
// failed request with a null result, which can be skipped.
|
||||
if (null === $result) {
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
|
||||
.' directory is writable and you have internet connectivity');
|
||||
}
|
||||
|
||||
if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
|
||||
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
});
|
||||
};
|
||||
|
||||
$accept = function ($response) use ($io, $cache, $package, $fileName, $path, $self, &$urls) {
|
||||
$url = reset($urls);
|
||||
$cacheKey = $url['cacheKey'];
|
||||
|
||||
if ($cache) {
|
||||
$self->lastCacheWrites[$package->getName()] = $cacheKey;
|
||||
$cache->copyFrom($cacheKey, $fileName);
|
||||
}
|
||||
|
||||
if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
|
||||
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$response->collect();
|
||||
|
||||
return $fileName;
|
||||
};
|
||||
|
||||
$reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->clearLastCacheWrite($package);
|
||||
throw $e;
|
||||
}
|
||||
$filesystem->removeDirectory($path);
|
||||
$self->clearLastCacheWrite($package);
|
||||
|
||||
return $fileName;
|
||||
if ($e instanceof TransportException) {
|
||||
// if we got an http response with a proper code, then requesting again will probably not help, abort
|
||||
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
|
||||
$retries = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// special error code returned when network is being artificially disabled
|
||||
if ($e instanceof TransportException && $e->getStatusCode() === 499) {
|
||||
$retries = 0;
|
||||
$urls = array();
|
||||
}
|
||||
|
||||
if ($retries) {
|
||||
usleep(500000);
|
||||
$retries--;
|
||||
|
||||
return $download();
|
||||
}
|
||||
|
||||
array_shift($urls);
|
||||
if ($urls) {
|
||||
if ($io->isDebug()) {
|
||||
$io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
|
||||
$io->writeError(' Trying the next URL for '.$package->getName());
|
||||
} elseif (count($urls)) {
|
||||
$io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
|
||||
}
|
||||
|
||||
$retries = 3;
|
||||
usleep(100000);
|
||||
|
||||
return $download();
|
||||
}
|
||||
|
||||
throw $e;
|
||||
};
|
||||
|
||||
return $download();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setOutputProgress($outputProgress)
|
||||
public function install(PackageInterface $package, $path, $output = true)
|
||||
{
|
||||
$this->outputProgress = $outputProgress;
|
||||
if ($output) {
|
||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||
}
|
||||
|
||||
return $this;
|
||||
$this->filesystem->ensureDirectoryExists($path);
|
||||
$this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME));
|
||||
}
|
||||
|
||||
protected function clearLastCacheWrite(PackageInterface $package)
|
||||
/**
|
||||
* TODO mark private in v3
|
||||
* @protected This is public due to PHP 5.3
|
||||
*/
|
||||
public function clearLastCacheWrite(PackageInterface $package)
|
||||
{
|
||||
if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
|
||||
$this->cache->remove($this->lastCacheWrites[$package->getName()]);
|
||||
|
@ -222,7 +258,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
|
||||
|
||||
$this->remove($initial, $path, false);
|
||||
$this->download($target, $path, false);
|
||||
$this->install($target, $path, false);
|
||||
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
@ -249,7 +285,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
return $path.'_'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,15 +327,15 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
public function getLocalChanges(PackageInterface $package, $targetDir)
|
||||
{
|
||||
$prevIO = $this->io;
|
||||
$prevProgress = $this->outputProgress;
|
||||
|
||||
$this->io = new NullIO;
|
||||
$this->io->loadConfiguration($this->config);
|
||||
$this->outputProgress = false;
|
||||
$e = null;
|
||||
|
||||
try {
|
||||
$this->download($package, $targetDir.'_compare', false);
|
||||
$res = $this->download($package, $targetDir.'_compare', false);
|
||||
$this->httpDownloader->wait();
|
||||
$res = $this->install($package, $targetDir.'_compare', false);
|
||||
|
||||
$comparer = new Comparer();
|
||||
$comparer->setSource($targetDir.'_compare');
|
||||
|
@ -311,7 +347,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
}
|
||||
|
||||
$this->io = $prevIO;
|
||||
$this->outputProgress = $prevProgress;
|
||||
|
||||
if ($e) {
|
||||
throw $e;
|
||||
|
|
|
@ -23,7 +23,7 @@ class FossilDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
public function doInstall(PackageInterface $package, $path, $url)
|
||||
{
|
||||
// Ensure we are allowed to use this URL by config
|
||||
$this->config->prohibitUrlByConfig($url, $this->io);
|
||||
|
|
|
@ -38,7 +38,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
public function doInstall(PackageInterface $package, $path, $url)
|
||||
{
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
|
|
|
@ -18,7 +18,7 @@ use Composer\EventDispatcher\EventDispatcher;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -30,15 +30,16 @@ class GzipDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));
|
||||
$filename = pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME);
|
||||
$targetFilepath = $path . DIRECTORY_SEPARATOR . $filename;
|
||||
|
||||
// Try to use gunzip on *nix
|
||||
if (!Platform::isWindows()) {
|
||||
|
@ -63,14 +64,6 @@ class GzipDownloader extends ArchiveDownloader
|
|||
$this->extractUsingExt($file, $targetFilepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
private function extractUsingExt($file, $targetFilepath)
|
||||
{
|
||||
$archiveFile = gzopen($file, 'rb');
|
||||
|
|
|
@ -24,7 +24,7 @@ class HgDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
public function doInstall(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
||||
|
||||
|
|
|
@ -61,6 +61,15 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
|||
$realUrl
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function install(PackageInterface $package, $path, $output = true)
|
||||
{
|
||||
$url = $package->getDistUrl();
|
||||
$realUrl = realpath($url);
|
||||
|
||||
// Get the transport options with default values
|
||||
$transportOptions = $package->getTransportOptions() + array('symlink' => null);
|
||||
|
|
|
@ -27,7 +27,7 @@ class PerforceDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
public function doInstall(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$ref = $package->getSourceReference();
|
||||
$label = $this->getLabelFromSourceReference($ref);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Downloader for phar files
|
||||
*
|
||||
|
@ -22,7 +24,7 @@ class PharDownloader extends ArchiveDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Can throw an UnexpectedValueException
|
||||
$archive = new \Phar($file);
|
||||
|
|
|
@ -18,8 +18,9 @@ use Composer\EventDispatcher\EventDispatcher;
|
|||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use RarArchive;
|
||||
|
||||
/**
|
||||
|
@ -33,13 +34,13 @@ class RarDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
$processError = null;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class SvnDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
public function doInstall(PackageInterface $package, $path, $url)
|
||||
{
|
||||
SvnUtil::cleanEnv();
|
||||
$ref = $package->getSourceReference();
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Downloader for tar files: tar, tar.gz or tar.bz2
|
||||
*
|
||||
|
@ -22,7 +24,7 @@ class TarDownloader extends ArchiveDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Can throw an UnexpectedValueException
|
||||
$archive = new \PharData($file);
|
||||
|
|
|
@ -55,6 +55,14 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, $path)
|
||||
{
|
||||
// noop for now, ideally we would do a git fetch already here, or make sure the cached git repo is synced, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(PackageInterface $package, $path)
|
||||
{
|
||||
if (!$package->getSourceReference()) {
|
||||
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
|
||||
|
@ -87,7 +95,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
$url = $needle . $url;
|
||||
}
|
||||
}
|
||||
$this->doDownload($package, $path, $url);
|
||||
$this->doInstall($package, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||
|
@ -202,15 +210,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download progress information is not available for all VCS downloaders.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setOutputProgress($outputProgress)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -260,7 +259,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
*/
|
||||
abstract protected function doDownload(PackageInterface $package, $path, $url);
|
||||
abstract protected function doInstall(PackageInterface $package, $path, $url);
|
||||
|
||||
/**
|
||||
* Updates specific package in specific folder from initial to target version.
|
||||
|
|
|
@ -17,7 +17,7 @@ use Composer\Cache;
|
|||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -30,14 +30,14 @@ class XzDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
$command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);
|
||||
|
||||
|
@ -49,12 +49,4 @@ class XzDownloader extends ArchiveDownloader
|
|||
|
||||
throw new \RuntimeException($processError);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use Composer\Package\PackageInterface;
|
|||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
use ZipArchive;
|
||||
|
@ -36,10 +36,10 @@ class ZipDownloader extends ArchiveDownloader
|
|||
protected $process;
|
||||
private $zipArchiveObject;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,7 +185,7 @@ class ZipDownloader extends ArchiveDownloader
|
|||
* @param string $file File to extract
|
||||
* @param string $path Path where to extract file
|
||||
*/
|
||||
public function extract($file, $path)
|
||||
public function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Each extract calls its alternative if not available or fails
|
||||
if (self::$isWindows) {
|
||||
|
|
|
@ -23,7 +23,8 @@ use Composer\Repository\WritableRepositoryInterface;
|
|||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Util\Silencer;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\EventDispatcher\Event;
|
||||
|
@ -325,14 +326,15 @@ class Factory
|
|||
$io->loadConfiguration($config);
|
||||
}
|
||||
|
||||
$rfs = self::createRemoteFilesystem($io, $config);
|
||||
$httpDownloader = self::createHttpDownloader($io, $config);
|
||||
$loop = new Loop($httpDownloader);
|
||||
|
||||
// initialize event dispatcher
|
||||
$dispatcher = new EventDispatcher($composer, $io);
|
||||
$composer->setEventDispatcher($dispatcher);
|
||||
|
||||
// initialize repository manager
|
||||
$rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
|
||||
$rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher);
|
||||
$composer->setRepositoryManager($rm);
|
||||
|
||||
// load local repository
|
||||
|
@ -352,12 +354,12 @@ class Factory
|
|||
$composer->setPackage($package);
|
||||
|
||||
// initialize installation manager
|
||||
$im = $this->createInstallationManager();
|
||||
$im = $this->createInstallationManager($loop);
|
||||
$composer->setInstallationManager($im);
|
||||
|
||||
if ($fullLoad) {
|
||||
// initialize download manager
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
|
||||
$dm = $this->createDownloadManager($io, $config, $httpDownloader, $dispatcher);
|
||||
$composer->setDownloadManager($dm);
|
||||
|
||||
// initialize autoload generator
|
||||
|
@ -365,7 +367,7 @@ class Factory
|
|||
$composer->setAutoloadGenerator($generator);
|
||||
|
||||
// initialize archive manager
|
||||
$am = $this->createArchiveManager($config, $dm);
|
||||
$am = $this->createArchiveManager($config, $dm, $loop);
|
||||
$composer->setArchiveManager($am);
|
||||
}
|
||||
|
||||
|
@ -451,7 +453,7 @@ class Factory
|
|||
* @param EventDispatcher $eventDispatcher
|
||||
* @return Downloader\DownloadManager
|
||||
*/
|
||||
public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
|
||||
{
|
||||
$cache = null;
|
||||
if ($config->get('cache-files-ttl') > 0) {
|
||||
|
@ -484,14 +486,14 @@ class Factory
|
|||
$dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs));
|
||||
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
|
||||
$dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
|
||||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
|
||||
return $dm;
|
||||
}
|
||||
|
@ -501,15 +503,9 @@ class Factory
|
|||
* @param Downloader\DownloadManager $dm Manager use to download sources
|
||||
* @return Archiver\ArchiveManager
|
||||
*/
|
||||
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
|
||||
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
|
||||
{
|
||||
if (null === $dm) {
|
||||
$io = new IO\NullIO();
|
||||
$io->loadConfiguration($config);
|
||||
$dm = $this->createDownloadManager($io, $config);
|
||||
}
|
||||
|
||||
$am = new Archiver\ArchiveManager($dm);
|
||||
$am = new Archiver\ArchiveManager($dm, $loop);
|
||||
$am->addArchiver(new Archiver\ZipArchiver);
|
||||
$am->addArchiver(new Archiver\PharArchiver);
|
||||
|
||||
|
@ -531,9 +527,9 @@ class Factory
|
|||
/**
|
||||
* @return Installer\InstallationManager
|
||||
*/
|
||||
protected function createInstallationManager()
|
||||
public function createInstallationManager(Loop $loop)
|
||||
{
|
||||
return new Installer\InstallationManager();
|
||||
return new Installer\InstallationManager($loop);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -579,10 +575,10 @@ class Factory
|
|||
/**
|
||||
* @param IOInterface $io IO instance
|
||||
* @param Config $config Config instance
|
||||
* @param array $options Array of options passed directly to RemoteFilesystem constructor
|
||||
* @return RemoteFilesystem
|
||||
* @param array $options Array of options passed directly to HttpDownloader constructor
|
||||
* @return HttpDownloader
|
||||
*/
|
||||
public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
|
||||
public static function createHttpDownloader(IOInterface $io, Config $config = null, $options = array())
|
||||
{
|
||||
static $warned = false;
|
||||
$disableTls = false;
|
||||
|
@ -596,18 +592,18 @@ class Factory
|
|||
throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
|
||||
. 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
|
||||
}
|
||||
$remoteFilesystemOptions = array();
|
||||
$httpDownloaderOptions = array();
|
||||
if ($disableTls === false) {
|
||||
if ($config && $config->get('cafile')) {
|
||||
$remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
|
||||
$httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
|
||||
}
|
||||
if ($config && $config->get('capath')) {
|
||||
$remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
|
||||
$httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
|
||||
}
|
||||
$remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
|
||||
$httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
|
||||
}
|
||||
try {
|
||||
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
|
||||
$httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls);
|
||||
} catch (TransportException $e) {
|
||||
if (false !== strpos($e->getMessage(), 'cafile')) {
|
||||
$io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
|
||||
|
@ -620,7 +616,7 @@ class Factory
|
|||
throw $e;
|
||||
}
|
||||
|
||||
return $remoteFilesystem;
|
||||
return $httpDownloader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,6 +24,7 @@ use Composer\DependencyResolver\Operation\UninstallOperation;
|
|||
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
|
||||
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
/**
|
||||
* Package operation manager.
|
||||
|
@ -37,6 +38,12 @@ class InstallationManager
|
|||
private $installers = array();
|
||||
private $cache = array();
|
||||
private $notifiablePackages = array();
|
||||
private $loop;
|
||||
|
||||
public function __construct(Loop $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
|
@ -156,7 +163,24 @@ class InstallationManager
|
|||
*/
|
||||
public function execute(RepositoryInterface $repo, OperationInterface $operation)
|
||||
{
|
||||
// TODO this should take all operations in one go
|
||||
$method = $operation->getJobType();
|
||||
|
||||
if ($method === 'install') {
|
||||
$package = $operation->getPackage();
|
||||
$installer = $this->getInstaller($package->getType());
|
||||
$promise = $installer->download($package);
|
||||
} elseif ($method === 'update') {
|
||||
$target = $operation->getTargetPackage();
|
||||
$targetType = $target->getType();
|
||||
$installer = $this->getInstaller($targetType);
|
||||
$promise = $installer->download($target, $operation->getInitialPackage());
|
||||
}
|
||||
|
||||
if (isset($promise)) {
|
||||
$this->loop->wait(array($promise));
|
||||
}
|
||||
|
||||
$this->$method($repo, $operation);
|
||||
}
|
||||
|
||||
|
@ -194,7 +218,8 @@ class InstallationManager
|
|||
$this->markForNotification($target);
|
||||
} else {
|
||||
$this->getInstaller($initialType)->uninstall($repo, $initial);
|
||||
$this->getInstaller($targetType)->install($repo, $target);
|
||||
$installer = $this->getInstaller($targetType);
|
||||
$installer->install($repo, $target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Installer;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use InvalidArgumentException;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Interface for the package installation manager.
|
||||
|
@ -42,6 +43,15 @@ interface InstallerInterface
|
|||
*/
|
||||
public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||
|
||||
/**
|
||||
* Downloads the files needed to later install the given package.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null);
|
||||
|
||||
/**
|
||||
* Installs specific package.
|
||||
*
|
||||
|
|
|
@ -85,6 +85,14 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
|
||||
}
|
||||
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$this->initializeVendorDir();
|
||||
$downloadPath = $this->getInstallPath($package);
|
||||
|
||||
return $this->downloadManager->download($package, $downloadPath, $prevPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -194,7 +202,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
protected function installCode(PackageInterface $package)
|
||||
{
|
||||
$downloadPath = $this->getInstallPath($package);
|
||||
$this->downloadManager->download($package, $downloadPath);
|
||||
$this->downloadManager->install($package, $downloadPath);
|
||||
}
|
||||
|
||||
protected function updateCode(PackageInterface $initial, PackageInterface $target)
|
||||
|
|
|
@ -38,6 +38,14 @@ class MetapackageInstaller implements InstallerInterface
|
|||
return $repo->hasPackage($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -40,6 +40,13 @@ class NoopInstaller implements InstallerInterface
|
|||
return $repo->hasPackage($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -50,13 +50,21 @@ class PluginInstaller extends LibraryInstaller
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$extra = $package->getExtra();
|
||||
if (empty($extra['class'])) {
|
||||
throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
||||
}
|
||||
|
||||
return parent::download($package, $prevPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
{
|
||||
parent::install($repo, $package);
|
||||
try {
|
||||
$this->composer->getPluginManager()->registerPackage($package, true);
|
||||
|
|
|
@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$installPath = $this->installPath;
|
||||
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
|
||||
|
@ -67,7 +67,16 @@ class ProjectInstaller implements InstallerInterface
|
|||
if (!is_dir($installPath)) {
|
||||
mkdir($installPath, 0777, true);
|
||||
}
|
||||
$this->downloadManager->download($package, $installPath);
|
||||
|
||||
return $this->downloadManager->download($package, $installPath, $prevPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
{
|
||||
$this->downloadManager->install($package, $this->installPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Composer\Json;
|
|||
use JsonSchema\Validator;
|
||||
use Seld\JsonLint\JsonParser;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
|
@ -35,25 +35,25 @@ class JsonFile
|
|||
const JSON_UNESCAPED_UNICODE = 256;
|
||||
|
||||
private $path;
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* Initializes json file reader/parser.
|
||||
*
|
||||
* @param string $path path to a lockfile
|
||||
* @param RemoteFilesystem $rfs required for loading http/https json files
|
||||
* @param string $path path to a lockfile
|
||||
* @param HttpDownloader $httpDownloader required for loading http/https json files
|
||||
* @param IOInterface $io
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
|
||||
public function __construct($path, HttpDownloader $httpDownloader = null, IOInterface $io = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
if (null === $rfs && preg_match('{^https?://}i', $path)) {
|
||||
throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
|
||||
if (null === $httpDownloader && preg_match('{^https?://}i', $path)) {
|
||||
throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
|
||||
}
|
||||
$this->rfs = $rfs;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
|
@ -84,8 +84,8 @@ class JsonFile
|
|||
public function read()
|
||||
{
|
||||
try {
|
||||
if ($this->rfs) {
|
||||
$json = $this->rfs->getContents($this->path, $this->path, false);
|
||||
if ($this->httpDownloader) {
|
||||
$json = $this->httpDownloader->get($this->path)->getBody();
|
||||
} else {
|
||||
if ($this->io && $this->io->isDebug()) {
|
||||
$this->io->writeError('Reading ' . $this->path);
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
/**
|
||||
|
@ -25,6 +26,7 @@ use Composer\Json\JsonFile;
|
|||
class ArchiveManager
|
||||
{
|
||||
protected $downloadManager;
|
||||
protected $loop;
|
||||
|
||||
protected $archivers = array();
|
||||
|
||||
|
@ -36,9 +38,10 @@ class ArchiveManager
|
|||
/**
|
||||
* @param DownloadManager $downloadManager A manager used to download package sources
|
||||
*/
|
||||
public function __construct(DownloadManager $downloadManager)
|
||||
public function __construct(DownloadManager $downloadManager, Loop $loop)
|
||||
{
|
||||
$this->downloadManager = $downloadManager;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +151,9 @@ class ArchiveManager
|
|||
$filesystem->ensureDirectoryExists($sourcePath);
|
||||
|
||||
// Download sources
|
||||
$this->downloadManager->download($package, $sourcePath);
|
||||
$promise = $this->downloadManager->download($package, $sourcePath);
|
||||
$this->loop->wait(array($promise));
|
||||
$this->downloadManager->install($package, $sourcePath);
|
||||
|
||||
// Check exclude from downloaded composer.json
|
||||
if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
|
||||
|
|
|
@ -18,7 +18,6 @@ use Composer\Package\Link;
|
|||
use Composer\Package\RootAliasPackage;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Semver\VersionParser as SemverVersionParser;
|
||||
|
||||
/**
|
||||
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
|
||||
|
@ -29,7 +28,7 @@ class ArrayLoader implements LoaderInterface
|
|||
protected $versionParser;
|
||||
protected $loadOptions;
|
||||
|
||||
public function __construct(SemverVersionParser $parser = null, $loadOptions = false)
|
||||
public function __construct(VersionParser $parser = null, $loadOptions = false)
|
||||
{
|
||||
if (!$parser) {
|
||||
$parser = new VersionParser;
|
||||
|
@ -39,6 +38,69 @@ class ArrayLoader implements LoaderInterface
|
|||
}
|
||||
|
||||
public function load(array $config, $class = 'Composer\Package\CompletePackage')
|
||||
{
|
||||
$package = $this->createObject($config, $class);
|
||||
|
||||
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
|
||||
if (isset($config[$type])) {
|
||||
$method = 'set'.ucfirst($opts['method']);
|
||||
$package->{$method}(
|
||||
$this->parseLinks(
|
||||
$package->getName(),
|
||||
$package->getPrettyVersion(),
|
||||
$opts['description'],
|
||||
$config[$type]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$package = $this->configureObject($package, $config);
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
public function loadPackages(array $versions, $class)
|
||||
{
|
||||
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
|
||||
|
||||
$packages = array();
|
||||
$linkCache = array();
|
||||
|
||||
foreach ($versions as $version) {
|
||||
if (isset($version['versions'])) {
|
||||
$baseVersion = $version;
|
||||
foreach ($uniqKeys as $key) {
|
||||
unset($baseVersion[$key.'s']);
|
||||
}
|
||||
|
||||
foreach ($version['versions'] as $index => $dummy) {
|
||||
$unpackedVersion = $baseVersion;
|
||||
foreach ($uniqKeys as $key) {
|
||||
$unpackedVersion[$key] = $version[$key.'s'][$index];
|
||||
}
|
||||
|
||||
$package = $this->createObject($unpackedVersion, $class);
|
||||
|
||||
$this->configureCachedLinks($linkCache, $package, $unpackedVersion);
|
||||
$package = $this->configureObject($package, $unpackedVersion);
|
||||
|
||||
$packages[] = $package;
|
||||
}
|
||||
} else {
|
||||
$package = $this->createObject($version, $class);
|
||||
|
||||
$this->configureCachedLinks($linkCache, $package, $version);
|
||||
$package = $this->configureObject($package, $version);
|
||||
|
||||
$packages[] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
private function createObject(array $config, $class)
|
||||
{
|
||||
if (!isset($config['name'])) {
|
||||
throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
|
||||
|
@ -53,7 +115,12 @@ class ArrayLoader implements LoaderInterface
|
|||
} else {
|
||||
$version = $this->versionParser->normalize($config['version']);
|
||||
}
|
||||
$package = new $class($config['name'], $version, $config['version']);
|
||||
|
||||
return new $class($config['name'], $version, $config['version']);
|
||||
}
|
||||
|
||||
private function configureObject($package, array $config)
|
||||
{
|
||||
$package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');
|
||||
|
||||
if (isset($config['target-dir'])) {
|
||||
|
@ -110,20 +177,6 @@ class ArrayLoader implements LoaderInterface
|
|||
}
|
||||
}
|
||||
|
||||
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
|
||||
if (isset($config[$type])) {
|
||||
$method = 'set'.ucfirst($opts['method']);
|
||||
$package->{$method}(
|
||||
$this->parseLinks(
|
||||
$package->getName(),
|
||||
$package->getPrettyVersion(),
|
||||
$opts['description'],
|
||||
$config[$type]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['suggest']) && is_array($config['suggest'])) {
|
||||
foreach ($config['suggest'] as $target => $reason) {
|
||||
if ('self.version' === trim($reason)) {
|
||||
|
@ -203,21 +256,50 @@ class ArrayLoader implements LoaderInterface
|
|||
}
|
||||
}
|
||||
|
||||
if ($aliasNormalized = $this->getBranchAlias($config)) {
|
||||
if ($package instanceof RootPackageInterface) {
|
||||
$package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
|
||||
} else {
|
||||
$package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->loadOptions && isset($config['transport-options'])) {
|
||||
$package->setTransportOptions($config['transport-options']);
|
||||
}
|
||||
|
||||
if ($aliasNormalized = $this->getBranchAlias($config)) {
|
||||
if ($package instanceof RootPackageInterface) {
|
||||
return new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
|
||||
}
|
||||
|
||||
return new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
private function configureCachedLinks(&$linkCache, $package, array $config)
|
||||
{
|
||||
$name = $package->getName();
|
||||
$prettyVersion = $package->getPrettyVersion();
|
||||
|
||||
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
|
||||
if (isset($config[$type])) {
|
||||
$method = 'set'.ucfirst($opts['method']);
|
||||
|
||||
$links = array();
|
||||
foreach ($config[$type] as $prettyTarget => $constraint) {
|
||||
$target = strtolower($prettyTarget);
|
||||
if ($constraint === 'self.version') {
|
||||
$links[$target] = $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint);
|
||||
} else {
|
||||
if (!isset($linkCache[$name][$type][$target][$constraint])) {
|
||||
$linkCache[$name][$type][$target][$constraint] = array($target, $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint));
|
||||
}
|
||||
|
||||
list($target, $link) = $linkCache[$name][$type][$target][$constraint];
|
||||
$links[$target] = $link;
|
||||
}
|
||||
}
|
||||
|
||||
$package->{$method}($links);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source source package name
|
||||
* @param string $sourceVersion source package version (pretty version ideally)
|
||||
|
@ -229,21 +311,26 @@ class ArrayLoader implements LoaderInterface
|
|||
{
|
||||
$res = array();
|
||||
foreach ($links as $target => $constraint) {
|
||||
if (!is_string($constraint)) {
|
||||
throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')');
|
||||
}
|
||||
if ('self.version' === $constraint) {
|
||||
$parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
|
||||
} else {
|
||||
$parsedConstraint = $this->versionParser->parseConstraints($constraint);
|
||||
}
|
||||
|
||||
$res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint);
|
||||
$res[strtolower($target)] = $this->createLink($source, $sourceVersion, $description, $target, $constraint);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
private function createLink($source, $sourceVersion, $description, $target, $prettyConstraint)
|
||||
{
|
||||
if (!is_string($prettyConstraint)) {
|
||||
throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')');
|
||||
}
|
||||
if ('self.version' === $prettyConstraint) {
|
||||
$parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
|
||||
} else {
|
||||
$parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint);
|
||||
}
|
||||
|
||||
return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
|
||||
*
|
||||
|
|
|
@ -192,7 +192,8 @@ class VersionGuesser
|
|||
}
|
||||
|
||||
// re-use the HgDriver to fetch branches (this properly includes bookmarks)
|
||||
$driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process);
|
||||
$io = new NullIO();
|
||||
$driver = new HgDriver(array('url' => $path), $io, $this->config, new HttpDownloader($io, $this->config), $this->process);
|
||||
$branches = array_keys($driver->getBranches());
|
||||
|
||||
// try to find the best (nearest) version branch to assume this feature's version
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
namespace Composer\Plugin;
|
||||
|
||||
use Composer\EventDispatcher\Event;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* The pre file download event.
|
||||
|
@ -23,9 +23,9 @@ use Composer\Util\RemoteFilesystem;
|
|||
class PreFileDownloadEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var RemoteFilesystem
|
||||
* @var HttpDownloader
|
||||
*/
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
@ -36,34 +36,22 @@ class PreFileDownloadEvent extends Event
|
|||
* Constructor.
|
||||
*
|
||||
* @param string $name The event name
|
||||
* @param RemoteFilesystem $rfs
|
||||
* @param HttpDownloader $httpDownloader
|
||||
* @param string $processedUrl
|
||||
*/
|
||||
public function __construct($name, RemoteFilesystem $rfs, $processedUrl)
|
||||
public function __construct($name, HttpDownloader $httpDownloader, $processedUrl)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->rfs = $rfs;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->processedUrl = $processedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote filesystem
|
||||
*
|
||||
* @return RemoteFilesystem
|
||||
* @return HttpDownloader
|
||||
*/
|
||||
public function getRemoteFilesystem()
|
||||
public function getHttpDownloader()
|
||||
{
|
||||
return $this->rfs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote filesystem
|
||||
*
|
||||
* @param RemoteFilesystem $rfs
|
||||
*/
|
||||
public function setRemoteFilesystem(RemoteFilesystem $rfs)
|
||||
{
|
||||
$this->rfs = $rfs;
|
||||
return $this->httpDownloader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace Composer\Repository\Pear;
|
||||
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* Base PEAR Channel reader.
|
||||
|
@ -33,12 +33,12 @@ abstract class BaseChannelReader
|
|||
const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases';
|
||||
const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package';
|
||||
|
||||
/** @var RemoteFilesystem */
|
||||
private $rfs;
|
||||
/** @var HttpDownloader */
|
||||
private $httpDownloader;
|
||||
|
||||
protected function __construct(RemoteFilesystem $rfs)
|
||||
protected function __construct(HttpDownloader $httpDownloader)
|
||||
{
|
||||
$this->rfs = $rfs;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,7 +52,11 @@ abstract class BaseChannelReader
|
|||
protected function requestContent($origin, $path)
|
||||
{
|
||||
$url = rtrim($origin, '/') . '/' . ltrim($path, '/');
|
||||
$content = $this->rfs->getContents($origin, $url, false);
|
||||
try {
|
||||
$content = $this->httpDownloader->get($url)->getBody();
|
||||
} catch (\Exception $e) {
|
||||
throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.', 0, $e);
|
||||
}
|
||||
if (!$content) {
|
||||
throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.');
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace Composer\Repository\Pear;
|
||||
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* PEAR Channel package reader.
|
||||
|
@ -26,12 +26,12 @@ class ChannelReader extends BaseChannelReader
|
|||
/** @var array of ('xpath test' => 'rest implementation') */
|
||||
private $readerMap;
|
||||
|
||||
public function __construct(RemoteFilesystem $rfs)
|
||||
public function __construct(HttpDownloader $httpDownloader)
|
||||
{
|
||||
parent::__construct($rfs);
|
||||
parent::__construct($httpDownloader);
|
||||
|
||||
$rest10reader = new ChannelRest10Reader($rfs);
|
||||
$rest11reader = new ChannelRest11Reader($rfs);
|
||||
$rest10reader = new ChannelRest10Reader($httpDownloader);
|
||||
$rest11reader = new ChannelRest11Reader($httpDownloader);
|
||||
|
||||
$this->readerMap = array(
|
||||
'REST1.3' => $rest11reader,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Repository\Pear;
|
||||
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* Read PEAR packages using REST 1.0 interface
|
||||
|
@ -29,9 +30,9 @@ class ChannelRest10Reader extends BaseChannelReader
|
|||
{
|
||||
private $dependencyReader;
|
||||
|
||||
public function __construct($rfs)
|
||||
public function __construct(HttpDownloader $httpDownloader)
|
||||
{
|
||||
parent::__construct($rfs);
|
||||
parent::__construct($httpDownloader);
|
||||
|
||||
$this->dependencyReader = new PackageDependencyParser();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Repository\Pear;
|
||||
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* Read PEAR packages using REST 1.1 interface
|
||||
*
|
||||
|
@ -25,9 +27,9 @@ class ChannelRest11Reader extends BaseChannelReader
|
|||
{
|
||||
private $dependencyReader;
|
||||
|
||||
public function __construct($rfs)
|
||||
public function __construct(HttpDownloader $httpDownloader)
|
||||
{
|
||||
parent::__construct($rfs);
|
||||
parent::__construct($httpDownloader);
|
||||
|
||||
$this->dependencyReader = new PackageDependencyParser();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use Composer\Repository\Pear\ChannelInfo;
|
|||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Config;
|
||||
use Composer\Factory;
|
||||
|
||||
|
@ -38,7 +38,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
|||
{
|
||||
private $url;
|
||||
private $io;
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
private $versionParser;
|
||||
private $repoConfig;
|
||||
|
||||
|
@ -47,7 +47,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
|||
*/
|
||||
private $vendorAlias;
|
||||
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null)
|
||||
{
|
||||
parent::__construct();
|
||||
if (!preg_match('{^https?://}', $repoConfig['url'])) {
|
||||
|
@ -61,7 +61,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
|||
|
||||
$this->url = rtrim($repoConfig['url'], '/');
|
||||
$this->io = $io;
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null;
|
||||
$this->versionParser = new VersionParser();
|
||||
$this->repoConfig = $repoConfig;
|
||||
|
@ -78,7 +78,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
|||
|
||||
$this->io->writeError('Initializing PEAR repository '.$this->url);
|
||||
|
||||
$reader = new ChannelReader($this->rfs);
|
||||
$reader = new ChannelReader($this->httpDownloader);
|
||||
try {
|
||||
$channelInfo = $reader->read($this->url);
|
||||
} catch (\Exception $e) {
|
||||
|
|
|
@ -308,6 +308,10 @@ class PlatformRepository extends ArrayRepository
|
|||
$this->addPackage($ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
private function buildPackageName($name)
|
||||
{
|
||||
return 'ext-' . str_replace(' ', '-', $name);
|
||||
|
|
|
@ -16,7 +16,7 @@ use Composer\Factory;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\Config;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@ class RepositoryFactory
|
|||
if (0 === strpos($repository, 'http')) {
|
||||
$repoConfig = array('type' => 'composer', 'url' => $repository);
|
||||
} elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) {
|
||||
$json = new JsonFile($repository, Factory::createRemoteFilesystem($io, $config));
|
||||
$json = new JsonFile($repository, Factory::createHttpDownloader($io, $config));
|
||||
$data = $json->read();
|
||||
if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
|
||||
$repoConfig = array('type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/'));
|
||||
|
@ -77,7 +77,7 @@ class RepositoryFactory
|
|||
*/
|
||||
public static function createRepo(IOInterface $io, Config $config, array $repoConfig)
|
||||
{
|
||||
$rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config));
|
||||
$rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config));
|
||||
$repos = static::createRepos($rm, array($repoConfig));
|
||||
|
||||
return reset($repos);
|
||||
|
@ -98,7 +98,7 @@ class RepositoryFactory
|
|||
if (!$io) {
|
||||
throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
|
||||
}
|
||||
$rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config));
|
||||
$rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config));
|
||||
}
|
||||
|
||||
return static::createRepos($rm, $config->getRepositories());
|
||||
|
@ -108,12 +108,12 @@ class RepositoryFactory
|
|||
* @param IOInterface $io
|
||||
* @param Config $config
|
||||
* @param EventDispatcher $eventDispatcher
|
||||
* @param RemoteFilesystem $rfs
|
||||
* @param HttpDownloader $httpDownloader
|
||||
* @return RepositoryManager
|
||||
*/
|
||||
public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
|
||||
{
|
||||
$rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs);
|
||||
$rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher);
|
||||
$rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
|
||||
$rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
|
||||
$rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Repository;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
|
||||
/**
|
||||
* Repository interface.
|
||||
|
@ -38,8 +39,8 @@ interface RepositoryInterface extends \Countable
|
|||
/**
|
||||
* Searches for the first match of a package by name and version.
|
||||
*
|
||||
* @param string $name package name
|
||||
* @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
|
||||
* @param string $name package name
|
||||
* @param string|ConstraintInterface $constraint package version or version constraint to match against
|
||||
*
|
||||
* @return PackageInterface|null
|
||||
*/
|
||||
|
@ -48,8 +49,8 @@ interface RepositoryInterface extends \Countable
|
|||
/**
|
||||
* Searches for all packages matching a name and optionally a version.
|
||||
*
|
||||
* @param string $name package name
|
||||
* @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
|
||||
* @param string $name package name
|
||||
* @param string|ConstraintInterface $constraint package version or version constraint to match against
|
||||
*
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
|
@ -66,7 +67,7 @@ interface RepositoryInterface extends \Countable
|
|||
/**
|
||||
* Returns list of registered packages with the supplied name
|
||||
*
|
||||
* @param bool[] $packageNameMap
|
||||
* @param ConstraintInterface[] $packageNameMap package names pointing to constraints
|
||||
* @param $isPackageAcceptableCallable
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
|
|
|
@ -16,7 +16,7 @@ use Composer\IO\IOInterface;
|
|||
use Composer\Config;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
/**
|
||||
* Repositories manager.
|
||||
|
@ -33,14 +33,14 @@ class RepositoryManager
|
|||
private $io;
|
||||
private $config;
|
||||
private $eventDispatcher;
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,8 +127,8 @@ class RepositoryManager
|
|||
|
||||
$reflMethod = new \ReflectionMethod($class, '__construct');
|
||||
$params = $reflMethod->getParameters();
|
||||
if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') {
|
||||
return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs);
|
||||
if (isset($params[3]) && $params[3]->getClass() && $params[3]->getClass()->getName() === 'Composer\Util\HttpDownloader') {
|
||||
return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher);
|
||||
}
|
||||
|
||||
return new $class($config, $this->io, $this->config, $this->eventDispatcher);
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Cache;
|
|||
use Composer\Downloader\TransportException;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Util\Bitbucket;
|
||||
use Composer\Util\Http\Response;
|
||||
|
||||
abstract class BitbucketDriver extends VcsDriver
|
||||
{
|
||||
|
@ -92,7 +93,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
)
|
||||
);
|
||||
|
||||
$repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource);
|
||||
$repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson();
|
||||
if ($this->fallbackDriver) {
|
||||
return false;
|
||||
}
|
||||
|
@ -204,7 +205,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
$file
|
||||
);
|
||||
|
||||
return $this->getContentsWithOAuthCredentials($resource);
|
||||
return $this->fetchWithOAuthCredentials($resource)->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,7 +223,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
$this->repository,
|
||||
$identifier
|
||||
);
|
||||
$commit = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
|
||||
$commit = $this->fetchWithOAuthCredentials($resource)->decodeJson();
|
||||
|
||||
return new \DateTime($commit['date']);
|
||||
}
|
||||
|
@ -284,7 +285,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
);
|
||||
$hasNext = true;
|
||||
while ($hasNext) {
|
||||
$tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
|
||||
$tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
|
||||
foreach ($tagsData['values'] as $data) {
|
||||
$this->tags[$data['name']] = $data['target']['hash'];
|
||||
}
|
||||
|
@ -328,7 +329,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
);
|
||||
$hasNext = true;
|
||||
while ($hasNext) {
|
||||
$branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
|
||||
$branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
|
||||
foreach ($branchData['values'] as $data) {
|
||||
// skip headless branches which seem to be deleted branches that bitbucket nevertheless returns in the API
|
||||
if ($this->vcsType === 'hg' && empty($data['heads'])) {
|
||||
|
@ -354,14 +355,14 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
* @param string $url The URL of content
|
||||
* @param bool $fetchingRepoData
|
||||
*
|
||||
* @return mixed The result
|
||||
* @return Response The result
|
||||
*/
|
||||
protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false)
|
||||
protected function fetchWithOAuthCredentials($url, $fetchingRepoData = false)
|
||||
{
|
||||
try {
|
||||
return parent::getContents($url);
|
||||
} catch (TransportException $e) {
|
||||
$bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem);
|
||||
$bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader);
|
||||
|
||||
if (403 === $e->getCode() || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) {
|
||||
if (!$this->io->hasAuthentication($this->originUrl)
|
||||
|
@ -371,7 +372,9 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (!$this->io->isInteractive() && $fetchingRepoData) {
|
||||
return $this->attemptCloneFallback();
|
||||
if ($this->attemptCloneFallback()) {
|
||||
return new Response(array('url' => 'dummy'), 200, array(), 'null');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,6 +393,8 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
{
|
||||
try {
|
||||
$this->setupFallbackDriver($this->generateSshUrl());
|
||||
|
||||
return true;
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->fallbackDriver = null;
|
||||
|
||||
|
@ -433,7 +438,7 @@ abstract class BitbucketDriver extends VcsDriver
|
|||
$this->repository
|
||||
);
|
||||
|
||||
$data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
|
||||
$data = $this->fetchWithOAuthCredentials($resource)->decodeJson();
|
||||
if (isset($data['mainbranch'])) {
|
||||
return $data['mainbranch'];
|
||||
}
|
||||
|
|
|
@ -75,8 +75,8 @@ class GitBitbucketDriver extends BitbucketDriver
|
|||
array('url' => $url),
|
||||
$this->io,
|
||||
$this->config,
|
||||
$this->process,
|
||||
$this->remoteFilesystem
|
||||
$this->httpDownloader,
|
||||
$this->process
|
||||
);
|
||||
$this->fallbackDriver->initialize();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ use Composer\Json\JsonFile;
|
|||
use Composer\Cache;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\GitHub;
|
||||
use Composer\Util\Http\Response;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -184,7 +186,7 @@ class GitHubDriver extends VcsDriver
|
|||
}
|
||||
|
||||
$resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier);
|
||||
$resource = JsonFile::parseJson($this->getContents($resource));
|
||||
$resource = $this->getContents($resource)->decodeJson();
|
||||
if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) {
|
||||
throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
|
||||
}
|
||||
|
@ -202,7 +204,7 @@ class GitHubDriver extends VcsDriver
|
|||
}
|
||||
|
||||
$resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
|
||||
$commit = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
$commit = $this->getContents($resource)->decodeJson();
|
||||
|
||||
return new \DateTime($commit['commit']['committer']['date']);
|
||||
}
|
||||
|
@ -220,12 +222,13 @@ class GitHubDriver extends VcsDriver
|
|||
$resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100';
|
||||
|
||||
do {
|
||||
$tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
$response = $this->getContents($resource);
|
||||
$tagsData = $response->decodeJson();
|
||||
foreach ($tagsData as $tag) {
|
||||
$this->tags[$tag['name']] = $tag['commit']['sha'];
|
||||
}
|
||||
|
||||
$resource = $this->getNextPage();
|
||||
$resource = $this->getNextPage($response);
|
||||
} while ($resource);
|
||||
}
|
||||
|
||||
|
@ -247,7 +250,8 @@ class GitHubDriver extends VcsDriver
|
|||
$branchBlacklist = array('gh-pages');
|
||||
|
||||
do {
|
||||
$branchData = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
$response = $this->getContents($resource);
|
||||
$branchData = $response->decodeJson();
|
||||
foreach ($branchData as $branch) {
|
||||
$name = substr($branch['ref'], 11);
|
||||
if (!in_array($name, $branchBlacklist)) {
|
||||
|
@ -255,7 +259,7 @@ class GitHubDriver extends VcsDriver
|
|||
}
|
||||
}
|
||||
|
||||
$resource = $this->getNextPage();
|
||||
$resource = $this->getNextPage($response);
|
||||
} while ($resource);
|
||||
}
|
||||
|
||||
|
@ -315,7 +319,7 @@ class GitHubDriver extends VcsDriver
|
|||
try {
|
||||
return parent::getContents($url);
|
||||
} catch (TransportException $e) {
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem);
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader);
|
||||
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
|
@ -330,16 +334,18 @@ class GitHubDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (!$this->io->isInteractive()) {
|
||||
return $this->attemptCloneFallback();
|
||||
if ($this->attemptCloneFallback()) {
|
||||
return new Response(array('url' => 'dummy'), 200, array(), 'null');
|
||||
}
|
||||
}
|
||||
|
||||
$scopesIssued = array();
|
||||
$scopesNeeded = array();
|
||||
if ($headers = $e->getHeaders()) {
|
||||
if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) {
|
||||
if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-OAuth-Scopes')) {
|
||||
$scopesIssued = explode(' ', $scopes);
|
||||
}
|
||||
if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
|
||||
if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
|
||||
$scopesNeeded = explode(' ', $scopes);
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +364,9 @@ class GitHubDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (!$this->io->isInteractive() && $fetchingRepoData) {
|
||||
return $this->attemptCloneFallback();
|
||||
if ($this->attemptCloneFallback()) {
|
||||
return new Response(array('url' => 'dummy'), 200, array(), 'null');
|
||||
}
|
||||
}
|
||||
|
||||
$rateLimited = $gitHubUtil->isRateLimited($e->getHeaders());
|
||||
|
@ -404,7 +412,7 @@ class GitHubDriver extends VcsDriver
|
|||
|
||||
$repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;
|
||||
|
||||
$this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl);
|
||||
$this->repoData = $this->getContents($repoDataUrl, true)->decodeJson();
|
||||
if (null === $this->repoData && null !== $this->gitDriver) {
|
||||
return;
|
||||
}
|
||||
|
@ -434,7 +442,7 @@ class GitHubDriver extends VcsDriver
|
|||
// are not interactive) then we fallback to GitDriver.
|
||||
$this->setupGitDriver($this->generateSshUrl());
|
||||
|
||||
return;
|
||||
return true;
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->gitDriver = null;
|
||||
|
||||
|
@ -449,23 +457,20 @@ class GitHubDriver extends VcsDriver
|
|||
array('url' => $url),
|
||||
$this->io,
|
||||
$this->config,
|
||||
$this->process,
|
||||
$this->remoteFilesystem
|
||||
$this->httpDownloader,
|
||||
$this->process
|
||||
);
|
||||
$this->gitDriver->initialize();
|
||||
}
|
||||
|
||||
protected function getNextPage()
|
||||
protected function getNextPage(Response $response)
|
||||
{
|
||||
$headers = $this->remoteFilesystem->getLastHeaders();
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
|
||||
$links = explode(',', $match[1]);
|
||||
foreach ($links as $link) {
|
||||
if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
}
|
||||
$header = $response->getHeader('link');
|
||||
|
||||
$links = explode(',', $header);
|
||||
foreach ($links as $link) {
|
||||
if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ use Composer\Cache;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\GitLab;
|
||||
use Composer\Util\Http\Response;
|
||||
|
||||
/**
|
||||
* Driver for GitLab API, use the Git driver for local checkouts.
|
||||
|
@ -110,14 +111,14 @@ class GitLabDriver extends VcsDriver
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the RemoteFilesystem instance.
|
||||
* Updates the HttpDownloader instance.
|
||||
* Mainly useful for tests.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem)
|
||||
public function setHttpDownloader(HttpDownloader $httpDownloader)
|
||||
{
|
||||
$this->remoteFilesystem = $remoteFilesystem;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +141,7 @@ class GitLabDriver extends VcsDriver
|
|||
$resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier;
|
||||
|
||||
try {
|
||||
$content = $this->getContents($resource);
|
||||
$content = $this->getContents($resource)->getBody();
|
||||
} catch (TransportException $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
throw $e;
|
||||
|
@ -297,7 +298,8 @@ class GitLabDriver extends VcsDriver
|
|||
|
||||
$references = array();
|
||||
do {
|
||||
$data = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
$response = $this->getContents($resource);
|
||||
$data = $response->decodeJson();
|
||||
|
||||
foreach ($data as $datum) {
|
||||
$references[$datum['name']] = $datum['commit']['id'];
|
||||
|
@ -308,7 +310,7 @@ class GitLabDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (count($data) >= $perPage) {
|
||||
$resource = $this->getNextPage();
|
||||
$resource = $this->getNextPage($response);
|
||||
} else {
|
||||
$resource = false;
|
||||
}
|
||||
|
@ -321,7 +323,7 @@ class GitLabDriver extends VcsDriver
|
|||
{
|
||||
// we need to fetch the default branch from the api
|
||||
$resource = $this->getApiUrl();
|
||||
$this->project = JsonFile::parseJson($this->getContents($resource, true), $resource);
|
||||
$this->project = $this->getContents($resource, true)->decodeJson();
|
||||
if (isset($this->project['visibility'])) {
|
||||
$this->isPrivate = $this->project['visibility'] !== 'public';
|
||||
} else {
|
||||
|
@ -344,7 +346,7 @@ class GitLabDriver extends VcsDriver
|
|||
// are not interactive) then we fallback to GitDriver.
|
||||
$this->setupGitDriver($url);
|
||||
|
||||
return;
|
||||
return true;
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->gitDriver = null;
|
||||
|
||||
|
@ -374,8 +376,8 @@ class GitLabDriver extends VcsDriver
|
|||
array('url' => $url),
|
||||
$this->io,
|
||||
$this->config,
|
||||
$this->process,
|
||||
$this->remoteFilesystem
|
||||
$this->httpDownloader,
|
||||
$this->process
|
||||
);
|
||||
$this->gitDriver->initialize();
|
||||
}
|
||||
|
@ -386,10 +388,10 @@ class GitLabDriver extends VcsDriver
|
|||
protected function getContents($url, $fetchingRepoData = false)
|
||||
{
|
||||
try {
|
||||
$res = parent::getContents($url);
|
||||
$response = parent::getContents($url);
|
||||
|
||||
if ($fetchingRepoData) {
|
||||
$json = JsonFile::parseJson($res, $url);
|
||||
$json = $response->decodeJson();
|
||||
|
||||
// force auth as the unauthenticated version of the API is broken
|
||||
if (!isset($json['default_branch'])) {
|
||||
|
@ -401,9 +403,9 @@ class GitLabDriver extends VcsDriver
|
|||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $response;
|
||||
} catch (TransportException $e) {
|
||||
$gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem);
|
||||
$gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader);
|
||||
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
|
@ -418,7 +420,9 @@ class GitLabDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (!$this->io->isInteractive()) {
|
||||
return $this->attemptCloneFallback();
|
||||
if ($this->attemptCloneFallback()) {
|
||||
return new Response(array('url' => 'dummy'), 200, array(), 'null');
|
||||
}
|
||||
}
|
||||
$this->io->writeError('<warning>Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
|
||||
$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');
|
||||
|
@ -431,7 +435,9 @@ class GitLabDriver extends VcsDriver
|
|||
}
|
||||
|
||||
if (!$this->io->isInteractive() && $fetchingRepoData) {
|
||||
return $this->attemptCloneFallback();
|
||||
if ($this->attemptCloneFallback()) {
|
||||
return new Response(array('url' => 'dummy'), 200, array(), 'null');
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
|
@ -471,17 +477,14 @@ class GitLabDriver extends VcsDriver
|
|||
return true;
|
||||
}
|
||||
|
||||
private function getNextPage()
|
||||
protected function getNextPage(Response $response)
|
||||
{
|
||||
$headers = $this->remoteFilesystem->getLastHeaders();
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
|
||||
$links = explode(',', $match[1]);
|
||||
foreach ($links as $link) {
|
||||
if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
}
|
||||
$header = $response->getHeader('link');
|
||||
|
||||
$links = explode(',', $header);
|
||||
foreach ($links as $link) {
|
||||
if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,8 @@ class HgBitbucketDriver extends BitbucketDriver
|
|||
array('url' => $url),
|
||||
$this->io,
|
||||
$this->config,
|
||||
$this->process,
|
||||
$this->remoteFilesystem
|
||||
$this->httpDownloader,
|
||||
$this->process
|
||||
);
|
||||
$this->fallbackDriver->initialize();
|
||||
}
|
||||
|
|
|
@ -19,8 +19,9 @@ use Composer\Factory;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Http\Response;
|
||||
|
||||
/**
|
||||
* A driver implementation for driver with authentication interaction.
|
||||
|
@ -41,8 +42,8 @@ abstract class VcsDriver implements VcsDriverInterface
|
|||
protected $config;
|
||||
/** @var ProcessExecutor */
|
||||
protected $process;
|
||||
/** @var RemoteFilesystem */
|
||||
protected $remoteFilesystem;
|
||||
/** @var HttpDownloader */
|
||||
protected $httpDownloader;
|
||||
/** @var array */
|
||||
protected $infoCache = array();
|
||||
/** @var Cache */
|
||||
|
@ -54,10 +55,10 @@ abstract class VcsDriver implements VcsDriverInterface
|
|||
* @param array $repoConfig The repository configuration
|
||||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The composer configuration
|
||||
* @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking
|
||||
* @param ProcessExecutor $process Process instance, injectable for mocking
|
||||
* @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
|
||||
*/
|
||||
final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
|
||||
final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process)
|
||||
{
|
||||
if (Filesystem::isLocalPath($repoConfig['url'])) {
|
||||
$repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']);
|
||||
|
@ -68,8 +69,8 @@ abstract class VcsDriver implements VcsDriverInterface
|
|||
$this->repoConfig = $repoConfig;
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->process = $process;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,13 +157,13 @@ abstract class VcsDriver implements VcsDriverInterface
|
|||
*
|
||||
* @param string $url The URL of content
|
||||
*
|
||||
* @return mixed The result
|
||||
* @return Response
|
||||
*/
|
||||
protected function getContents($url)
|
||||
{
|
||||
$options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array();
|
||||
|
||||
return $this->remoteFilesystem->getContents($this->originUrl, $url, false, $options);
|
||||
return $this->httpDownloader->get($url, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,8 @@ use Composer\Package\Loader\ValidatingArrayLoader;
|
|||
use Composer\Package\Loader\InvalidPackageException;
|
||||
use Composer\Package\Loader\LoaderInterface;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Config;
|
||||
|
||||
|
@ -37,6 +39,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
protected $type;
|
||||
protected $loader;
|
||||
protected $repoConfig;
|
||||
protected $httpDownloader;
|
||||
protected $processExecutor;
|
||||
protected $branchErrorOccurred = false;
|
||||
private $drivers;
|
||||
/** @var VcsDriverInterface */
|
||||
|
@ -44,7 +48,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
/** @var VersionCacheInterface */
|
||||
private $versionCache;
|
||||
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->drivers = $drivers ?: array(
|
||||
|
@ -67,6 +71,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
$this->config = $config;
|
||||
$this->repoConfig = $repoConfig;
|
||||
$this->versionCache = $versionCache;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->processExecutor = new ProcessExecutor($io);
|
||||
}
|
||||
|
||||
public function getRepoConfig()
|
||||
|
@ -87,7 +93,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
|
||||
if (isset($this->drivers[$this->type])) {
|
||||
$class = $this->drivers[$this->type];
|
||||
$this->driver = new $class($this->repoConfig, $this->io, $this->config);
|
||||
$this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
|
||||
$this->driver->initialize();
|
||||
|
||||
return $this->driver;
|
||||
|
@ -95,7 +101,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
|
||||
foreach ($this->drivers as $driver) {
|
||||
if ($driver::supports($this->io, $this->config, $this->url)) {
|
||||
$this->driver = new $driver($this->repoConfig, $this->io, $this->config);
|
||||
$this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
|
||||
$this->driver->initialize();
|
||||
|
||||
return $this->driver;
|
||||
|
@ -104,7 +110,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
|
|||
|
||||
foreach ($this->drivers as $driver) {
|
||||
if ($driver::supports($this->io, $this->config, $this->url, true)) {
|
||||
$this->driver = new $driver($this->repoConfig, $this->io, $this->config);
|
||||
$this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
|
||||
$this->driver->initialize();
|
||||
|
||||
return $this->driver;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace Composer\SelfUpdate;
|
||||
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Config;
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
|
@ -21,13 +21,13 @@ use Composer\Json\JsonFile;
|
|||
*/
|
||||
class Versions
|
||||
{
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
private $config;
|
||||
private $channel;
|
||||
|
||||
public function __construct(Config $config, RemoteFilesystem $rfs)
|
||||
public function __construct(Config $config, HttpDownloader $httpDownloader)
|
||||
{
|
||||
$this->rfs = $rfs;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ class Versions
|
|||
public function getLatest()
|
||||
{
|
||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||
$versions = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false));
|
||||
$versions = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson();
|
||||
|
||||
foreach ($versions[$this->getChannel()] as $version) {
|
||||
if ($version['min-php'] <= PHP_VERSION_ID) {
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Util;
|
|||
|
||||
use Composer\Config;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -29,7 +30,11 @@ class AuthHelper
|
|||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function storeAuth($originUrl, $storeAuth)
|
||||
/**
|
||||
* @param string $origin
|
||||
* @param string|bool $storeAuth
|
||||
*/
|
||||
public function storeAuth($origin, $storeAuth)
|
||||
{
|
||||
$store = false;
|
||||
$configSource = $this->config->getAuthConfigSource();
|
||||
|
@ -37,7 +42,7 @@ class AuthHelper
|
|||
$store = $configSource;
|
||||
} elseif ($storeAuth === 'prompt') {
|
||||
$answer = $this->io->askAndValidate(
|
||||
'Do you want to store credentials for '.$originUrl.' in '.$configSource->getName().' ? [Yn] ',
|
||||
'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ',
|
||||
function ($value) {
|
||||
$input = strtolower(substr(trim($value), 0, 1));
|
||||
if (in_array($input, array('y','n'))) {
|
||||
|
@ -55,9 +60,192 @@ class AuthHelper
|
|||
}
|
||||
if ($store) {
|
||||
$store->addConfigSetting(
|
||||
'http-basic.'.$originUrl,
|
||||
$this->io->getAuthentication($originUrl)
|
||||
'http-basic.'.$origin,
|
||||
$this->io->getAuthentication($origin)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $origin
|
||||
* @param int $statusCode HTTP status code that triggered this call
|
||||
* @param string|null $reason a message/description explaining why this was called
|
||||
* @param string $warning an authentication warning returned by the server as {"warning": ".."}, if present
|
||||
* @param string[] $headers
|
||||
* @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be
|
||||
* retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json
|
||||
*/
|
||||
public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array())
|
||||
{
|
||||
$storeAuth = false;
|
||||
$retry = false;
|
||||
|
||||
if (in_array($origin, $this->config->get('github-domains'), true)) {
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, null);
|
||||
$message = "\n";
|
||||
|
||||
$rateLimited = $gitHubUtil->isRateLimited($headers);
|
||||
if ($rateLimited) {
|
||||
$rateLimit = $gitHubUtil->getRateLimit($headers);
|
||||
if ($this->io->hasAuthentication($origin)) {
|
||||
$message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
|
||||
} else {
|
||||
$message = 'Create a GitHub OAuth token to go over the API rate limit.';
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.',
|
||||
$rateLimit['limit'],
|
||||
$rateLimit['reset']
|
||||
)."\n";
|
||||
} else {
|
||||
$message .= 'Could not fetch '.$url.', please ';
|
||||
if ($this->io->hasAuthentication($origin)) {
|
||||
$message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
|
||||
} else {
|
||||
$message .= 'create a GitHub OAuth token to access private repos';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gitHubUtil->authorizeOAuth($origin)
|
||||
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against '.$origin, 401);
|
||||
}
|
||||
} elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
|
||||
$message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
|
||||
$gitLabUtil = new GitLab($this->io, $this->config, null);
|
||||
|
||||
if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') {
|
||||
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
|
||||
}
|
||||
|
||||
if (!$gitLabUtil->authorizeOAuth($origin)
|
||||
&& (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against '.$origin, 401);
|
||||
}
|
||||
} elseif ($origin === 'bitbucket.org') {
|
||||
$askForOAuthToken = true;
|
||||
if ($this->io->hasAuthentication($origin)) {
|
||||
$auth = $this->io->getAuthentication($origin);
|
||||
if ($auth['username'] !== 'x-token-auth') {
|
||||
$bitbucketUtil = new Bitbucket($this->io, $this->config);
|
||||
$accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']);
|
||||
if (!empty($accessToken)) {
|
||||
$this->io->setAuthentication($origin, 'x-token-auth', $accessToken);
|
||||
$askForOAuthToken = false;
|
||||
}
|
||||
} else {
|
||||
throw new TransportException('Could not authenticate against ' . $origin, 401);
|
||||
}
|
||||
}
|
||||
|
||||
if ($askForOAuthToken) {
|
||||
$message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit');
|
||||
$bitBucketUtil = new Bitbucket($this->io, $this->config);
|
||||
if (! $bitBucketUtil->authorizeOAuth($origin)
|
||||
&& (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against ' . $origin, 401);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 404s are only handled for github
|
||||
if ($statusCode === 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fail if the console is not interactive
|
||||
if (!$this->io->isInteractive()) {
|
||||
if ($statusCode === 401) {
|
||||
$message = "The '" . $url . "' URL required authentication.\nYou must be using the interactive console to authenticate";
|
||||
}
|
||||
if ($statusCode === 403) {
|
||||
$message = "The '" . $url . "' URL could not be accessed: " . $reason;
|
||||
}
|
||||
|
||||
throw new TransportException($message, $statusCode);
|
||||
}
|
||||
// fail if we already have auth
|
||||
if ($this->io->hasAuthentication($origin)) {
|
||||
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
|
||||
}
|
||||
|
||||
$this->io->overwriteError('');
|
||||
if ($warning) {
|
||||
$this->io->writeError(' <warning>'.$warning.'</warning>');
|
||||
}
|
||||
$this->io->writeError(' Authentication required (<info>'.parse_url($url, PHP_URL_HOST).'</info>):');
|
||||
$username = $this->io->ask(' Username: ');
|
||||
$password = $this->io->askAndHideAnswer(' Password: ');
|
||||
$this->io->setAuthentication($origin, $username, $password);
|
||||
$storeAuth = $this->config->get('store-auths');
|
||||
}
|
||||
|
||||
$retry = true;
|
||||
|
||||
return array('retry' => $retry, 'storeAuth' => $storeAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers
|
||||
* @param string $origin
|
||||
* @param string $url
|
||||
* @return array updated headers array
|
||||
*/
|
||||
public function addAuthenticationHeader(array $headers, $origin, $url)
|
||||
{
|
||||
if ($this->io->hasAuthentication($origin)) {
|
||||
$auth = $this->io->getAuthentication($origin);
|
||||
if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
|
||||
$headers[] = 'Authorization: token '.$auth['username'];
|
||||
} elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
|
||||
if ($auth['password'] === 'oauth2') {
|
||||
$headers[] = 'Authorization: Bearer '.$auth['username'];
|
||||
} elseif ($auth['password'] === 'private-token') {
|
||||
$headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
|
||||
}
|
||||
} elseif (
|
||||
'bitbucket.org' === $origin
|
||||
&& $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL
|
||||
&& 'x-token-auth' === $auth['username']
|
||||
) {
|
||||
if (!$this->isPublicBitBucketDownload($url)) {
|
||||
$headers[] = 'Authorization: Bearer ' . $auth['password'];
|
||||
}
|
||||
} else {
|
||||
$authStr = base64_encode($auth['username'] . ':' . $auth['password']);
|
||||
$headers[] = 'Authorization: Basic '.$authStr;
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/composer/composer/issues/5584
|
||||
*
|
||||
* @param string $urlToBitBucketFile URL to a file at bitbucket.org.
|
||||
*
|
||||
* @return bool Whether the given URL is a public BitBucket download which requires no authentication.
|
||||
*/
|
||||
public function isPublicBitBucketDownload($urlToBitBucketFile)
|
||||
{
|
||||
$domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
|
||||
if (strpos($domain, 'bitbucket.org') === false) {
|
||||
// Bitbucket downloads are hosted on amazonaws.
|
||||
// We do not need to authenticate there at all
|
||||
return true;
|
||||
}
|
||||
|
||||
$path = parse_url($urlToBitBucketFile, PHP_URL_PATH);
|
||||
|
||||
// Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
|
||||
// {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
|
||||
$pathParts = explode('/', $path);
|
||||
|
||||
return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class Bitbucket
|
|||
private $io;
|
||||
private $config;
|
||||
private $process;
|
||||
private $remoteFilesystem;
|
||||
private $httpDownloader;
|
||||
private $token = array();
|
||||
private $time;
|
||||
|
||||
|
@ -37,15 +37,15 @@ class Bitbucket
|
|||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The composer configuration
|
||||
* @param ProcessExecutor $process Process instance, injectable for mocking
|
||||
* @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
|
||||
* @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking
|
||||
* @param int $time Timestamp, injectable for mocking
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null)
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null, $time = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ class Bitbucket
|
|||
private function requestAccessToken($originUrl)
|
||||
{
|
||||
try {
|
||||
$json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array(
|
||||
$response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, array(
|
||||
'retry-auth-failure' => false,
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
|
@ -98,7 +98,7 @@ class Bitbucket
|
|||
),
|
||||
));
|
||||
|
||||
$this->token = json_decode($json, true);
|
||||
$this->token = $response->decodeJson();
|
||||
} catch (TransportException $e) {
|
||||
if ($e->getCode() === 400) {
|
||||
$this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
|
||||
|
|
|
@ -25,7 +25,7 @@ class GitHub
|
|||
protected $io;
|
||||
protected $config;
|
||||
protected $process;
|
||||
protected $remoteFilesystem;
|
||||
protected $httpDownloader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -33,14 +33,14 @@ class GitHub
|
|||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The composer configuration
|
||||
* @param ProcessExecutor $process Process instance, injectable for mocking
|
||||
* @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
|
||||
* @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ class GitHub
|
|||
try {
|
||||
$apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/';
|
||||
|
||||
$this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl, false, array(
|
||||
$this->httpDownloader->get('https://'. $apiUrl, array(
|
||||
'retry-auth-failure' => false,
|
||||
));
|
||||
} catch (TransportException $e) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class GitLab
|
|||
protected $io;
|
||||
protected $config;
|
||||
protected $process;
|
||||
protected $remoteFilesystem;
|
||||
protected $httpDownloader;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -34,14 +34,14 @@ class GitLab
|
|||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The composer configuration
|
||||
* @param ProcessExecutor $process Process instance, injectable for mocking
|
||||
* @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
|
||||
* @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,10 +154,10 @@ class GitLab
|
|||
),
|
||||
);
|
||||
|
||||
$json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options);
|
||||
$token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson();
|
||||
|
||||
$this->io->writeError('Token successfully created');
|
||||
|
||||
return JsonFile::parseJson($json);
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,463 @@
|
|||
<?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\Util\Http;
|
||||
|
||||
use Composer\Config;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\CaBundle\CaBundle;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
use Composer\Util\AuthHelper;
|
||||
use Composer\Util\Url;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CurlDownloader
|
||||
{
|
||||
private $multiHandle;
|
||||
private $shareHandle;
|
||||
private $jobs = array();
|
||||
/** @var IOInterface */
|
||||
private $io;
|
||||
/** @var Config */
|
||||
private $config;
|
||||
/** @var AuthHelper */
|
||||
private $authHelper;
|
||||
private $selectTimeout = 5.0;
|
||||
private $maxRedirects = 20;
|
||||
protected $multiErrors = array(
|
||||
CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'),
|
||||
CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."),
|
||||
CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'),
|
||||
CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!')
|
||||
);
|
||||
|
||||
private static $options = array(
|
||||
'http' => array(
|
||||
'method' => CURLOPT_CUSTOMREQUEST,
|
||||
'content' => CURLOPT_POSTFIELDS,
|
||||
'proxy' => CURLOPT_PROXY,
|
||||
'header' => CURLOPT_HTTPHEADER,
|
||||
),
|
||||
'ssl' => array(
|
||||
'ciphers' => CURLOPT_SSL_CIPHER_LIST,
|
||||
'cafile' => CURLOPT_CAINFO,
|
||||
'capath' => CURLOPT_CAPATH,
|
||||
),
|
||||
);
|
||||
|
||||
private static $timeInfo = array(
|
||||
'total_time' => true,
|
||||
'namelookup_time' => true,
|
||||
'connect_time' => true,
|
||||
'pretransfer_time' => true,
|
||||
'starttransfer_time' => true,
|
||||
'redirect_time' => true,
|
||||
);
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
|
||||
$this->multiHandle = $mh = curl_multi_init();
|
||||
if (function_exists('curl_multi_setopt')) {
|
||||
curl_multi_setopt($mh, CURLMOPT_PIPELINING, /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3);
|
||||
if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
|
||||
curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('curl_share_init')) {
|
||||
$this->shareHandle = $sh = curl_share_init();
|
||||
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
||||
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
||||
}
|
||||
|
||||
$this->authHelper = new AuthHelper($io, $config);
|
||||
}
|
||||
|
||||
public function download($resolve, $reject, $origin, $url, $options, $copyTo = null)
|
||||
{
|
||||
$attributes = array();
|
||||
if (isset($options['retry-auth-failure'])) {
|
||||
$attributes['retryAuthFailure'] = $options['retry-auth-failure'];
|
||||
unset($options['retry-auth-failure']);
|
||||
}
|
||||
|
||||
return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes);
|
||||
}
|
||||
|
||||
private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array(
|
||||
'retryAuthFailure' => true,
|
||||
'redirects' => 0,
|
||||
'storeAuth' => false,
|
||||
), $attributes);
|
||||
|
||||
$originalOptions = $options;
|
||||
|
||||
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
||||
if (!preg_match('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) {
|
||||
$this->config->prohibitUrlByConfig($url, $this->io);
|
||||
}
|
||||
|
||||
$curlHandle = curl_init();
|
||||
$headerHandle = fopen('php://temp/maxmemory:32768', 'w+b');
|
||||
|
||||
if ($copyTo) {
|
||||
$errorMessage = '';
|
||||
set_error_handler(function ($code, $msg) use (&$errorMessage) {
|
||||
if ($errorMessage) {
|
||||
$errorMessage .= "\n";
|
||||
}
|
||||
$errorMessage .= preg_replace('{^fopen\(.*?\): }', '', $msg);
|
||||
});
|
||||
$bodyHandle = fopen($copyTo.'~', 'w+b');
|
||||
restore_error_handler();
|
||||
if (!$bodyHandle) {
|
||||
throw new TransportException('The "'.$url.'" file could not be written to '.$copyTo.': '.$errorMessage);
|
||||
}
|
||||
} else {
|
||||
$bodyHandle = @fopen('php://temp/maxmemory:524288', 'w+b');
|
||||
}
|
||||
|
||||
curl_setopt($curlHandle, CURLOPT_URL, $url);
|
||||
curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
|
||||
//curl_setopt($curlHandle, CURLOPT_DNS_USE_GLOBAL_CACHE, false);
|
||||
curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
curl_setopt($curlHandle, CURLOPT_TIMEOUT, 60);
|
||||
curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle);
|
||||
curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle);
|
||||
curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip");
|
||||
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
|
||||
if (defined('CURLOPT_SSL_FALSESTART')) {
|
||||
curl_setopt($curlHandle, CURLOPT_SSL_FALSESTART, true);
|
||||
}
|
||||
if (function_exists('curl_share_init')) {
|
||||
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
|
||||
}
|
||||
|
||||
if (!isset($options['http']['header'])) {
|
||||
$options['http']['header'] = array();
|
||||
}
|
||||
|
||||
$options['http']['header'] = array_diff($options['http']['header'], array('Connection: close'));
|
||||
$options['http']['header'][] = 'Connection: keep-alive';
|
||||
|
||||
$version = curl_version();
|
||||
$features = $version['features'];
|
||||
if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features)) {
|
||||
curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||
}
|
||||
|
||||
$options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url);
|
||||
$options = StreamContextFactory::initOptions($url, $options);
|
||||
|
||||
foreach (self::$options as $type => $curlOptions) {
|
||||
foreach ($curlOptions as $name => $curlOption) {
|
||||
if (isset($options[$type][$name])) {
|
||||
curl_setopt($curlHandle, $curlOption, $options[$type][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
||||
|
||||
$this->jobs[(int) $curlHandle] = array(
|
||||
'url' => $url,
|
||||
'origin' => $origin,
|
||||
'attributes' => $attributes,
|
||||
'options' => $originalOptions,
|
||||
'progress' => $progress,
|
||||
'curlHandle' => $curlHandle,
|
||||
'filename' => $copyTo,
|
||||
'headerHandle' => $headerHandle,
|
||||
'bodyHandle' => $bodyHandle,
|
||||
'resolve' => $resolve,
|
||||
'reject' => $reject,
|
||||
);
|
||||
|
||||
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
|
||||
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
|
||||
if ($attributes['redirects'] === 0) {
|
||||
$this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG);
|
||||
}
|
||||
|
||||
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
|
||||
// TODO progress
|
||||
//$params['notification'](STREAM_NOTIFY_RESOLVE, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false);
|
||||
}
|
||||
|
||||
public function tick()
|
||||
{
|
||||
if (!$this->jobs) {
|
||||
return;
|
||||
}
|
||||
|
||||
$active = true;
|
||||
$this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
|
||||
if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
|
||||
// sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
|
||||
usleep(150);
|
||||
}
|
||||
|
||||
while ($progress = curl_multi_info_read($this->multiHandle)) {
|
||||
$curlHandle = $progress['handle'];
|
||||
$i = (int) $curlHandle;
|
||||
if (!isset($this->jobs[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
||||
$job = $this->jobs[$i];
|
||||
unset($this->jobs[$i]);
|
||||
curl_multi_remove_handle($this->multiHandle, $curlHandle);
|
||||
$error = curl_error($curlHandle);
|
||||
$errno = curl_errno($curlHandle);
|
||||
curl_close($curlHandle);
|
||||
|
||||
$headers = null;
|
||||
$statusCode = null;
|
||||
$response = null;
|
||||
try {
|
||||
// TODO progress
|
||||
//$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']);
|
||||
if (CURLE_OK !== $errno || $error) {
|
||||
throw new TransportException($error);
|
||||
}
|
||||
|
||||
$statusCode = $progress['http_code'];
|
||||
rewind($job['headerHandle']);
|
||||
$headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle'])));
|
||||
fclose($job['headerHandle']);
|
||||
|
||||
// prepare response object
|
||||
if ($job['filename']) {
|
||||
fclose($job['bodyHandle']);
|
||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~');
|
||||
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
||||
} else {
|
||||
rewind($job['bodyHandle']);
|
||||
$contents = stream_get_contents($job['bodyHandle']);
|
||||
fclose($job['bodyHandle']);
|
||||
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
|
||||
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG);
|
||||
}
|
||||
|
||||
$result = $this->isAuthenticatedRetryNeeded($job, $response);
|
||||
if ($result['retry']) {
|
||||
if ($job['filename']) {
|
||||
@unlink($job['filename'].'~');
|
||||
}
|
||||
|
||||
$this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth']));
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle 3xx redirects, 304 Not Modified is excluded
|
||||
if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) {
|
||||
$location = $this->handleRedirect($job, $response);
|
||||
if ($location) {
|
||||
$this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// fail 4xx and 5xx responses and capture the response
|
||||
if ($statusCode >= 400 && $statusCode <= 599) {
|
||||
throw $this->failResponse($job, $response, $response->getStatusMessage());
|
||||
// TODO progress
|
||||
// $this->io->overwriteError("Downloading (<error>failed</error>)", false);
|
||||
}
|
||||
|
||||
if ($job['attributes']['storeAuth']) {
|
||||
$this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
|
||||
}
|
||||
|
||||
// resolve promise
|
||||
if ($job['filename']) {
|
||||
rename($job['filename'].'~', $job['filename']);
|
||||
call_user_func($job['resolve'], $response);
|
||||
} else {
|
||||
call_user_func($job['resolve'], $response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof TransportException && $headers) {
|
||||
$e->setHeaders($headers);
|
||||
$e->setStatusCode($statusCode);
|
||||
}
|
||||
if ($e instanceof TransportException && $response) {
|
||||
$e->setResponse($response->getBody());
|
||||
}
|
||||
|
||||
if (is_resource($job['headerHandle'])) {
|
||||
fclose($job['headerHandle']);
|
||||
}
|
||||
if (is_resource($job['bodyHandle'])) {
|
||||
fclose($job['bodyHandle']);
|
||||
}
|
||||
if ($job['filename']) {
|
||||
@unlink($job['filename'].'~');
|
||||
}
|
||||
call_user_func($job['reject'], $e);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->jobs as $i => $curlHandle) {
|
||||
if (!isset($this->jobs[$i])) {
|
||||
continue;
|
||||
}
|
||||
$curlHandle = $this->jobs[$i]['curlHandle'];
|
||||
$progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);
|
||||
|
||||
if ($this->jobs[$i]['progress'] !== $progress) {
|
||||
$previousProgress = $this->jobs[$i]['progress'];
|
||||
$this->jobs[$i]['progress'] = $progress;
|
||||
|
||||
// TODO
|
||||
//$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleRedirect(array $job, Response $response)
|
||||
{
|
||||
if ($locationHeader = $response->getHeader('location')) {
|
||||
if (parse_url($locationHeader, PHP_URL_SCHEME)) {
|
||||
// Absolute URL; e.g. https://example.com/composer
|
||||
$targetUrl = $locationHeader;
|
||||
} elseif (parse_url($locationHeader, PHP_URL_HOST)) {
|
||||
// Scheme relative; e.g. //example.com/foo
|
||||
$targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader;
|
||||
} elseif ('/' === $locationHeader[0]) {
|
||||
// Absolute path; e.g. /foo
|
||||
$urlHost = parse_url($job['url'], PHP_URL_HOST);
|
||||
|
||||
// Replace path using hostname as an anchor.
|
||||
$targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']);
|
||||
} else {
|
||||
// Relative path; e.g. foo
|
||||
// This actually differs from PHP which seems to add duplicate slashes.
|
||||
$targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($targetUrl)) {
|
||||
$this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG);
|
||||
|
||||
return $targetUrl;
|
||||
}
|
||||
|
||||
throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')');
|
||||
}
|
||||
|
||||
private function isAuthenticatedRetryNeeded(array $job, Response $response)
|
||||
{
|
||||
if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) {
|
||||
$warning = null;
|
||||
if ($response->getHeader('content-type') === 'application/json') {
|
||||
$data = json_decode($response->getBody(), true);
|
||||
if (!empty($data['warning'])) {
|
||||
$warning = $data['warning'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $warning, $response->getHeaders());
|
||||
|
||||
if ($result['retry']) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
$locationHeader = $response->getHeader('location');
|
||||
$needsAuthRetry = false;
|
||||
|
||||
// check for bitbucket login page asking to authenticate
|
||||
if (
|
||||
$job['origin'] === 'bitbucket.org'
|
||||
&& !$this->authHelper->isPublicBitBucketDownload($job['url'])
|
||||
&& substr($job['url'], -4) === '.zip'
|
||||
&& (!$locationHeader || substr($locationHeader, -4) !== '.zip')
|
||||
&& preg_match('{^text/html\b}i', $response->getHeader('content-type'))
|
||||
) {
|
||||
$needsAuthRetry = 'Bitbucket requires authentication and it was not provided';
|
||||
}
|
||||
|
||||
// check for gitlab 404 when downloading archives
|
||||
if (
|
||||
$response->getStatusCode() === 404
|
||||
&& $this->config && in_array($job['origin'], $this->config->get('gitlab-domains'), true)
|
||||
&& false !== strpos($job['url'], 'archive.zip')
|
||||
) {
|
||||
$needsAuthRetry = 'GitLab requires authentication and it was not provided';
|
||||
}
|
||||
|
||||
if ($needsAuthRetry) {
|
||||
if ($job['attributes']['retryAuthFailure']) {
|
||||
$result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401);
|
||||
if ($result['retry']) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
throw $this->failResponse($job, $response, $needsAuthRetry);
|
||||
}
|
||||
|
||||
return array('retry' => false, 'storeAuth' => false);
|
||||
}
|
||||
|
||||
private function restartJob(array $job, $url, array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge($job['attributes'], $attributes);
|
||||
$origin = Url::getOrigin($this->config, $url);
|
||||
|
||||
$this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes);
|
||||
}
|
||||
|
||||
private function failResponse(array $job, Response $response, $errorMessage)
|
||||
{
|
||||
return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')', $response->getStatusCode());
|
||||
}
|
||||
|
||||
private function onProgress($curlHandle, callable $notify, array $progress, array $previousProgress)
|
||||
{
|
||||
// TODO add support for progress
|
||||
if (300 <= $progress['http_code'] && $progress['http_code'] < 400) {
|
||||
return;
|
||||
}
|
||||
if ($previousProgress['download_content_length'] < $progress['download_content_length']) {
|
||||
$notify(STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['download_content_length'], false);
|
||||
}
|
||||
if ($previousProgress['size_download'] < $progress['size_download']) {
|
||||
$notify(STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, (int) $progress['size_download'], (int) $progress['download_content_length'], false);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkCurlResult($code)
|
||||
{
|
||||
if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) {
|
||||
throw new \RuntimeException(isset($this->multiErrors[$code])
|
||||
? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}"
|
||||
: 'Unexpected cURL error: ' . $code
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?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\Util\Http;
|
||||
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
class Response
|
||||
{
|
||||
private $request;
|
||||
private $code;
|
||||
private $headers;
|
||||
private $body;
|
||||
|
||||
public function __construct(array $request, $code, array $headers, $body)
|
||||
{
|
||||
if (!isset($request['url'])) {
|
||||
throw new \LogicException('url key missing from request array');
|
||||
}
|
||||
$this->request = $request;
|
||||
$this->code = (int) $code;
|
||||
$this->headers = $headers;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getStatusMessage()
|
||||
{
|
||||
$value = null;
|
||||
foreach ($this->headers as $header) {
|
||||
if (preg_match('{^HTTP/\S+ \d+}i', $header)) {
|
||||
// In case of redirects, headers contain the headers of all responses
|
||||
// so we can not return directly and need to keep iterating
|
||||
$value = $header;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getHeader($name)
|
||||
{
|
||||
$value = null;
|
||||
foreach ($this->headers as $header) {
|
||||
if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) {
|
||||
$value = $match[1];
|
||||
} elseif (preg_match('{^HTTP/}i', $header)) {
|
||||
// TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary
|
||||
//
|
||||
// In case of redirects, headers contains the headers of all responses
|
||||
// so we reset the flag when a new response is being parsed as we are only interested in the last response
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function decodeJson()
|
||||
{
|
||||
return JsonFile::parseJson($this->body, $this->request['url']);
|
||||
}
|
||||
|
||||
public function collect()
|
||||
{
|
||||
$this->request = $this->code = $this->headers = $this->body = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
<?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\Util;
|
||||
|
||||
use Composer\Config;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\CaBundle\CaBundle;
|
||||
use Composer\Util\Http\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class HttpDownloader
|
||||
{
|
||||
const STATUS_QUEUED = 1;
|
||||
const STATUS_STARTED = 2;
|
||||
const STATUS_COMPLETED = 3;
|
||||
const STATUS_FAILED = 4;
|
||||
|
||||
private $io;
|
||||
private $config;
|
||||
private $jobs = array();
|
||||
private $options = array();
|
||||
private $runningJobs = 0;
|
||||
private $maxJobs = 10;
|
||||
private $lastProgress;
|
||||
private $disableTls = false;
|
||||
private $curl;
|
||||
private $rfs;
|
||||
private $idGen = 0;
|
||||
private $disabled;
|
||||
|
||||
/**
|
||||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The config
|
||||
* @param array $options The options
|
||||
* @param bool $disableTls
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false)
|
||||
{
|
||||
$this->io = $io;
|
||||
|
||||
$this->disabled = (bool) getenv('COMPOSER_DISABLE_NETWORK');
|
||||
|
||||
// Setup TLS options
|
||||
// The cafile option can be set via config.json
|
||||
if ($disableTls === false) {
|
||||
$logger = $io instanceof LoggerInterface ? $io : null;
|
||||
$this->options = StreamContextFactory::getTlsDefaults($options, $logger);
|
||||
} else {
|
||||
$this->disableTls = true;
|
||||
}
|
||||
|
||||
// handle the other externally set options normally.
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
$this->config = $config;
|
||||
|
||||
// TODO enable curl only on 5.6+ if older versions cause any problem
|
||||
if (extension_loaded('curl')) {
|
||||
$this->curl = new Http\CurlDownloader($io, $config, $options, $disableTls);
|
||||
}
|
||||
|
||||
$this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls);
|
||||
}
|
||||
|
||||
public function get($url, $options = array())
|
||||
{
|
||||
list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false), true);
|
||||
$this->wait($job['id']);
|
||||
|
||||
return $this->getResponse($job['id']);
|
||||
}
|
||||
|
||||
public function add($url, $options = array())
|
||||
{
|
||||
list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false));
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
public function copy($url, $to, $options = array())
|
||||
{
|
||||
list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => $to), true);
|
||||
$this->wait($job['id']);
|
||||
|
||||
return $this->getResponse($job['id']);
|
||||
}
|
||||
|
||||
public function addCopy($url, $to, $options = array())
|
||||
{
|
||||
list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => $to));
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the options set in the constructor
|
||||
*
|
||||
* @return array Options
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges new options
|
||||
*
|
||||
* @return array $options
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
}
|
||||
|
||||
private function addJob($request, $sync = false)
|
||||
{
|
||||
$job = array(
|
||||
'id' => $this->idGen++,
|
||||
'status' => self::STATUS_QUEUED,
|
||||
'request' => $request,
|
||||
'sync' => $sync,
|
||||
'origin' => Url::getOrigin($this->config, $request['url']),
|
||||
);
|
||||
|
||||
// capture username/password from URL if there is one
|
||||
if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) {
|
||||
$this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2]));
|
||||
}
|
||||
|
||||
$rfs = $this->rfs;
|
||||
|
||||
if ($this->curl && preg_match('{^https?://}i', $job['request']['url'])) {
|
||||
$resolver = function ($resolve, $reject) use (&$job) {
|
||||
$job['status'] = HttpDownloader::STATUS_QUEUED;
|
||||
$job['resolve'] = $resolve;
|
||||
$job['reject'] = $reject;
|
||||
};
|
||||
} else {
|
||||
$resolver = function ($resolve, $reject) use (&$job, $rfs) {
|
||||
// start job
|
||||
$url = $job['request']['url'];
|
||||
$options = $job['request']['options'];
|
||||
|
||||
$job['status'] = HttpDownloader::STATUS_STARTED;
|
||||
|
||||
if ($job['request']['copyTo']) {
|
||||
$result = $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options);
|
||||
|
||||
$headers = $rfs->getLastHeaders();
|
||||
$response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~');
|
||||
|
||||
$resolve($response);
|
||||
} else {
|
||||
$body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options);
|
||||
$headers = $rfs->getLastHeaders();
|
||||
$response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body);
|
||||
|
||||
$resolve($response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$downloader = $this;
|
||||
$io = $this->io;
|
||||
|
||||
$canceler = function () {};
|
||||
|
||||
$promise = new Promise($resolver, $canceler);
|
||||
$promise->then(function ($response) use (&$job, $downloader) {
|
||||
$job['status'] = HttpDownloader::STATUS_COMPLETED;
|
||||
$job['response'] = $response;
|
||||
|
||||
// TODO 3.0 this should be done directly on $this when PHP 5.3 is dropped
|
||||
$downloader->markJobDone();
|
||||
$downloader->scheduleNextJob();
|
||||
|
||||
return $response;
|
||||
}, function ($e) use ($io, &$job, $downloader) {
|
||||
$job['status'] = HttpDownloader::STATUS_FAILED;
|
||||
$job['exception'] = $e;
|
||||
|
||||
$downloader->markJobDone();
|
||||
$downloader->scheduleNextJob();
|
||||
|
||||
throw $e;
|
||||
});
|
||||
$this->jobs[$job['id']] =& $job;
|
||||
|
||||
if ($this->runningJobs < $this->maxJobs) {
|
||||
$this->startJob($job['id']);
|
||||
}
|
||||
|
||||
return array($job, $promise);
|
||||
}
|
||||
|
||||
private function startJob($id)
|
||||
{
|
||||
$job =& $this->jobs[$id];
|
||||
if ($job['status'] !== self::STATUS_QUEUED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start job
|
||||
$job['status'] = self::STATUS_STARTED;
|
||||
$this->runningJobs++;
|
||||
|
||||
$resolve = $job['resolve'];
|
||||
$reject = $job['reject'];
|
||||
$url = $job['request']['url'];
|
||||
$options = $job['request']['options'];
|
||||
$origin = $job['origin'];
|
||||
|
||||
if ($this->disabled) {
|
||||
if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) {
|
||||
$resolve(new Response(array('url' => $url), 304, array(), ''));
|
||||
} else {
|
||||
$e = new TransportException('Network disabled', 499);
|
||||
$e->setStatusCode(499);
|
||||
$reject($e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($job['request']['copyTo']) {
|
||||
$this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']);
|
||||
} else {
|
||||
$this->curl->download($resolve, $reject, $origin, $url, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function markJobDone()
|
||||
{
|
||||
$this->runningJobs--;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function scheduleNextJob()
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
if ($job['status'] === self::STATUS_QUEUED) {
|
||||
$this->startJob($job['id']);
|
||||
if ($this->runningJobs >= $this->maxJobs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function wait($index = null, $progress = false)
|
||||
{
|
||||
while (true) {
|
||||
if ($this->curl) {
|
||||
$this->curl->tick();
|
||||
}
|
||||
|
||||
if (null !== $index) {
|
||||
if ($this->jobs[$index]['status'] === self::STATUS_COMPLETED || $this->jobs[$index]['status'] === self::STATUS_FAILED) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$done = true;
|
||||
foreach ($this->jobs as $job) {
|
||||
if (!in_array($job['status'], array(self::STATUS_COMPLETED, self::STATUS_FAILED), true)) {
|
||||
$done = false;
|
||||
break;
|
||||
} elseif (!$job['sync']) {
|
||||
unset($this->jobs[$job['id']]);
|
||||
}
|
||||
}
|
||||
if ($done) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
private function getResponse($index)
|
||||
{
|
||||
if (!isset($this->jobs[$index])) {
|
||||
throw new \LogicException('Invalid request id');
|
||||
}
|
||||
|
||||
if ($this->jobs[$index]['status'] === self::STATUS_FAILED) {
|
||||
throw $this->jobs[$index]['exception'];
|
||||
}
|
||||
|
||||
if (!isset($this->jobs[$index]['response'])) {
|
||||
throw new \LogicException('Response not available yet, call wait() first');
|
||||
}
|
||||
|
||||
$resp = $this->jobs[$index]['response'];
|
||||
|
||||
unset($this->jobs[$index]);
|
||||
|
||||
return $resp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?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\Util;
|
||||
|
||||
use Composer\Util\HttpDownloader;
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class Loop
|
||||
{
|
||||
private $io;
|
||||
|
||||
public function __construct(HttpDownloader $httpDownloader)
|
||||
{
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
}
|
||||
|
||||
public function wait(array $promises)
|
||||
{
|
||||
$uncaught = null;
|
||||
|
||||
\React\Promise\all($promises)->then(
|
||||
function () { },
|
||||
function ($e) use (&$uncaught) {
|
||||
$uncaught = $e;
|
||||
}
|
||||
);
|
||||
|
||||
$this->httpDownloader->wait();
|
||||
|
||||
if ($uncaught) {
|
||||
throw $uncaught;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ class RemoteFilesystem
|
|||
private $retryAuthFailure;
|
||||
private $lastHeaders;
|
||||
private $storeAuth;
|
||||
private $authHelper;
|
||||
private $degradedMode = false;
|
||||
private $redirects;
|
||||
private $maxRedirects = 20;
|
||||
|
@ -53,14 +54,15 @@ class RemoteFilesystem
|
|||
* @param array $options The options
|
||||
* @param bool $disableTls
|
||||
*/
|
||||
public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false)
|
||||
public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false)
|
||||
{
|
||||
$this->io = $io;
|
||||
|
||||
// Setup TLS options
|
||||
// The cafile option can be set via config.json
|
||||
if ($disableTls === false) {
|
||||
$this->options = $this->getTlsDefaults($options);
|
||||
$logger = $io instanceof LoggerInterface ? $io : null;
|
||||
$this->options = StreamContextFactory::getTlsDefaults($options, $logger);
|
||||
} else {
|
||||
$this->disableTls = true;
|
||||
}
|
||||
|
@ -68,6 +70,7 @@ class RemoteFilesystem
|
|||
// handle the other externally set options normally.
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
$this->config = $config;
|
||||
$this->authHelper = new AuthHelper($io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,7 +149,7 @@ class RemoteFilesystem
|
|||
* @param string $name header name (case insensitive)
|
||||
* @return string|null
|
||||
*/
|
||||
public function findHeaderValue(array $headers, $name)
|
||||
public static function findHeaderValue(array $headers, $name)
|
||||
{
|
||||
$value = null;
|
||||
foreach ($headers as $header) {
|
||||
|
@ -166,7 +169,7 @@ class RemoteFilesystem
|
|||
* @param array $headers array of returned headers like from getLastHeaders()
|
||||
* @return int|null
|
||||
*/
|
||||
public function findStatusCode(array $headers)
|
||||
public static function findStatusCode(array $headers)
|
||||
{
|
||||
$value = null;
|
||||
foreach ($headers as $header) {
|
||||
|
@ -214,27 +217,6 @@ class RemoteFilesystem
|
|||
*/
|
||||
protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
|
||||
{
|
||||
if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
|
||||
$originUrl = 'github.com';
|
||||
}
|
||||
|
||||
// Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
|
||||
// is the host without the path, so we look for the registered gitlab-domains matching the host here
|
||||
if (
|
||||
$this->config
|
||||
&& is_array($this->config->get('gitlab-domains'))
|
||||
&& false === strpos($originUrl, '/')
|
||||
&& !in_array($originUrl, $this->config->get('gitlab-domains'))
|
||||
) {
|
||||
foreach ($this->config->get('gitlab-domains') as $gitlabDomain) {
|
||||
if (0 === strpos($gitlabDomain, $originUrl)) {
|
||||
$originUrl = $gitlabDomain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($gitlabDomain);
|
||||
}
|
||||
|
||||
$this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
|
||||
$this->bytesMax = 0;
|
||||
$this->originUrl = $originUrl;
|
||||
|
@ -246,11 +228,6 @@ class RemoteFilesystem
|
|||
$this->lastHeaders = array();
|
||||
$this->redirects = 1; // The first request counts.
|
||||
|
||||
// capture username/password from URL if there is one
|
||||
if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) {
|
||||
$this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2]));
|
||||
}
|
||||
|
||||
$tempAdditionalOptions = $additionalOptions;
|
||||
if (isset($tempAdditionalOptions['retry-auth-failure'])) {
|
||||
$this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];
|
||||
|
@ -271,14 +248,6 @@ class RemoteFilesystem
|
|||
|
||||
$origFileUrl = $fileUrl;
|
||||
|
||||
if (isset($options['github-token'])) {
|
||||
// only add the access_token if it is actually a github URL (in case we were redirected to S3)
|
||||
if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) {
|
||||
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
|
||||
}
|
||||
unset($options['github-token']);
|
||||
}
|
||||
|
||||
if (isset($options['gitlab-token'])) {
|
||||
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
|
||||
unset($options['gitlab-token']);
|
||||
|
@ -399,7 +368,7 @@ class RemoteFilesystem
|
|||
|
||||
// check for bitbucket login page asking to authenticate
|
||||
if ($originUrl === 'bitbucket.org'
|
||||
&& !$this->isPublicBitBucketDownload($fileUrl)
|
||||
&& !$this->authHelper->isPublicBitBucketDownload($fileUrl)
|
||||
&& substr($fileUrl, -4) === '.zip'
|
||||
&& (!$locationHeader || substr($locationHeader, -4) !== '.zip')
|
||||
&& $contentType && preg_match('{^text/html\b}i', $contentType)
|
||||
|
@ -543,8 +512,7 @@ class RemoteFilesystem
|
|||
$result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
|
||||
|
||||
if ($this->storeAuth && $this->config) {
|
||||
$authHelper = new AuthHelper($this->io, $this->config);
|
||||
$authHelper->storeAuth($this->originUrl, $this->storeAuth);
|
||||
$this->authHelper->storeAuth($this->originUrl, $this->storeAuth);
|
||||
$this->storeAuth = false;
|
||||
}
|
||||
|
||||
|
@ -649,111 +617,14 @@ class RemoteFilesystem
|
|||
|
||||
protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array())
|
||||
{
|
||||
if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, null);
|
||||
$message = "\n";
|
||||
$result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $warning, $headers);
|
||||
|
||||
$rateLimited = $gitHubUtil->isRateLimited($headers);
|
||||
if ($rateLimited) {
|
||||
$rateLimit = $gitHubUtil->getRateLimit($headers);
|
||||
if ($this->io->hasAuthentication($this->originUrl)) {
|
||||
$message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
|
||||
} else {
|
||||
$message = 'Create a GitHub OAuth token to go over the API rate limit.';
|
||||
}
|
||||
$this->storeAuth = $result['storeAuth'];
|
||||
$this->retry = $result['retry'];
|
||||
|
||||
$message = sprintf(
|
||||
'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.',
|
||||
$rateLimit['limit'],
|
||||
$rateLimit['reset']
|
||||
)."\n";
|
||||
} else {
|
||||
$message .= 'Could not fetch '.$this->fileUrl.', please ';
|
||||
if ($this->io->hasAuthentication($this->originUrl)) {
|
||||
$message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
|
||||
} else {
|
||||
$message .= 'create a GitHub OAuth token to access private repos';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gitHubUtil->authorizeOAuth($this->originUrl)
|
||||
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
|
||||
}
|
||||
} elseif ($this->config && in_array($this->originUrl, $this->config->get('gitlab-domains'), true)) {
|
||||
$message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit');
|
||||
$gitLabUtil = new GitLab($this->io, $this->config, null);
|
||||
|
||||
if ($this->io->hasAuthentication($this->originUrl) && ($auth = $this->io->getAuthentication($this->originUrl)) && $auth['password'] === 'private-token') {
|
||||
throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
|
||||
}
|
||||
|
||||
if (!$gitLabUtil->authorizeOAuth($this->originUrl)
|
||||
&& (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
|
||||
}
|
||||
} elseif ($this->config && $this->originUrl === 'bitbucket.org') {
|
||||
$askForOAuthToken = true;
|
||||
if ($this->io->hasAuthentication($this->originUrl)) {
|
||||
$auth = $this->io->getAuthentication($this->originUrl);
|
||||
if ($auth['username'] !== 'x-token-auth') {
|
||||
$bitbucketUtil = new Bitbucket($this->io, $this->config);
|
||||
$accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']);
|
||||
if (!empty($accessToken)) {
|
||||
$this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken);
|
||||
$askForOAuthToken = false;
|
||||
}
|
||||
} else {
|
||||
throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
|
||||
}
|
||||
}
|
||||
|
||||
if ($askForOAuthToken) {
|
||||
$message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit');
|
||||
$bitBucketUtil = new Bitbucket($this->io, $this->config);
|
||||
if (! $bitBucketUtil->authorizeOAuth($this->originUrl)
|
||||
&& (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message))
|
||||
) {
|
||||
throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 404s are only handled for github
|
||||
if ($httpStatus === 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fail if the console is not interactive
|
||||
if (!$this->io->isInteractive()) {
|
||||
if ($httpStatus === 401) {
|
||||
$message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console to authenticate";
|
||||
}
|
||||
if ($httpStatus === 403) {
|
||||
$message = "The '" . $this->fileUrl . "' URL could not be accessed: " . $reason;
|
||||
}
|
||||
|
||||
throw new TransportException($message, $httpStatus);
|
||||
}
|
||||
// fail if we already have auth
|
||||
if ($this->io->hasAuthentication($this->originUrl)) {
|
||||
throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
|
||||
}
|
||||
|
||||
$this->io->overwriteError('');
|
||||
if ($warning) {
|
||||
$this->io->writeError(' <warning>'.$warning.'</warning>');
|
||||
}
|
||||
$this->io->writeError(' Authentication required (<info>'.parse_url($this->fileUrl, PHP_URL_HOST).'</info>):');
|
||||
$username = $this->io->ask(' Username: ');
|
||||
$password = $this->io->askAndHideAnswer(' Password: ');
|
||||
$this->io->setAuthentication($this->originUrl, $username, $password);
|
||||
$this->storeAuth = $this->config->get('store-auths');
|
||||
if ($this->retry) {
|
||||
throw new TransportException('RETRY');
|
||||
}
|
||||
|
||||
$this->retry = true;
|
||||
throw new TransportException('RETRY');
|
||||
}
|
||||
|
||||
protected function getOptionsForUrl($originUrl, $additionalOptions)
|
||||
|
@ -813,27 +684,7 @@ class RemoteFilesystem
|
|||
$headers[] = 'Connection: close';
|
||||
}
|
||||
|
||||
if ($this->io->hasAuthentication($originUrl)) {
|
||||
$auth = $this->io->getAuthentication($originUrl);
|
||||
if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
|
||||
$options['github-token'] = $auth['username'];
|
||||
} elseif ($this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)) {
|
||||
if ($auth['password'] === 'oauth2') {
|
||||
$headers[] = 'Authorization: Bearer '.$auth['username'];
|
||||
} elseif ($auth['password'] === 'private-token') {
|
||||
$headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
|
||||
}
|
||||
} elseif ('bitbucket.org' === $originUrl
|
||||
&& $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username']
|
||||
) {
|
||||
if (!$this->isPublicBitBucketDownload($this->fileUrl)) {
|
||||
$headers[] = 'Authorization: Bearer ' . $auth['password'];
|
||||
}
|
||||
} else {
|
||||
$authStr = base64_encode($auth['username'] . ':' . $auth['password']);
|
||||
$headers[] = 'Authorization: Basic '.$authStr;
|
||||
}
|
||||
}
|
||||
$headers = $this->authHelper->addAuthenticationHeader($headers, $originUrl, $this->fileUrl);
|
||||
|
||||
$options['http']['follow_location'] = 0;
|
||||
|
||||
|
@ -891,111 +742,6 @@ class RemoteFilesystem
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTlsDefaults(array $options)
|
||||
{
|
||||
$ciphers = implode(':', array(
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'DHE-RSA-AES128-GCM-SHA256',
|
||||
'DHE-DSS-AES128-GCM-SHA256',
|
||||
'kEDH+AESGCM',
|
||||
'ECDHE-RSA-AES128-SHA256',
|
||||
'ECDHE-ECDSA-AES128-SHA256',
|
||||
'ECDHE-RSA-AES128-SHA',
|
||||
'ECDHE-ECDSA-AES128-SHA',
|
||||
'ECDHE-RSA-AES256-SHA384',
|
||||
'ECDHE-ECDSA-AES256-SHA384',
|
||||
'ECDHE-RSA-AES256-SHA',
|
||||
'ECDHE-ECDSA-AES256-SHA',
|
||||
'DHE-RSA-AES128-SHA256',
|
||||
'DHE-RSA-AES128-SHA',
|
||||
'DHE-DSS-AES128-SHA256',
|
||||
'DHE-RSA-AES256-SHA256',
|
||||
'DHE-DSS-AES256-SHA',
|
||||
'DHE-RSA-AES256-SHA',
|
||||
'AES128-GCM-SHA256',
|
||||
'AES256-GCM-SHA384',
|
||||
'AES128-SHA256',
|
||||
'AES256-SHA256',
|
||||
'AES128-SHA',
|
||||
'AES256-SHA',
|
||||
'AES',
|
||||
'CAMELLIA',
|
||||
'DES-CBC3-SHA',
|
||||
'!aNULL',
|
||||
'!eNULL',
|
||||
'!EXPORT',
|
||||
'!DES',
|
||||
'!RC4',
|
||||
'!MD5',
|
||||
'!PSK',
|
||||
'!aECDH',
|
||||
'!EDH-DSS-DES-CBC3-SHA',
|
||||
'!EDH-RSA-DES-CBC3-SHA',
|
||||
'!KRB5-DES-CBC3-SHA',
|
||||
));
|
||||
|
||||
/**
|
||||
* CN_match and SNI_server_name are only known once a URL is passed.
|
||||
* They will be set in the getOptionsForUrl() method which receives a URL.
|
||||
*
|
||||
* cafile or capath can be overridden by passing in those options to constructor.
|
||||
*/
|
||||
$defaults = array(
|
||||
'ssl' => array(
|
||||
'ciphers' => $ciphers,
|
||||
'verify_peer' => true,
|
||||
'verify_depth' => 7,
|
||||
'SNI_enabled' => true,
|
||||
'capture_peer_cert' => true,
|
||||
),
|
||||
);
|
||||
|
||||
if (isset($options['ssl'])) {
|
||||
$defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
|
||||
}
|
||||
|
||||
$caBundleLogger = $this->io instanceof LoggerInterface ? $this->io : null;
|
||||
|
||||
/**
|
||||
* Attempt to find a local cafile or throw an exception if none pre-set
|
||||
* The user may go download one if this occurs.
|
||||
*/
|
||||
if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
|
||||
$result = CaBundle::getSystemCaRootBundlePath($caBundleLogger);
|
||||
|
||||
if (is_dir($result)) {
|
||||
$defaults['ssl']['capath'] = $result;
|
||||
} else {
|
||||
$defaults['ssl']['cafile'] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $caBundleLogger))) {
|
||||
throw new TransportException('The configured cafile was not valid or could not be read.');
|
||||
}
|
||||
|
||||
if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) {
|
||||
throw new TransportException('The configured capath was not valid or could not be read.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable TLS compression to prevent CRIME attacks where supported.
|
||||
*/
|
||||
if (PHP_VERSION_ID >= 50413) {
|
||||
$defaults['ssl']['disable_compression'] = true;
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch certificate common name and fingerprint for validation of SAN.
|
||||
*
|
||||
|
@ -1065,29 +811,4 @@ class RemoteFilesystem
|
|||
|
||||
return parse_url($url, PHP_URL_HOST).':'.$port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/composer/composer/issues/5584
|
||||
*
|
||||
* @param string $urlToBitBucketFile URL to a file at bitbucket.org.
|
||||
*
|
||||
* @return bool Whether the given URL is a public BitBucket download which requires no authentication.
|
||||
*/
|
||||
private function isPublicBitBucketDownload($urlToBitBucketFile)
|
||||
{
|
||||
$domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
|
||||
if (strpos($domain, 'bitbucket.org') === false) {
|
||||
// Bitbucket downloads are hosted on amazonaws.
|
||||
// We do not need to authenticate there at all
|
||||
return true;
|
||||
}
|
||||
|
||||
$path = parse_url($urlToBitBucketFile, PHP_URL_PATH);
|
||||
|
||||
// Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
|
||||
// {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
|
||||
$pathParts = explode('/', $path);
|
||||
|
||||
return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
namespace Composer\Util;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\CaBundle\CaBundle;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Allows the creation of a basic context supporting http proxy
|
||||
|
@ -39,6 +41,32 @@ final class StreamContextFactory
|
|||
'max_redirects' => 20,
|
||||
));
|
||||
|
||||
$options = array_replace_recursive($options, self::initOptions($url, $defaultOptions));
|
||||
unset($defaultOptions['http']['header']);
|
||||
$options = array_replace_recursive($options, $defaultOptions);
|
||||
|
||||
if (isset($options['http']['header'])) {
|
||||
$options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
|
||||
}
|
||||
|
||||
return stream_context_create($options, $defaultParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @return array ['http' => ['header' => [...], 'proxy' => '..', 'request_fulluri' => bool]] formatted as a stream context array
|
||||
*/
|
||||
public static function initOptions($url, array $options)
|
||||
{
|
||||
// Make sure the headers are in an array form
|
||||
if (!isset($options['http']['header'])) {
|
||||
$options['http']['header'] = array();
|
||||
}
|
||||
if (is_string($options['http']['header'])) {
|
||||
$options['http']['header'] = explode("\r\n", $options['http']['header']);
|
||||
}
|
||||
|
||||
// Handle HTTP_PROXY/http_proxy on CLI only for security reasons
|
||||
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
|
||||
$proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
|
||||
|
@ -85,15 +113,15 @@ final class StreamContextFactory
|
|||
|
||||
// enabled request_fulluri unless it is explicitly disabled
|
||||
switch (parse_url($url, PHP_URL_SCHEME)) {
|
||||
case 'http': // default request_fulluri to true
|
||||
case 'http': // default request_fulluri to true for HTTP
|
||||
$reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
|
||||
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
|
||||
$options['http']['request_fulluri'] = true;
|
||||
}
|
||||
break;
|
||||
case 'https': // default request_fulluri to true
|
||||
case 'https': // default request_fulluri to false for HTTPS
|
||||
$reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
|
||||
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
|
||||
if (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv) {
|
||||
$options['http']['request_fulluri'] = true;
|
||||
}
|
||||
break;
|
||||
|
@ -115,42 +143,139 @@ final class StreamContextFactory
|
|||
}
|
||||
$auth = base64_encode($auth);
|
||||
|
||||
// Preserve headers if already set in default options
|
||||
if (isset($defaultOptions['http']['header'])) {
|
||||
if (is_string($defaultOptions['http']['header'])) {
|
||||
$defaultOptions['http']['header'] = array($defaultOptions['http']['header']);
|
||||
}
|
||||
$defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
|
||||
} else {
|
||||
$options['http']['header'] = array("Proxy-Authorization: Basic {$auth}");
|
||||
}
|
||||
$options['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
|
||||
}
|
||||
}
|
||||
|
||||
$options = array_replace_recursive($options, $defaultOptions);
|
||||
|
||||
if (isset($options['http']['header'])) {
|
||||
$options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
|
||||
}
|
||||
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$phpVersion = 'HHVM ' . HHVM_VERSION;
|
||||
} else {
|
||||
$phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
|
||||
}
|
||||
|
||||
if (extension_loaded('curl')) {
|
||||
$curl = curl_version();
|
||||
$httpVersion = 'curl '.$curl['version'];
|
||||
} else {
|
||||
$httpVersion = 'streams';
|
||||
}
|
||||
|
||||
if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) {
|
||||
$options['http']['header'][] = sprintf(
|
||||
'User-Agent: Composer/%s (%s; %s; %s%s)',
|
||||
Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION,
|
||||
'User-Agent: Composer/%s (%s; %s; %s; %s%s)',
|
||||
Composer::VERSION === '@package_version@' ? Composer::SOURCE_VERSION : Composer::VERSION,
|
||||
function_exists('php_uname') ? php_uname('s') : 'Unknown',
|
||||
function_exists('php_uname') ? php_uname('r') : 'Unknown',
|
||||
$phpVersion,
|
||||
$httpVersion,
|
||||
getenv('CI') ? '; CI' : ''
|
||||
);
|
||||
}
|
||||
|
||||
return stream_context_create($options, $defaultParams);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getTlsDefaults(array $options, LoggerInterface $logger = null)
|
||||
{
|
||||
$ciphers = implode(':', array(
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'DHE-RSA-AES128-GCM-SHA256',
|
||||
'DHE-DSS-AES128-GCM-SHA256',
|
||||
'kEDH+AESGCM',
|
||||
'ECDHE-RSA-AES128-SHA256',
|
||||
'ECDHE-ECDSA-AES128-SHA256',
|
||||
'ECDHE-RSA-AES128-SHA',
|
||||
'ECDHE-ECDSA-AES128-SHA',
|
||||
'ECDHE-RSA-AES256-SHA384',
|
||||
'ECDHE-ECDSA-AES256-SHA384',
|
||||
'ECDHE-RSA-AES256-SHA',
|
||||
'ECDHE-ECDSA-AES256-SHA',
|
||||
'DHE-RSA-AES128-SHA256',
|
||||
'DHE-RSA-AES128-SHA',
|
||||
'DHE-DSS-AES128-SHA256',
|
||||
'DHE-RSA-AES256-SHA256',
|
||||
'DHE-DSS-AES256-SHA',
|
||||
'DHE-RSA-AES256-SHA',
|
||||
'AES128-GCM-SHA256',
|
||||
'AES256-GCM-SHA384',
|
||||
'AES128-SHA256',
|
||||
'AES256-SHA256',
|
||||
'AES128-SHA',
|
||||
'AES256-SHA',
|
||||
'AES',
|
||||
'CAMELLIA',
|
||||
'DES-CBC3-SHA',
|
||||
'!aNULL',
|
||||
'!eNULL',
|
||||
'!EXPORT',
|
||||
'!DES',
|
||||
'!RC4',
|
||||
'!MD5',
|
||||
'!PSK',
|
||||
'!aECDH',
|
||||
'!EDH-DSS-DES-CBC3-SHA',
|
||||
'!EDH-RSA-DES-CBC3-SHA',
|
||||
'!KRB5-DES-CBC3-SHA',
|
||||
));
|
||||
|
||||
/**
|
||||
* CN_match and SNI_server_name are only known once a URL is passed.
|
||||
* They will be set in the getOptionsForUrl() method which receives a URL.
|
||||
*
|
||||
* cafile or capath can be overridden by passing in those options to constructor.
|
||||
*/
|
||||
$defaults = array(
|
||||
'ssl' => array(
|
||||
'ciphers' => $ciphers,
|
||||
'verify_peer' => true,
|
||||
'verify_depth' => 7,
|
||||
'SNI_enabled' => true,
|
||||
'capture_peer_cert' => true,
|
||||
),
|
||||
);
|
||||
|
||||
if (isset($options['ssl'])) {
|
||||
$defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find a local cafile or throw an exception if none pre-set
|
||||
* The user may go download one if this occurs.
|
||||
*/
|
||||
if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
|
||||
$result = CaBundle::getSystemCaRootBundlePath($logger);
|
||||
|
||||
if (is_dir($result)) {
|
||||
$defaults['ssl']['capath'] = $result;
|
||||
} else {
|
||||
$defaults['ssl']['cafile'] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) {
|
||||
throw new TransportException('The configured cafile was not valid or could not be read.');
|
||||
}
|
||||
|
||||
if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) {
|
||||
throw new TransportException('The configured capath was not valid or could not be read.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable TLS compression to prevent CRIME attacks where supported.
|
||||
*/
|
||||
if (PHP_VERSION_ID >= 50413) {
|
||||
$defaults['ssl']['disable_compression'] = true;
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,12 @@ use Composer\Config;
|
|||
*/
|
||||
class Url
|
||||
{
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param string $url
|
||||
* @param string $ref
|
||||
* @return string the updated URL
|
||||
*/
|
||||
public static function updateDistReference(Config $config, $url, $ref)
|
||||
{
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
|
@ -52,4 +58,45 @@ class Url
|
|||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public static function getOrigin(Config $config, $url)
|
||||
{
|
||||
if (0 === strpos($url, 'file://')) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$origin = (string) parse_url($url, PHP_URL_HOST);
|
||||
|
||||
if (strpos($origin, '.github.com') === (strlen($origin) - 11)) {
|
||||
return 'github.com';
|
||||
}
|
||||
|
||||
if ($origin === 'repo.packagist.org') {
|
||||
return 'packagist.org';
|
||||
}
|
||||
|
||||
if ($origin === '') {
|
||||
$origin = $url;
|
||||
}
|
||||
|
||||
// Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
|
||||
// is the host without the path, so we look for the registered gitlab-domains matching the host here
|
||||
if (
|
||||
is_array($config->get('gitlab-domains'))
|
||||
&& false === strpos($origin, '/')
|
||||
&& !in_array($origin, $config->get('gitlab-domains'))
|
||||
) {
|
||||
foreach ($config->get('gitlab-domains') as $gitlabDomain) {
|
||||
if (0 === strpos($gitlabDomain, $origin)) {
|
||||
return $gitlabDomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $origin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class ComposerTest extends TestCase
|
|||
public function testSetGetInstallationManager()
|
||||
{
|
||||
$composer = new Composer();
|
||||
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock();
|
||||
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
|
||||
$composer->setInstallationManager($manager);
|
||||
|
||||
$this->assertSame($manager, $composer->getInstallationManager());
|
||||
|
|
|
@ -29,7 +29,7 @@ class ArchiveDownloaderTest extends TestCase
|
|||
$method->setAccessible(true);
|
||||
|
||||
$first = $method->invoke($downloader, $packageMock, '/path');
|
||||
$this->assertRegExp('#/path/[a-z0-9]+\.js#', $first);
|
||||
$this->assertRegExp('#/path_[a-z0-9]+\.js#', $first);
|
||||
$this->assertSame($first, $method->invoke($downloader, $packageMock, '/path'));
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,11 @@ class ArchiveDownloaderTest extends TestCase
|
|||
{
|
||||
return $this->getMockForAbstractClass(
|
||||
'Composer\Downloader\ArchiveDownloader',
|
||||
array($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getMockBuilder('Composer\Config')->getMock())
|
||||
array(
|
||||
$io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
|
||||
$config = $this->getMockBuilder('Composer\Config')->getMock(),
|
||||
new \Composer\Util\HttpDownloader($io, $config),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$this->setExpectedException('InvalidArgumentException');
|
||||
|
||||
$manager->getDownloaderForInstalledPackage($package);
|
||||
$manager->getDownloaderForPackage($package);
|
||||
}
|
||||
|
||||
public function testGetDownloaderForCorrectlyInstalledDistPackage()
|
||||
|
@ -82,7 +82,7 @@ class DownloadManagerTest extends TestCase
|
|||
->with('pear')
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
$this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package));
|
||||
$this->assertSame($downloader, $manager->getDownloaderForPackage($package));
|
||||
}
|
||||
|
||||
public function testGetDownloaderForIncorrectlyInstalledDistPackage()
|
||||
|
@ -116,7 +116,7 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
$manager->getDownloaderForInstalledPackage($package);
|
||||
$manager->getDownloaderForPackage($package);
|
||||
}
|
||||
|
||||
public function testGetDownloaderForCorrectlyInstalledSourcePackage()
|
||||
|
@ -148,7 +148,7 @@ class DownloadManagerTest extends TestCase
|
|||
->with('git')
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
$this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package));
|
||||
$this->assertSame($downloader, $manager->getDownloaderForPackage($package));
|
||||
}
|
||||
|
||||
public function testGetDownloaderForIncorrectlyInstalledSourcePackage()
|
||||
|
@ -182,7 +182,7 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
$manager->getDownloaderForInstalledPackage($package);
|
||||
$manager->getDownloaderForPackage($package);
|
||||
}
|
||||
|
||||
public function testGetDownloaderForMetapackage()
|
||||
|
@ -195,7 +195,7 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = new DownloadManager($this->io, false, $this->filesystem);
|
||||
|
||||
$this->assertNull($manager->getDownloaderForInstalledPackage($package));
|
||||
$this->assertNull($manager->getDownloaderForPackage($package));
|
||||
}
|
||||
|
||||
public function testFullPackageDownload()
|
||||
|
@ -223,11 +223,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -274,16 +274,16 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->at(0))
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloaderFail));
|
||||
$manager
|
||||
->expects($this->at(1))
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloaderSuccess));
|
||||
|
||||
|
@ -333,11 +333,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -369,11 +369,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -399,11 +399,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue(null)); // There is no downloader for Metapackages.
|
||||
|
||||
|
@ -435,11 +435,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -472,11 +472,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -509,11 +509,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -550,33 +550,30 @@ class DownloadManagerTest extends TestCase
|
|||
$initial
|
||||
->expects($this->once())
|
||||
->method('getDistType')
|
||||
->will($this->returnValue('pear'));
|
||||
->will($this->returnValue('zip'));
|
||||
|
||||
$target = $this->createPackageMock();
|
||||
$target
|
||||
->expects($this->once())
|
||||
->method('getDistType')
|
||||
->will($this->returnValue('pear'));
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('dist'));
|
||||
$target
|
||||
->expects($this->once())
|
||||
->method('setInstallationSource')
|
||||
->with('dist');
|
||||
->method('getDistType')
|
||||
->will($this->returnValue('zip'));
|
||||
|
||||
$pearDownloader = $this->createDownloaderMock();
|
||||
$pearDownloader
|
||||
$zipDownloader = $this->createDownloaderMock();
|
||||
$zipDownloader
|
||||
->expects($this->once())
|
||||
->method('update')
|
||||
->with($initial, $target, 'vendor/bundles/FOS/UserBundle');
|
||||
$zipDownloader
|
||||
->expects($this->any())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('dist'));
|
||||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->with($initial)
|
||||
->will($this->returnValue($pearDownloader));
|
||||
$manager = new DownloadManager($this->io, false, $this->filesystem);
|
||||
$manager->setDownloader('zip', $zipDownloader);
|
||||
|
||||
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
|
||||
}
|
||||
|
@ -591,113 +588,89 @@ class DownloadManagerTest extends TestCase
|
|||
$initial
|
||||
->expects($this->once())
|
||||
->method('getDistType')
|
||||
->will($this->returnValue('pear'));
|
||||
->will($this->returnValue('xz'));
|
||||
|
||||
$target = $this->createPackageMock();
|
||||
$target
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('dist'));
|
||||
$target
|
||||
->expects($this->any())
|
||||
->method('getDistType')
|
||||
->will($this->returnValue('composer'));
|
||||
->will($this->returnValue('zip'));
|
||||
|
||||
$pearDownloader = $this->createDownloaderMock();
|
||||
$pearDownloader
|
||||
$xzDownloader = $this->createDownloaderMock();
|
||||
$xzDownloader
|
||||
->expects($this->once())
|
||||
->method('remove')
|
||||
->with($initial, 'vendor/bundles/FOS/UserBundle');
|
||||
$xzDownloader
|
||||
->expects($this->any())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('dist'));
|
||||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
|
||||
->getMock();
|
||||
$manager
|
||||
$zipDownloader = $this->createDownloaderMock();
|
||||
$zipDownloader
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->with($initial)
|
||||
->will($this->returnValue($pearDownloader));
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('download')
|
||||
->with($target, 'vendor/bundles/FOS/UserBundle', false);
|
||||
->method('install')
|
||||
->with($target, 'vendor/bundles/FOS/UserBundle');
|
||||
$zipDownloader
|
||||
->expects($this->any())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('dist'));
|
||||
|
||||
$manager = new DownloadManager($this->io, false, $this->filesystem);
|
||||
$manager->setDownloader('xz', $xzDownloader);
|
||||
$manager->setDownloader('zip', $zipDownloader);
|
||||
|
||||
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
|
||||
}
|
||||
|
||||
public function testUpdateSourceWithEqualTypes()
|
||||
/**
|
||||
* @dataProvider updatesProvider
|
||||
*/
|
||||
public function testGetAvailableSourcesUpdateSticksToSameSource($prevPkgSource, $prevPkgIsDev, $targetAvailable, $targetIsDev, $expected)
|
||||
{
|
||||
$initial = $this->createPackageMock();
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('source'));
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->method('getSourceType')
|
||||
->will($this->returnValue('svn'));
|
||||
$initial = null;
|
||||
if ($prevPkgSource) {
|
||||
$initial = $this->prophesize('Composer\Package\PackageInterface');
|
||||
$initial->getInstallationSource()->willReturn($prevPkgSource);
|
||||
$initial->isDev()->willReturn($prevPkgIsDev);
|
||||
}
|
||||
|
||||
$target = $this->createPackageMock();
|
||||
$target
|
||||
->expects($this->once())
|
||||
->method('getSourceType')
|
||||
->will($this->returnValue('svn'));
|
||||
$target = $this->prophesize('Composer\Package\PackageInterface');
|
||||
$target->getSourceType()->willReturn(in_array('source', $targetAvailable, true) ? 'git' : null);
|
||||
$target->getDistType()->willReturn(in_array('dist', $targetAvailable, true) ? 'zip' : null);
|
||||
$target->isDev()->willReturn($targetIsDev);
|
||||
|
||||
$svnDownloader = $this->createDownloaderMock();
|
||||
$svnDownloader
|
||||
->expects($this->once())
|
||||
->method('update')
|
||||
->with($initial, $target, 'vendor/pkg');
|
||||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->with($initial)
|
||||
->will($this->returnValue($svnDownloader));
|
||||
|
||||
$manager->update($initial, $target, 'vendor/pkg');
|
||||
$manager = new DownloadManager($this->io, false, $this->filesystem);
|
||||
$method = new \ReflectionMethod($manager, 'getAvailableSources');
|
||||
$method->setAccessible(true);
|
||||
$this->assertEquals($expected, $method->invoke($manager, $target->reveal(), $initial ? $initial->reveal() : null));
|
||||
}
|
||||
|
||||
public function testUpdateSourceWithNotEqualTypes()
|
||||
public static function updatesProvider()
|
||||
{
|
||||
$initial = $this->createPackageMock();
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->method('getInstallationSource')
|
||||
->will($this->returnValue('source'));
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->method('getSourceType')
|
||||
->will($this->returnValue('svn'));
|
||||
|
||||
$target = $this->createPackageMock();
|
||||
$target
|
||||
->expects($this->once())
|
||||
->method('getSourceType')
|
||||
->will($this->returnValue('git'));
|
||||
|
||||
$svnDownloader = $this->createDownloaderMock();
|
||||
$svnDownloader
|
||||
->expects($this->once())
|
||||
->method('remove')
|
||||
->with($initial, 'vendor/pkg');
|
||||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->with($initial)
|
||||
->will($this->returnValue($svnDownloader));
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('download')
|
||||
->with($target, 'vendor/pkg', true);
|
||||
|
||||
$manager->update($initial, $target, 'vendor/pkg');
|
||||
return array(
|
||||
// prevPkg source, prevPkg isDev, pkg available, pkg isDev, expected
|
||||
// updates keep previous source as preference
|
||||
array('source', false, array('source', 'dist'), false, array('source', 'dist')),
|
||||
array('dist', false, array('source', 'dist'), false, array('dist', 'source')),
|
||||
// updates do not keep previous source if target package does not have it
|
||||
array('source', false, array('dist'), false, array('dist')),
|
||||
array('dist', false, array('source'), false, array('source')),
|
||||
// updates do not keep previous source if target is dev and prev wasn't dev and installed from dist
|
||||
array('source', false, array('source', 'dist'), true, array('source', 'dist')),
|
||||
array('dist', false, array('source', 'dist'), true, array('source', 'dist')),
|
||||
// install picks the right default
|
||||
array(null, null, array('source', 'dist'), true, array('source', 'dist')),
|
||||
array(null, null, array('dist'), true, array('dist')),
|
||||
array(null, null, array('source'), true, array('source')),
|
||||
array(null, null, array('source', 'dist'), false, array('dist', 'source')),
|
||||
array(null, null, array('dist'), false, array('dist')),
|
||||
array(null, null, array('source'), false, array('source')),
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpdateMetapackage()
|
||||
|
@ -707,11 +680,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->expects($this->exactly(2))
|
||||
->method('getDownloaderForPackage')
|
||||
->with($initial)
|
||||
->will($this->returnValue(null)); // There is no downloader for metapackages.
|
||||
|
||||
|
@ -730,11 +703,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($pearDownloader));
|
||||
|
||||
|
@ -747,11 +720,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue(null)); // There is no downloader for metapackages.
|
||||
|
||||
|
@ -790,11 +763,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -833,11 +806,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
|
||||
|
@ -879,11 +852,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'source'));
|
||||
|
@ -926,11 +899,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'source'));
|
||||
|
@ -973,11 +946,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'auto'));
|
||||
|
@ -1020,11 +993,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'auto'));
|
||||
|
@ -1063,11 +1036,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'source'));
|
||||
|
@ -1106,11 +1079,11 @@ class DownloadManagerTest extends TestCase
|
|||
|
||||
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
|
||||
->setConstructorArgs(array($this->io, false, $this->filesystem))
|
||||
->setMethods(array('getDownloaderForInstalledPackage'))
|
||||
->setMethods(array('getDownloaderForPackage'))
|
||||
->getMock();
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('getDownloaderForInstalledPackage')
|
||||
->method('getDownloaderForPackage')
|
||||
->with($package)
|
||||
->will($this->returnValue($downloader));
|
||||
$manager->setPreferences(array('foo/*' => 'dist'));
|
||||
|
|
|
@ -15,16 +15,23 @@ namespace Composer\Test\Downloader;
|
|||
use Composer\Downloader\FileDownloader;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Http\Response;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
class FileDownloaderTest extends TestCase
|
||||
{
|
||||
protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $rfs = null, $filesystem = null)
|
||||
protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $httpDownloader = null, $filesystem = null)
|
||||
{
|
||||
$io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
|
||||
$config = $config ?: $this->getMockBuilder('Composer\Config')->getMock();
|
||||
$rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock();
|
||||
$httpDownloader = $httpDownloader ?: $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock();
|
||||
$httpDownloader
|
||||
->expects($this->any())
|
||||
->method('addCopy')
|
||||
->will($this->returnValue(\React\Promise\resolve(new Response(array('url' => 'http://example.org/'), 200, array(), 'file~'))));
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
|
||||
return new FileDownloader($io, $config, $eventDispatcher, $cache, $rfs, $filesystem);
|
||||
return new FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $filesystem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,7 +91,7 @@ class FileDownloaderTest extends TestCase
|
|||
$method = new \ReflectionMethod($downloader, 'getFileName');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertEquals('/path/script.js', $method->invoke($downloader, $packageMock, '/path'));
|
||||
$this->assertEquals('/path_script.js', $method->invoke($downloader, $packageMock, '/path'));
|
||||
}
|
||||
|
||||
public function testDownloadButFileIsUnsaved()
|
||||
|
@ -118,8 +125,11 @@ class FileDownloaderTest extends TestCase
|
|||
|
||||
$downloader = $this->getDownloader($ioMock);
|
||||
try {
|
||||
$downloader->download($packageMock, $path);
|
||||
$this->fail();
|
||||
$promise = $downloader->download($packageMock, $path);
|
||||
$loop = new Loop($this->httpDownloader);
|
||||
$loop->wait(array($promise));
|
||||
|
||||
$this->fail('Download was expected to throw');
|
||||
} catch (\Exception $e) {
|
||||
if (is_dir($path)) {
|
||||
$fs = new Filesystem();
|
||||
|
@ -128,7 +138,7 @@ class FileDownloaderTest extends TestCase
|
|||
unlink($path);
|
||||
}
|
||||
|
||||
$this->assertInstanceOf('UnexpectedValueException', $e);
|
||||
$this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage());
|
||||
$this->assertContains('could not be saved to', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -188,11 +198,14 @@ class FileDownloaderTest extends TestCase
|
|||
$path = $this->getUniqueTmpDirectory();
|
||||
$downloader = $this->getDownloader(null, null, null, null, null, $filesystem);
|
||||
// make sure the file expected to be downloaded is on disk already
|
||||
touch($path.'/script.js');
|
||||
touch($path.'_script.js');
|
||||
|
||||
try {
|
||||
$downloader->download($packageMock, $path);
|
||||
$this->fail();
|
||||
$promise = $downloader->download($packageMock, $path);
|
||||
$loop = new Loop($this->httpDownloader);
|
||||
$loop->wait(array($promise));
|
||||
|
||||
$this->fail('Download was expected to throw');
|
||||
} catch (\Exception $e) {
|
||||
if (is_dir($path)) {
|
||||
$fs = new Filesystem();
|
||||
|
@ -201,7 +214,7 @@ class FileDownloaderTest extends TestCase
|
|||
unlink($path);
|
||||
}
|
||||
|
||||
$this->assertInstanceOf('UnexpectedValueException', $e);
|
||||
$this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage());
|
||||
$this->assertContains('checksum verification', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -232,17 +245,25 @@ class FileDownloaderTest extends TestCase
|
|||
|
||||
$ioMock = $this->getMock('Composer\IO\IOInterface');
|
||||
$ioMock->expects($this->at(0))
|
||||
->method('writeError')
|
||||
->with($this->stringContains('Downloading'));
|
||||
|
||||
$ioMock->expects($this->at(1))
|
||||
->method('writeError')
|
||||
->with($this->stringContains('Downgrading'));
|
||||
|
||||
$path = $this->getUniqueTmpDirectory();
|
||||
touch($path.'/script.js');
|
||||
touch($path.'_script.js');
|
||||
$filesystem = $this->getMock('Composer\Util\Filesystem');
|
||||
$filesystem->expects($this->once())
|
||||
->method('removeDirectory')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem);
|
||||
$promise = $downloader->download($newPackage, $path, $oldPackage);
|
||||
$loop = new Loop($this->httpDownloader);
|
||||
$loop->wait(array($promise));
|
||||
|
||||
$downloader->update($oldPackage, $newPackage, $path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class FossilDownloaderTest extends TestCase
|
|||
->will($this->returnValue(null));
|
||||
|
||||
$downloader = $this->getDownloaderMock();
|
||||
$downloader->download($packageMock, '/path');
|
||||
$downloader->install($packageMock, '/path');
|
||||
}
|
||||
|
||||
public function testDownload()
|
||||
|
@ -89,7 +89,7 @@ class FossilDownloaderTest extends TestCase
|
|||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
|
||||
$downloader->download($packageMock, 'repo');
|
||||
$downloader->install($packageMock, 'repo');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,7 +79,7 @@ class GitDownloaderTest extends TestCase
|
|||
->will($this->returnValue(null));
|
||||
|
||||
$downloader = $this->getDownloaderMock();
|
||||
$downloader->download($packageMock, '/path');
|
||||
$downloader->install($packageMock, '/path');
|
||||
}
|
||||
|
||||
public function testDownload()
|
||||
|
@ -130,7 +130,7 @@ class GitDownloaderTest extends TestCase
|
|||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
}
|
||||
|
||||
public function testDownloadWithCache()
|
||||
|
@ -195,7 +195,7 @@ class GitDownloaderTest extends TestCase
|
|||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, $config, $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
@rmdir($cachePath);
|
||||
}
|
||||
|
||||
|
@ -265,7 +265,7 @@ class GitDownloaderTest extends TestCase
|
|||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
}
|
||||
|
||||
public function pushUrlProvider()
|
||||
|
@ -329,7 +329,7 @@ class GitDownloaderTest extends TestCase
|
|||
$config->merge(array('config' => array('github-protocols' => $protocols)));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, $config, $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,7 +360,7 @@ class GitDownloaderTest extends TestCase
|
|||
->will($this->returnValue(1));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,7 +56,7 @@ class HgDownloaderTest extends TestCase
|
|||
->will($this->returnValue(null));
|
||||
|
||||
$downloader = $this->getDownloaderMock();
|
||||
$downloader->download($packageMock, '/path');
|
||||
$downloader->install($packageMock, '/path');
|
||||
}
|
||||
|
||||
public function testDownload()
|
||||
|
@ -83,7 +83,7 @@ class HgDownloaderTest extends TestCase
|
|||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
|
||||
$downloader->download($packageMock, 'composerPath');
|
||||
$downloader->install($packageMock, 'composerPath');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@ use Composer\Config;
|
|||
use Composer\Repository\VcsRepository;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Factory;
|
||||
use Composer\Util\Filesystem;
|
||||
|
||||
/**
|
||||
|
@ -96,7 +97,7 @@ class PerforceDownloaderTest extends TestCase
|
|||
{
|
||||
$repository = $this->getMockBuilder('Composer\Repository\VcsRepository')
|
||||
->setMethods(array('getRepoConfig'))
|
||||
->setConstructorArgs(array($repoConfig, $io, $config))
|
||||
->setConstructorArgs(array($repoConfig, $io, $config, Factory::createHttpDownloader($io, $config)))
|
||||
->getMock();
|
||||
$repository->expects($this->any())->method('getRepoConfig')->will($this->returnValue($repoConfig));
|
||||
|
||||
|
@ -137,7 +138,7 @@ class PerforceDownloaderTest extends TestCase
|
|||
$perforce->expects($this->at(5))->method('syncCodeBase')->with($label);
|
||||
$perforce->expects($this->at(6))->method('cleanupClientSpec');
|
||||
$this->downloader->setPerforce($perforce);
|
||||
$this->downloader->doDownload($this->package, $this->testPath, 'url');
|
||||
$this->downloader->doInstall($this->package, $this->testPath, 'url');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,6 +161,6 @@ class PerforceDownloaderTest extends TestCase
|
|||
$perforce->expects($this->at(5))->method('syncCodeBase')->with($label);
|
||||
$perforce->expects($this->at(6))->method('cleanupClientSpec');
|
||||
$this->downloader->setPerforce($perforce);
|
||||
$this->downloader->doDownload($this->package, $this->testPath, 'url');
|
||||
$this->downloader->doInstall($this->package, $this->testPath, 'url');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ use Composer\Downloader\XzDownloader;
|
|||
use Composer\Test\TestCase;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Util\HttpDownloader;
|
||||
|
||||
class XzDownloaderTest extends TestCase
|
||||
{
|
||||
|
@ -66,10 +67,14 @@ class XzDownloaderTest extends TestCase
|
|||
->method('get')
|
||||
->with('vendor-dir')
|
||||
->will($this->returnValue($this->testDir));
|
||||
$downloader = new XzDownloader($io, $config, null, null, null, new RemoteFilesystem($io));
|
||||
$downloader = new XzDownloader($io, $config, $httpDownloader = new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null);
|
||||
|
||||
try {
|
||||
$downloader->download($packageMock, $this->getUniqueTmpDirectory());
|
||||
$promise = $downloader->download($packageMock, $this->testDir);
|
||||
$loop = new Loop($httpDownloader);
|
||||
$loop->wait(array($promise));
|
||||
$downloader->install($packageMock, $this->testDir);
|
||||
|
||||
$this->fail('Download of invalid tarball should throw an exception');
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->assertRegexp('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage());
|
||||
|
|
|
@ -16,6 +16,8 @@ use Composer\Downloader\ZipDownloader;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
class ZipDownloaderTest extends TestCase
|
||||
{
|
||||
|
@ -26,12 +28,16 @@ class ZipDownloaderTest extends TestCase
|
|||
private $prophet;
|
||||
private $io;
|
||||
private $config;
|
||||
private $package;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->testDir = $this->getUniqueTmpDirectory();
|
||||
$this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
|
||||
$this->config = $this->getMockBuilder('Composer\Config')->getMock();
|
||||
$dlConfig = $this->getMockBuilder('Composer\Config')->getMock();
|
||||
$this->httpDownloader = new HttpDownloader($this->io, $dlConfig);
|
||||
$this->package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
@ -64,42 +70,33 @@ class ZipDownloaderTest extends TestCase
|
|||
}
|
||||
|
||||
$this->config->expects($this->at(0))
|
||||
->method('get')
|
||||
->with('disable-tls')
|
||||
->will($this->returnValue(false));
|
||||
$this->config->expects($this->at(1))
|
||||
->method('get')
|
||||
->with('cafile')
|
||||
->will($this->returnValue(null));
|
||||
$this->config->expects($this->at(2))
|
||||
->method('get')
|
||||
->with('capath')
|
||||
->will($this->returnValue(null));
|
||||
$this->config->expects($this->at(3))
|
||||
->method('get')
|
||||
->with('vendor-dir')
|
||||
->will($this->returnValue($this->testDir));
|
||||
|
||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||
$packageMock->expects($this->any())
|
||||
$this->package->expects($this->any())
|
||||
->method('getDistUrl')
|
||||
->will($this->returnValue($distUrl = 'file://'.__FILE__))
|
||||
;
|
||||
$packageMock->expects($this->any())
|
||||
$this->package->expects($this->any())
|
||||
->method('getDistUrls')
|
||||
->will($this->returnValue(array($distUrl)))
|
||||
;
|
||||
$packageMock->expects($this->atLeastOnce())
|
||||
$this->package->expects($this->atLeastOnce())
|
||||
->method('getTransportOptions')
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$downloader = new ZipDownloader($this->io, $this->config);
|
||||
$downloader = new ZipDownloader($this->io, $this->config, $this->httpDownloader);
|
||||
|
||||
$this->setPrivateProperty('hasSystemUnzip', false);
|
||||
|
||||
try {
|
||||
$downloader->download($packageMock, sys_get_temp_dir().'/composer-zip-test');
|
||||
$promise = $downloader->download($this->package, $path = sys_get_temp_dir().'/composer-zip-test');
|
||||
$loop = new Loop($this->httpDownloader);
|
||||
$loop->wait(array($promise));
|
||||
$downloader->install($this->package, $path);
|
||||
|
||||
$this->fail('Download of invalid zip files should throw an exception');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertContains('is not a zip archive', $e->getMessage());
|
||||
|
@ -118,8 +115,7 @@ class ZipDownloaderTest extends TestCase
|
|||
|
||||
$this->setPrivateProperty('hasSystemUnzip', false);
|
||||
$this->setPrivateProperty('hasZipArchive', true);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config);
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader);
|
||||
$zipArchive = $this->getMockBuilder('ZipArchive')->getMock();
|
||||
$zipArchive->expects($this->at(0))
|
||||
->method('open')
|
||||
|
@ -129,7 +125,7 @@ class ZipDownloaderTest extends TestCase
|
|||
->will($this->returnValue(false));
|
||||
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,8 +140,7 @@ class ZipDownloaderTest extends TestCase
|
|||
|
||||
$this->setPrivateProperty('hasSystemUnzip', false);
|
||||
$this->setPrivateProperty('hasZipArchive', true);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config);
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader);
|
||||
$zipArchive = $this->getMockBuilder('ZipArchive')->getMock();
|
||||
$zipArchive->expects($this->at(0))
|
||||
->method('open')
|
||||
|
@ -155,7 +150,7 @@ class ZipDownloaderTest extends TestCase
|
|||
->will($this->throwException(new \ErrorException('Not a directory')));
|
||||
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,8 +164,7 @@ class ZipDownloaderTest extends TestCase
|
|||
|
||||
$this->setPrivateProperty('hasSystemUnzip', false);
|
||||
$this->setPrivateProperty('hasZipArchive', true);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config);
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader);
|
||||
$zipArchive = $this->getMockBuilder('ZipArchive')->getMock();
|
||||
$zipArchive->expects($this->at(0))
|
||||
->method('open')
|
||||
|
@ -180,7 +174,7 @@ class ZipDownloaderTest extends TestCase
|
|||
->will($this->returnValue(true));
|
||||
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,8 +194,8 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('execute')
|
||||
->will($this->returnValue(1));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
public function testSystemUnzipOnlyGood()
|
||||
|
@ -217,8 +211,8 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('execute')
|
||||
->will($this->returnValue(0));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
public function testNonWindowsFallbackGood()
|
||||
|
@ -244,9 +238,9 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('extractTo')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,9 +270,9 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('extractTo')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
public function testWindowsFallbackGood()
|
||||
|
@ -304,9 +298,9 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('extractTo')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -336,9 +330,9 @@ class ZipDownloaderTest extends TestCase
|
|||
->method('extractTo')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor);
|
||||
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
|
||||
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
|
||||
$downloader->extract('testfile.zip', 'vendor/dir');
|
||||
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,8 +343,13 @@ class MockedZipDownloader extends ZipDownloader
|
|||
return;
|
||||
}
|
||||
|
||||
public function extract($file, $path)
|
||||
public function install(PackageInterface $package, $path, $output = true)
|
||||
{
|
||||
parent::extract($file, $path);
|
||||
return;
|
||||
}
|
||||
|
||||
public function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
parent::extract($package, $file, $path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class EventDispatcherTest extends TestCase
|
|||
$composer->setPackage($package);
|
||||
|
||||
$composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
|
||||
$composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock());
|
||||
$composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock());
|
||||
|
||||
$dispatcher = new EventDispatcher(
|
||||
$composer,
|
||||
|
|
|
@ -35,6 +35,6 @@ class FactoryTest extends TestCase
|
|||
->with($this->equalTo('disable-tls'))
|
||||
->will($this->returnValue(true));
|
||||
|
||||
Factory::createRemoteFilesystem($ioMock, $config);
|
||||
Factory::createHttpDownloader($ioMock, $config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Test\Installer;
|
||||
|
||||
use Composer\Installer\InstallationManager;
|
||||
use Composer\Installer\NoopInstaller;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
|
@ -21,9 +22,11 @@ use PHPUnit\Framework\TestCase;
|
|||
class InstallationManagerTest extends TestCase
|
||||
{
|
||||
protected $repository;
|
||||
protected $loop;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = $this->getMockBuilder('Composer\Util\Loop')->disableOriginalConstructor()->getMock();
|
||||
$this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock();
|
||||
}
|
||||
|
||||
|
@ -38,7 +41,7 @@ class InstallationManagerTest extends TestCase
|
|||
return $arg === 'vendor';
|
||||
}));
|
||||
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
|
||||
$manager->addInstaller($installer);
|
||||
$this->assertSame($installer, $manager->getInstaller('vendor'));
|
||||
|
@ -67,7 +70,7 @@ class InstallationManagerTest extends TestCase
|
|||
return $arg === 'vendor';
|
||||
}));
|
||||
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
|
||||
$manager->addInstaller($installer);
|
||||
$this->assertSame($installer, $manager->getInstaller('vendor'));
|
||||
|
@ -80,16 +83,21 @@ class InstallationManagerTest extends TestCase
|
|||
public function testExecute()
|
||||
{
|
||||
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')
|
||||
->setConstructorArgs(array($this->loop))
|
||||
->setMethods(array('install', 'update', 'uninstall'))
|
||||
->getMock();
|
||||
|
||||
$installOperation = new InstallOperation($this->createPackageMock());
|
||||
$removeOperation = new UninstallOperation($this->createPackageMock());
|
||||
$installOperation = new InstallOperation($package = $this->createPackageMock());
|
||||
$removeOperation = new UninstallOperation($package);
|
||||
$updateOperation = new UpdateOperation(
|
||||
$this->createPackageMock(),
|
||||
$this->createPackageMock()
|
||||
$package,
|
||||
$package
|
||||
);
|
||||
|
||||
$package->expects($this->any())
|
||||
->method('getType')
|
||||
->will($this->returnValue('library'));
|
||||
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('install')
|
||||
|
@ -103,6 +111,7 @@ class InstallationManagerTest extends TestCase
|
|||
->method('update')
|
||||
->with($this->repository, $updateOperation);
|
||||
|
||||
$manager->addInstaller(new NoopInstaller());
|
||||
$manager->execute($this->repository, $installOperation);
|
||||
$manager->execute($this->repository, $removeOperation);
|
||||
$manager->execute($this->repository, $updateOperation);
|
||||
|
@ -111,7 +120,7 @@ class InstallationManagerTest extends TestCase
|
|||
public function testInstall()
|
||||
{
|
||||
$installer = $this->createInstallerMock();
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
$manager->addInstaller($installer);
|
||||
|
||||
$package = $this->createPackageMock();
|
||||
|
@ -139,7 +148,7 @@ class InstallationManagerTest extends TestCase
|
|||
public function testUpdateWithEqualTypes()
|
||||
{
|
||||
$installer = $this->createInstallerMock();
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
$manager->addInstaller($installer);
|
||||
|
||||
$initial = $this->createPackageMock();
|
||||
|
@ -173,18 +182,17 @@ class InstallationManagerTest extends TestCase
|
|||
{
|
||||
$libInstaller = $this->createInstallerMock();
|
||||
$bundleInstaller = $this->createInstallerMock();
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
$manager->addInstaller($libInstaller);
|
||||
$manager->addInstaller($bundleInstaller);
|
||||
|
||||
$initial = $this->createPackageMock();
|
||||
$target = $this->createPackageMock();
|
||||
$operation = new UpdateOperation($initial, $target, 'test');
|
||||
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->method('getType')
|
||||
->will($this->returnValue('library'));
|
||||
|
||||
$target = $this->createPackageMock();
|
||||
$target
|
||||
->expects($this->once())
|
||||
->method('getType')
|
||||
|
@ -213,13 +221,14 @@ class InstallationManagerTest extends TestCase
|
|||
->method('install')
|
||||
->with($this->repository, $target);
|
||||
|
||||
$operation = new UpdateOperation($initial, $target, 'test');
|
||||
$manager->update($this->repository, $operation);
|
||||
}
|
||||
|
||||
public function testUninstall()
|
||||
{
|
||||
$installer = $this->createInstallerMock();
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
$manager->addInstaller($installer);
|
||||
|
||||
$package = $this->createPackageMock();
|
||||
|
@ -249,7 +258,7 @@ class InstallationManagerTest extends TestCase
|
|||
$installer = $this->getMockBuilder('Composer\Installer\LibraryInstaller')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$manager = new InstallationManager();
|
||||
$manager = new InstallationManager($this->loop);
|
||||
$manager->addInstaller($installer);
|
||||
|
||||
$package = $this->createPackageMock();
|
||||
|
@ -281,7 +290,9 @@ class InstallationManagerTest extends TestCase
|
|||
|
||||
private function createPackageMock()
|
||||
{
|
||||
return $this->getMockBuilder('Composer\Package\PackageInterface')
|
||||
$mock = $this->getMockBuilder('Composer\Package\PackageInterface')
|
||||
->getMock();
|
||||
|
||||
return $mock;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ class LibraryInstallerTest extends TestCase
|
|||
|
||||
$this->dm
|
||||
->expects($this->once())
|
||||
->method('download')
|
||||
->method('install')
|
||||
->with($package, $this->vendorDir.'/some/package');
|
||||
|
||||
$this->repository
|
||||
|
|
|
@ -63,7 +63,9 @@ class InstallerTest extends TestCase
|
|||
->getMock();
|
||||
$config = $this->getMockBuilder('Composer\Config')->getMock();
|
||||
|
||||
$repositoryManager = new RepositoryManager($io, $config);
|
||||
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
|
||||
$httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock();
|
||||
$repositoryManager = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher);
|
||||
$repositoryManager->setLocalRepository(new InstalledArrayRepository());
|
||||
|
||||
if (!is_array($repositories)) {
|
||||
|
@ -76,7 +78,6 @@ class InstallerTest extends TestCase
|
|||
$locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
|
||||
$installationManager = new InstallationManagerMock();
|
||||
|
||||
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
|
||||
$autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock();
|
||||
|
||||
$installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Repository\WritableRepositoryInterface;
|
|||
use Composer\Installer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
class FactoryMock extends Factory
|
||||
{
|
||||
|
@ -39,9 +40,9 @@ class FactoryMock extends Factory
|
|||
{
|
||||
}
|
||||
|
||||
protected function createInstallationManager()
|
||||
public function createInstallationManager(Loop $loop)
|
||||
{
|
||||
return new InstallationManagerMock;
|
||||
return new InstallationManagerMock();
|
||||
}
|
||||
|
||||
protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)
|
||||
|
|
|
@ -12,13 +12,11 @@
|
|||
|
||||
namespace Composer\Test\Mock;
|
||||
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Http\Response;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* Remote filesystem mock
|
||||
*/
|
||||
class RemoteFilesystemMock extends RemoteFilesystem
|
||||
class HttpDownloaderMock extends HttpDownloader
|
||||
{
|
||||
protected $contentMap;
|
||||
|
||||
|
@ -30,10 +28,10 @@ class RemoteFilesystemMock extends RemoteFilesystem
|
|||
$this->contentMap = $contentMap;
|
||||
}
|
||||
|
||||
public function getContents($originUrl, $fileUrl, $progress = true, $options = array())
|
||||
public function get($fileUrl, $options = array())
|
||||
{
|
||||
if (!empty($this->contentMap[$fileUrl])) {
|
||||
return $this->contentMap[$fileUrl];
|
||||
return new Response(array('url' => $fileUrl), 200, array(), $this->contentMap[$fileUrl]);
|
||||
}
|
||||
|
||||
throw new TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404);
|
|
@ -17,6 +17,7 @@ use Composer\Repository\RepositoryInterface;
|
|||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
|
||||
|
@ -29,6 +30,18 @@ class InstallationManagerMock extends InstallationManager
|
|||
private $uninstalled = array();
|
||||
private $trace = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function execute(RepositoryInterface $repo, OperationInterface $operation)
|
||||
{
|
||||
$method = $operation->getJobType();
|
||||
// skipping download() step here for tests
|
||||
$this->$method($repo, $operation);
|
||||
}
|
||||
|
||||
public function getInstallPath(PackageInterface $package)
|
||||
{
|
||||
return '';
|
||||
|
|
|
@ -12,9 +12,12 @@
|
|||
|
||||
namespace Composer\Test\Package\Archiver;
|
||||
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Factory;
|
||||
use Composer\Package\Archiver\ArchiveManager;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Test\Mock\FactoryMock;
|
||||
|
||||
class ArchiveManagerTest extends ArchiverTest
|
||||
{
|
||||
|
@ -30,7 +33,13 @@ class ArchiveManagerTest extends ArchiverTest
|
|||
parent::setUp();
|
||||
|
||||
$factory = new Factory();
|
||||
$this->manager = $factory->createArchiveManager($factory->createConfig());
|
||||
$dm = $factory->createDownloadManager(
|
||||
$io = new NullIO,
|
||||
$config = FactoryMock::createConfig(),
|
||||
$httpDownloader = $factory->createHttpDownloader($io, $config)
|
||||
);
|
||||
$loop = new Loop($httpDownloader);
|
||||
$this->manager = $factory->createArchiveManager($factory->createConfig(), $dm, $loop);
|
||||
$this->targetDir = $this->testDir.'/composer_archiver_tests';
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ class PluginInstallerTest extends TestCase
|
|||
->method('getLocalRepository')
|
||||
->will($this->returnValue($this->repository));
|
||||
|
||||
$im = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock();
|
||||
$im = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
|
||||
$im->expects($this->any())
|
||||
->method('getInstallPath')
|
||||
->will($this->returnCallback(function ($package) {
|
||||
|
|
|
@ -18,7 +18,7 @@ use Composer\Repository\RepositoryInterface;
|
|||
use Composer\Test\Mock\FactoryMock;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
||||
class ComposerRepositoryTest extends TestCase
|
||||
{
|
||||
|
@ -32,11 +32,13 @@ class ComposerRepositoryTest extends TestCase
|
|||
);
|
||||
|
||||
$repository = $this->getMockBuilder('Composer\Repository\ComposerRepository')
|
||||
->setMethods(array('loadRootServerFile', 'createPackage'))
|
||||
->setMethods(array('loadRootServerFile', 'createPackages'))
|
||||
->setConstructorArgs(array(
|
||||
$repoConfig,
|
||||
new NullIO,
|
||||
FactoryMock::createConfig(),
|
||||
$this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(),
|
||||
$this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock()
|
||||
))
|
||||
->getMock();
|
||||
|
||||
|
@ -45,16 +47,17 @@ class ComposerRepositoryTest extends TestCase
|
|||
->method('loadRootServerFile')
|
||||
->will($this->returnValue($repoPackages));
|
||||
|
||||
$stubs = array();
|
||||
foreach ($expected as $at => $arg) {
|
||||
$stubPackage = $this->getPackage('stub/stub', '1.0.0');
|
||||
|
||||
$repository
|
||||
->expects($this->at($at + 2))
|
||||
->method('createPackage')
|
||||
->with($this->identicalTo($arg), $this->equalTo('Composer\Package\CompletePackage'))
|
||||
->will($this->returnValue($stubPackage));
|
||||
$stubs[] = $this->getPackage('stub/stub', '1.0.0');
|
||||
}
|
||||
|
||||
$repository
|
||||
->expects($this->at(2))
|
||||
->method('createPackages')
|
||||
->with($this->identicalTo($expected), $this->equalTo('Composer\Package\CompletePackage'))
|
||||
->will($this->returnValue($stubs));
|
||||
|
||||
// Triggers initialization
|
||||
$packages = $repository->getPackages();
|
||||
|
||||
|
@ -143,19 +146,12 @@ class ComposerRepositoryTest extends TestCase
|
|||
)));
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
$repo->setRootAliases(array(
|
||||
'a' => array(
|
||||
$versionParser->normalize('0.6') => array('alias' => 'dev-feature', 'alias_normalized' => $versionParser->normalize('dev-feature')),
|
||||
$versionParser->normalize('1.1.x-dev') => array('alias' => '1.0', 'alias_normalized' => $versionParser->normalize('1.0')),
|
||||
),
|
||||
));
|
||||
$reflMethod = new \ReflectionMethod($repo, 'whatProvides');
|
||||
$reflMethod->setAccessible(true);
|
||||
$packages = $reflMethod->invoke($repo, 'a', array($this, 'isPackageAcceptableReturnTrue'));
|
||||
|
||||
$packages = $repo->whatProvides('a', false, array($this, 'isPackageAcceptableReturnTrue'));
|
||||
|
||||
$this->assertCount(7, $packages);
|
||||
$this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages));
|
||||
$this->assertInstanceOf('Composer\Package\AliasPackage', $packages['2-root']);
|
||||
$this->assertSame($packages['2'], $packages['2-root']->getAliasOf());
|
||||
$this->assertCount(5, $packages);
|
||||
$this->assertEquals(array('1', '1-alias', '2', '2-alias', '3'), array_keys($packages));
|
||||
$this->assertSame($packages['2'], $packages['2-alias']->getAliasOf());
|
||||
}
|
||||
|
||||
|
@ -179,21 +175,29 @@ class ComposerRepositoryTest extends TestCase
|
|||
),
|
||||
);
|
||||
|
||||
$rfs = $this->getMockBuilder('Composer\Util\RemoteFilesystem')
|
||||
$httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$rfs->expects($this->at(0))
|
||||
->method('getContents')
|
||||
->with('example.org', 'http://example.org/packages.json', false)
|
||||
->willReturn(json_encode(array('search' => '/search.json?q=%query%&type=%type%')));
|
||||
$httpDownloader->expects($this->at(0))
|
||||
->method('get')
|
||||
->with($url = 'http://example.org/packages.json')
|
||||
->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode(array('search' => '/search.json?q=%query%&type=%type%'))));
|
||||
|
||||
$rfs->expects($this->at(1))
|
||||
->method('getContents')
|
||||
->with('example.org', 'http://example.org/search.json?q=foo&type=composer-plugin', false)
|
||||
->willReturn(json_encode($result));
|
||||
$httpDownloader->expects($this->at(1))
|
||||
->method('get')
|
||||
->with($url = 'http://example.org/search.json?q=foo&type=composer-plugin')
|
||||
->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode($result)));
|
||||
|
||||
$repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), null, $rfs);
|
||||
$httpDownloader->expects($this->at(2))
|
||||
->method('get')
|
||||
->with($url = 'http://example.org/search.json?q=foo&type=library')
|
||||
->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode(array())));
|
||||
|
||||
$repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), $httpDownloader, $eventDispatcher);
|
||||
|
||||
$this->assertSame(
|
||||
array(array('name' => 'foo', 'description' => null)),
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace Composer\Test\Repository;
|
|||
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Repository\PathRepository;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
||||
class PathRepositoryTest extends TestCase
|
||||
{
|
||||
|
|
|
@ -22,19 +22,19 @@ use Composer\Semver\VersionParser;
|
|||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\CompletePackage;
|
||||
use Composer\Test\Mock\RemoteFilesystemMock;
|
||||
use Composer\Test\Mock\HttpDownloaderMock;
|
||||
|
||||
class ChannelReaderTest extends TestCase
|
||||
{
|
||||
public function testShouldBuildPackagesFromPearSchema()
|
||||
{
|
||||
$rfs = new RemoteFilesystemMock(array(
|
||||
$httpDownloader = new HttpDownloaderMock(array(
|
||||
'http://pear.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'),
|
||||
'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'),
|
||||
'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'),
|
||||
));
|
||||
|
||||
$reader = new \Composer\Repository\Pear\ChannelReader($rfs);
|
||||
$reader = new \Composer\Repository\Pear\ChannelReader($httpDownloader);
|
||||
|
||||
$channelInfo = $reader->read('http://pear.net/');
|
||||
$packages = $channelInfo->getPackages();
|
||||
|
@ -50,17 +50,21 @@ class ChannelReaderTest extends TestCase
|
|||
|
||||
public function testShouldSelectCorrectReader()
|
||||
{
|
||||
$rfs = new RemoteFilesystemMock(array(
|
||||
$httpDownloader = new HttpDownloaderMock(array(
|
||||
'http://pear.1.0.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.0.xml'),
|
||||
'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'),
|
||||
'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'),
|
||||
'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'),
|
||||
'http://test.loc/rest10/r/http_client/deps.1.2.1.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_deps.1.2.1.txt'),
|
||||
'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'),
|
||||
'http://test.loc/rest10/r/http_request/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_allreleases.xml'),
|
||||
'http://test.loc/rest10/r/http_request/deps.1.4.0.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_deps.1.4.0.txt'),
|
||||
'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'),
|
||||
'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'),
|
||||
'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'),
|
||||
));
|
||||
|
||||
$reader = new \Composer\Repository\Pear\ChannelReader($rfs);
|
||||
$reader = new \Composer\Repository\Pear\ChannelReader($httpDownloader);
|
||||
|
||||
$reader->read('http://pear.1.0.net/');
|
||||
$reader->read('http://pear.1.1.net/');
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue