Merge branch 'tls-config'
commit
d7c61c50ad
|
@ -12,7 +12,6 @@ addons:
|
|||
- parallel
|
||||
|
||||
php:
|
||||
- 5.3.3
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
|
@ -29,7 +28,6 @@ matrix:
|
|||
before_script:
|
||||
- rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
|
||||
- flags=""
|
||||
- if [ `phpenv version-name` == "5.3.3" ]; then flags="--ignore-platform-reqs"; fi
|
||||
- composer install $flags
|
||||
- bin/composer install $flags
|
||||
- git config --global user.name travis-ci
|
||||
|
|
|
@ -146,8 +146,9 @@ C:\Users\username>cd C:\bin
|
|||
C:\bin>php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
```
|
||||
|
||||
> **Note:** If the above fails due to readfile, use the `http` url or enable
|
||||
> php_openssl.dll in php.ini
|
||||
> **Note:** If the above fails due to readfile, enable php_openssl.dll in php.ini.
|
||||
> You may use the `http` URL, however this will leave the request susceptible to a
|
||||
> Man-In-The-Middle (MITM) attack.
|
||||
|
||||
Create a new `composer.bat` file alongside `composer.phar`:
|
||||
|
||||
|
|
|
@ -704,6 +704,11 @@ By default it points to $COMPOSER_HOME/cache on \*nix and OSX, and
|
|||
This env var controls the time Composer waits for commands (such as git
|
||||
commands) to finish executing. The default value is 300 seconds (5 minutes).
|
||||
|
||||
### COMPOSER_CAFILE
|
||||
|
||||
By setting this environmental value, you can set a path to a certificate bundle
|
||||
file to be used during SSL/TLS peer verification.
|
||||
|
||||
### COMPOSER_DISCARD_CHANGES
|
||||
|
||||
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
|
||||
|
|
|
@ -40,6 +40,25 @@ of their API. [Read
|
|||
more](articles/troubleshooting.md#api-rate-limit-and-oauth-tokens) on how to get
|
||||
an OAuth token for GitHub.
|
||||
|
||||
## gitlab-oauth
|
||||
|
||||
A list of domain names and oauth keys. For example using `{"gitlab.com":
|
||||
"oauthtoken"}` as the value of this option will use `oauthtoken` to access
|
||||
private repositories on gitlab.
|
||||
|
||||
## disable-tls
|
||||
|
||||
Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP
|
||||
instead and no network level encryption is performed. Enabling this is a
|
||||
security risk and is NOT recommended. The better way is to enable the
|
||||
php_openssl extension in php.ini.
|
||||
|
||||
## cafile
|
||||
|
||||
A way to set the path to the openssl CA file. In PHP 5.6+ you should rather
|
||||
set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to
|
||||
detect your system CA file automatically.
|
||||
|
||||
## http-basic
|
||||
|
||||
A list of domain names and username/passwords to authenticate against them. For
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -141,6 +141,14 @@
|
|||
"description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"disable-tls": {
|
||||
"type": "boolean",
|
||||
"description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini."
|
||||
},
|
||||
"cafile": {
|
||||
"type": "string",
|
||||
"description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."
|
||||
},
|
||||
"http-basic": {
|
||||
"type": "object",
|
||||
"description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
|
||||
|
|
|
@ -133,7 +133,8 @@ EOT
|
|||
throw new \RuntimeException('--file and --global can not be combined');
|
||||
}
|
||||
|
||||
$this->config = Factory::createConfig($this->getIO());
|
||||
$io = $this->getIO();
|
||||
$this->config = Factory::createConfig($io);
|
||||
|
||||
// Get the local composer.json, global config.json, or if the user
|
||||
// passed in a file to use
|
||||
|
@ -146,14 +147,14 @@ EOT
|
|||
file_put_contents($configFile, "{\n}\n");
|
||||
}
|
||||
|
||||
$this->configFile = new JsonFile($configFile);
|
||||
$this->configFile = new JsonFile($configFile, null, $io);
|
||||
$this->configSource = new JsonConfigSource($this->configFile);
|
||||
|
||||
$authConfigFile = $input->getOption('global')
|
||||
? ($this->config->get('home') . '/auth.json')
|
||||
: dirname(realpath($configFile)) . '/auth.json';
|
||||
|
||||
$this->authConfigFile = new JsonFile($authConfigFile);
|
||||
$this->authConfigFile = new JsonFile($authConfigFile, null, $io);
|
||||
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
||||
|
||||
// initialize the global file if it's not there
|
||||
|
@ -326,6 +327,11 @@ EOT
|
|||
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||
'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
|
||||
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||
'disable-tls' => array($booleanValidator, $booleanNormalizer),
|
||||
'cafile' => array(
|
||||
function ($val) { return file_exists($val) && is_readable($val); },
|
||||
function ($val) { return $val === 'null' ? null : $val; }
|
||||
),
|
||||
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
||||
);
|
||||
$multiConfigValues = array(
|
||||
|
|
|
@ -239,7 +239,7 @@ EOT
|
|||
if (null === $repositoryUrl) {
|
||||
$sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
|
||||
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION) && file_exists($repositoryUrl)) {
|
||||
$json = new JsonFile($repositoryUrl, new RemoteFilesystem($io, $config));
|
||||
$json = new JsonFile($repositoryUrl, Factory::createRemoteFilesystem($io, $config));
|
||||
$data = $json->read();
|
||||
if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
|
||||
$sourceRepo = new ComposerRepository(array('url' => 'file://' . strtr(realpath($repositoryUrl), '\\', '/')), $io, $config);
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Command;
|
|||
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
use Composer\Config;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
|
@ -73,7 +74,7 @@ EOT
|
|||
$config = Factory::createConfig();
|
||||
}
|
||||
|
||||
$this->rfs = new RemoteFilesystem($io, $config);
|
||||
$this->rfs = Factory::createRemoteFilesystem($io, $config);
|
||||
$this->process = new ProcessExecutor($io);
|
||||
|
||||
$io->write('Checking platform settings: ', false);
|
||||
|
@ -83,10 +84,10 @@ EOT
|
|||
$this->outputResult($this->checkGit());
|
||||
|
||||
$io->write('Checking http connectivity to packagist: ', false);
|
||||
$this->outputResult($this->checkHttp('http'));
|
||||
$this->outputResult($this->checkHttp('http', $config));
|
||||
|
||||
$io->write('Checking https connectivity to packagist: ', false);
|
||||
$this->outputResult($this->checkHttp('https'));
|
||||
$this->outputResult($this->checkHttp('https', $config));
|
||||
|
||||
$opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org'));
|
||||
if (!empty($opts['http']['proxy'])) {
|
||||
|
@ -172,12 +173,32 @@ EOT
|
|||
return true;
|
||||
}
|
||||
|
||||
private function checkHttp($proto)
|
||||
private function checkHttp($proto, Config $config)
|
||||
{
|
||||
$disableTls = false;
|
||||
$result = array();
|
||||
if ($proto === 'https' && $config->get('disable-tls') === true) {
|
||||
$disableTls = true;
|
||||
$result[] = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
|
||||
}
|
||||
if ($proto === 'https' && !extension_loaded('openssl') && !$disableTls) {
|
||||
$result[] = '<error>Composer is configured to use SSL/TLS protection but the openssl extension is not available.</error>';
|
||||
}
|
||||
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $proto . '://packagist.org/packages.json', false);
|
||||
} catch (\Exception $e) {
|
||||
return $e;
|
||||
} catch (TransportException $e) {
|
||||
if (false !== strpos($e->getMessage(), 'cafile')) {
|
||||
$result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
|
||||
$result[] = '<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>';
|
||||
$result[] = '<error>You can alternatively disable this error, at your own risk, by enabling the \'disable-tls\' option.</error>';
|
||||
} else {
|
||||
array_unshift($result, '[' . get_class($e) . '] ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (count($result) > 0) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -332,7 +353,13 @@ EOT
|
|||
if ($result instanceof \Exception) {
|
||||
$io->write('['.get_class($result).'] '.$result->getMessage());
|
||||
} elseif ($result) {
|
||||
$io->write(trim($result));
|
||||
if (is_array($result)) {
|
||||
foreach ($result as $message) {
|
||||
$io->write($message);
|
||||
}
|
||||
} else {
|
||||
$io->write($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,10 +58,17 @@ EOT
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE;
|
||||
$config = Factory::createConfig();
|
||||
|
||||
if ($config->get('disable-tls') === true) {
|
||||
$baseUrl = 'http://' . self::HOMEPAGE;
|
||||
} else {
|
||||
$baseUrl = 'https://' . self::HOMEPAGE;
|
||||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
$remoteFilesystem = new RemoteFilesystem($io, $config);
|
||||
$remoteFilesystem = Factory::createRemoteFilesystem($io, $config);
|
||||
|
||||
$cacheDir = $config->get('cache-dir');
|
||||
$rollbackDir = $config->get('home');
|
||||
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
|
||||
|
|
|
@ -137,6 +137,9 @@ class Compiler
|
|||
$this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/include_paths.php'));
|
||||
}
|
||||
$this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php'));
|
||||
|
||||
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../res/cacert.pem'), false);
|
||||
|
||||
$this->addComposerBin($phar);
|
||||
|
||||
// Stubs
|
||||
|
|
|
@ -44,6 +44,8 @@ class Config
|
|||
'classmap-authoritative' => false,
|
||||
'prepend-autoloader' => true,
|
||||
'github-domains' => array('github.com'),
|
||||
'disable-tls' => false,
|
||||
'cafile' => null,
|
||||
'github-expose-hostname' => true,
|
||||
'gitlab-domains' => array('gitlab.com'),
|
||||
'store-auths' => 'prompt',
|
||||
|
@ -174,6 +176,7 @@ class Config
|
|||
case 'cache-files-dir':
|
||||
case 'cache-repo-dir':
|
||||
case 'cache-vcs-dir':
|
||||
case 'cafile':
|
||||
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
|
||||
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
|
||||
|
||||
|
@ -263,6 +266,9 @@ class Config
|
|||
|
||||
return $this->config[$key];
|
||||
|
||||
case 'disable-tls':
|
||||
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
|
||||
|
||||
default:
|
||||
if (!isset($this->config[$key])) {
|
||||
return null;
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Downloader;
|
|||
|
||||
use Composer\Config;
|
||||
use Composer\Cache;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
|
@ -54,7 +55,7 @@ class FileDownloader implements DownloaderInterface
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs ?: new RemoteFilesystem($io, $config);
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
$this->cache = $cache;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use Composer\Cache;
|
|||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -28,10 +29,10 @@ class GzipDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Config;
|
|||
use Composer\Cache;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\IO\IOInterface;
|
||||
use RarArchive;
|
||||
|
||||
|
@ -30,10 +31,10 @@ class RarDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
|
|
|
@ -37,7 +37,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->filesystem = $fs ?: new Filesystem;
|
||||
$this->filesystem = $fs ?: new Filesystem($this->process);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,7 @@ use Composer\Cache;
|
|||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -29,11 +30,11 @@ class XzDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Config;
|
|||
use Composer\Cache;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\IO\IOInterface;
|
||||
use ZipArchive;
|
||||
|
||||
|
@ -26,10 +27,10 @@ class ZipDownloader extends ArchiveDownloader
|
|||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
|
|
|
@ -19,12 +19,14 @@ use Composer\Package\Archiver;
|
|||
use Composer\Package\Version\VersionGuesser;
|
||||
use Composer\Repository\RepositoryManager;
|
||||
use Composer\Repository\WritableRepositoryInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Autoload\AutoloadGenerator;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Seld\JsonLint\JsonParser;
|
||||
|
||||
/**
|
||||
|
@ -161,7 +163,7 @@ class Factory
|
|||
throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
|
||||
}
|
||||
$factory = new static;
|
||||
$rm = $factory->createRepositoryManager($io, $config);
|
||||
$rm = $factory->createRepositoryManager($io, $config, null, self::createRemoteFilesystem($io, $config));
|
||||
}
|
||||
|
||||
foreach ($config->getRepositories() as $index => $repo) {
|
||||
|
@ -207,7 +209,8 @@ class Factory
|
|||
|
||||
if (is_string($localConfig)) {
|
||||
$composerFile = $localConfig;
|
||||
$file = new JsonFile($localConfig, new RemoteFilesystem($io));
|
||||
|
||||
$file = new JsonFile($localConfig, null, $io);
|
||||
|
||||
if (!$file->exists()) {
|
||||
if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
|
||||
|
@ -260,16 +263,18 @@ class Factory
|
|||
$io->loadConfiguration($config);
|
||||
}
|
||||
|
||||
$rfs = self::createRemoteFilesystem($io, $config);
|
||||
|
||||
// initialize event dispatcher
|
||||
$dispatcher = new EventDispatcher($composer, $io);
|
||||
$composer->setEventDispatcher($dispatcher);
|
||||
|
||||
// initialize repository manager
|
||||
$rm = $this->createRepositoryManager($io, $config, $dispatcher);
|
||||
$rm = $this->createRepositoryManager($io, $config, $dispatcher, $rfs);
|
||||
$composer->setRepositoryManager($rm);
|
||||
|
||||
// load local repository
|
||||
$this->addLocalRepository($rm, $vendorDir);
|
||||
$this->addLocalRepository($io, $rm, $vendorDir);
|
||||
|
||||
// force-set the version of the global package if not defined as
|
||||
// guessing it adds no value and only takes time
|
||||
|
@ -290,7 +295,7 @@ class Factory
|
|||
|
||||
if ($fullLoad) {
|
||||
// initialize download manager
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher);
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
|
||||
$composer->setDownloadManager($dm);
|
||||
|
||||
// initialize autoload generator
|
||||
|
@ -320,7 +325,8 @@ class Factory
|
|||
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
|
||||
? substr($composerFile, 0, -4).'lock'
|
||||
: $composerFile . '.lock';
|
||||
$locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, file_get_contents($composerFile));
|
||||
|
||||
$locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
|
||||
$composer->setLocker($locker);
|
||||
}
|
||||
|
||||
|
@ -333,9 +339,9 @@ class Factory
|
|||
* @param EventDispatcher $eventDispatcher
|
||||
* @return Repository\RepositoryManager
|
||||
*/
|
||||
protected function createRepositoryManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
|
||||
protected function createRepositoryManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$rm = new RepositoryManager($io, $config, $eventDispatcher);
|
||||
$rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs);
|
||||
$rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
|
||||
$rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
|
||||
$rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
|
||||
|
@ -355,9 +361,9 @@ class Factory
|
|||
* @param Repository\RepositoryManager $rm
|
||||
* @param string $vendorDir
|
||||
*/
|
||||
protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
|
||||
protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir)
|
||||
{
|
||||
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
|
||||
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -388,7 +394,7 @@ class Factory
|
|||
* @param EventDispatcher $eventDispatcher
|
||||
* @return Downloader\DownloadManager
|
||||
*/
|
||||
public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
|
||||
public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$cache = null;
|
||||
if ($config->get('cache-files-ttl') > 0) {
|
||||
|
@ -409,18 +415,21 @@ class Factory
|
|||
break;
|
||||
}
|
||||
|
||||
$dm->setDownloader('git', new Downloader\GitDownloader($io, $config));
|
||||
$dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config));
|
||||
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config));
|
||||
$executor = new ProcessExecutor($io);
|
||||
$fs = new Filesystem($executor);
|
||||
|
||||
$dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $executor, $fs));
|
||||
$dm->setDownloader('svn', new Downloader\SvnDownloader($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));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$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));
|
||||
|
||||
return $dm;
|
||||
}
|
||||
|
@ -503,4 +512,48 @@ class Factory
|
|||
|
||||
return $factory->createComposer($io, $config, $disablePlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOInterface $io IO instance
|
||||
* @param Config $config Config instance
|
||||
* @param array $options Array of options passed directly to RemoteFilesystem constructor
|
||||
* @return RemoteFilesystem
|
||||
*/
|
||||
public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
|
||||
{
|
||||
static $warned = false;
|
||||
$disableTls = false;
|
||||
if ($config && $config->get('disable-tls') === true) {
|
||||
if (!$warned) {
|
||||
$io->write('<warning>You are running Composer with SSL/TLS protection disabled.</warning>');
|
||||
}
|
||||
$warned = true;
|
||||
$disableTls = true;
|
||||
} elseif (!extension_loaded('openssl')) {
|
||||
throw new \RuntimeException('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();
|
||||
if ($disableTls === false) {
|
||||
if ($config && $config->get('cafile')) {
|
||||
$remoteFilesystemOptions = array('ssl' => array('cafile' => $config->get('cafile')));
|
||||
}
|
||||
$remoteFilesystemOptions = array_merge_recursive($remoteFilesystemOptions, $options);
|
||||
}
|
||||
try {
|
||||
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $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>');
|
||||
$io->write('<error>A valid CA certificate file is required for SSL/TLS protection.</error>');
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
$io->write('<error>It is recommended you upgrade to PHP 5.6+ which can detect your system CA file automatically.</error>');
|
||||
}
|
||||
$io->write('<error>You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.</error>');
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $remoteFilesystem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use JsonSchema\Validator;
|
|||
use Seld\JsonLint\JsonParser;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +36,7 @@ class JsonFile
|
|||
|
||||
private $path;
|
||||
private $rfs;
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* Initializes json file reader/parser.
|
||||
|
@ -43,7 +45,7 @@ class JsonFile
|
|||
* @param RemoteFilesystem $rfs required for loading http/https json files
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($path, RemoteFilesystem $rfs = null)
|
||||
public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
|
@ -51,6 +53,7 @@ class JsonFile
|
|||
throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
|
||||
}
|
||||
$this->rfs = $rfs;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,6 +86,9 @@ class JsonFile
|
|||
if ($this->rfs) {
|
||||
$json = $this->rfs->getContents($this->path, $this->path, false);
|
||||
} else {
|
||||
if ($this->io && $this->io->isDebug()) {
|
||||
$this->io->writeError('Reading ' . $this->path);
|
||||
}
|
||||
$json = file_get_contents($this->path);
|
||||
}
|
||||
} catch (TransportException $e) {
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\DependencyResolver\Pool;
|
|||
use Composer\Json\JsonFile;
|
||||
use Composer\Cache;
|
||||
use Composer\Config;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
|
@ -58,7 +59,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
|||
private $degradedMode = false;
|
||||
private $rootData;
|
||||
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) {
|
||||
// assume http as the default protocol
|
||||
|
@ -89,7 +90,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
|||
$this->io = $io;
|
||||
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
|
||||
$this->loader = new ArrayLoader();
|
||||
$this->rfs = new RemoteFilesystem($this->io, $this->config, $this->options);
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $this->config, $this->options);
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->repoConfig = $repoConfig;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use Composer\Package\Link;
|
|||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Config;
|
||||
use Composer\Factory;
|
||||
|
||||
/**
|
||||
* Builds list of package from PEAR channel.
|
||||
|
@ -58,7 +59,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
|||
|
||||
$this->url = rtrim($repoConfig['url'], '/');
|
||||
$this->io = $io;
|
||||
$this->rfs = $rfs ?: new RemoteFilesystem($this->io, $config);
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null;
|
||||
$this->versionParser = new VersionParser();
|
||||
$this->repoConfig = $repoConfig;
|
||||
|
|
|
@ -16,6 +16,7 @@ use Composer\IO\IOInterface;
|
|||
use Composer\Config;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
* Repositories manager.
|
||||
|
@ -32,12 +33,14 @@ class RepositoryManager
|
|||
private $io;
|
||||
private $config;
|
||||
private $eventDispatcher;
|
||||
private $rfs;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,7 +105,7 @@ class RepositoryManager
|
|||
|
||||
$class = $this->repositoryClasses[$type];
|
||||
|
||||
return new $class($config, $this->io, $this->config, $this->eventDispatcher);
|
||||
return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Repository\Vcs;
|
|||
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Config;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
@ -62,7 +63,7 @@ abstract class VcsDriver implements VcsDriverInterface
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io, $config);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,7 @@ use Composer\Json\JsonValidationException;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Spdx\SpdxLicenses;
|
||||
use Composer\Factory;
|
||||
|
||||
/**
|
||||
* Validates a composer configuration.
|
||||
|
@ -52,7 +53,7 @@ class ConfigValidator
|
|||
// validate json schema
|
||||
$laxValid = false;
|
||||
try {
|
||||
$json = new JsonFile($file, new RemoteFilesystem($this->io));
|
||||
$json = new JsonFile($file, null, $this->io);
|
||||
$manifest = $json->read();
|
||||
|
||||
$json->validateSchema(JsonFile::LAX_SCHEMA);
|
||||
|
|
|
@ -39,7 +39,7 @@ class GitHub
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor;
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io, $config);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Util;
|
|||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Config;
|
||||
use Composer\Factory;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
|
@ -40,7 +41,7 @@ class GitLab
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->process = $process ?: new ProcessExecutor();
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io, $config);
|
||||
$this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,7 +33,8 @@ class RemoteFilesystem
|
|||
private $retry;
|
||||
private $progress;
|
||||
private $lastProgress;
|
||||
private $options;
|
||||
private $options = array();
|
||||
private $disableTls = false;
|
||||
private $retryAuthFailure;
|
||||
private $lastHeaders;
|
||||
private $storeAuth;
|
||||
|
@ -42,15 +43,34 @@ class RemoteFilesystem
|
|||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The config
|
||||
* @param array $options The options
|
||||
* @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 = null, array $options = array())
|
||||
public function __construct(IOInterface $io, Config $config = null, 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();
|
||||
if (isset($options['ssl']['cafile'])
|
||||
&& (
|
||||
!is_readable($options['ssl']['cafile'])
|
||||
|| !self::validateCaFile(file_get_contents($options['ssl']['cafile']))
|
||||
)
|
||||
) {
|
||||
throw new TransportException('The configured cafile was not valid or could not be read.');
|
||||
}
|
||||
} else {
|
||||
$this->disableTls = true;
|
||||
}
|
||||
|
||||
// handle the other externally set options normally.
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
$this->config = $config;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,6 +114,11 @@ class RemoteFilesystem
|
|||
return $this->options;
|
||||
}
|
||||
|
||||
public function isTlsDisabled()
|
||||
{
|
||||
return $this->disableTls === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers of the last request
|
||||
*
|
||||
|
@ -118,7 +143,7 @@ class RemoteFilesystem
|
|||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true)
|
||||
protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true, $expectedCommonName = '')
|
||||
{
|
||||
if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
|
||||
$originUrl = 'github.com';
|
||||
|
@ -145,7 +170,7 @@ class RemoteFilesystem
|
|||
unset($additionalOptions['retry-auth-failure']);
|
||||
}
|
||||
|
||||
$options = $this->getOptionsForUrl($originUrl, $additionalOptions);
|
||||
$options = $this->getOptionsForUrl($originUrl, $additionalOptions, $expectedCommonName);
|
||||
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
|
||||
|
@ -294,7 +319,7 @@ class RemoteFilesystem
|
|||
if ($this->retry) {
|
||||
$this->retry = false;
|
||||
|
||||
$result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
|
||||
$result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress, $expectedCommonName);
|
||||
|
||||
$authHelper = new AuthHelper($this->io, $this->config);
|
||||
$authHelper->storeAuth($this->originUrl, $this->storeAuth);
|
||||
|
@ -304,7 +329,7 @@ class RemoteFilesystem
|
|||
}
|
||||
|
||||
if (false === $result) {
|
||||
$e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
|
||||
$e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage.' using CN='.$expectedCommonName, $errorCode);
|
||||
if (!empty($http_response_header[0])) {
|
||||
$e->setHeaders($http_response_header);
|
||||
}
|
||||
|
@ -449,13 +474,27 @@ class RemoteFilesystem
|
|||
|
||||
protected function getOptionsForUrl($originUrl, $additionalOptions)
|
||||
{
|
||||
$tlsOptions = array();
|
||||
|
||||
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
|
||||
if ($this->disableTls === false && PHP_VERSION_ID < 50600) {
|
||||
if (!preg_match('{^https?://}', $this->fileUrl)) {
|
||||
$host = $originUrl;
|
||||
} else {
|
||||
$host = parse_url($this->fileUrl, PHP_URL_HOST);
|
||||
}
|
||||
|
||||
$tlsOptions['ssl']['CN_match'] = $host;
|
||||
$tlsOptions['ssl']['SNI_server_name'] = $host;
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
|
||||
if (extension_loaded('zlib')) {
|
||||
$headers[] = 'Accept-Encoding: gzip';
|
||||
}
|
||||
|
||||
$options = array_replace_recursive($this->options, $additionalOptions);
|
||||
$options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions);
|
||||
if (!$this->degradedMode) {
|
||||
// degraded mode disables HTTP/1.1 which causes issues with some bad
|
||||
// proxies/software due to the use of chunked encoding
|
||||
|
@ -486,4 +525,200 @@ class RemoteFilesystem
|
|||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function getTlsDefaults()
|
||||
{
|
||||
$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',
|
||||
'ECDHE-RSA-RC4-SHA',
|
||||
'ECDHE-ECDSA-RC4-SHA',
|
||||
'AES128',
|
||||
'AES256',
|
||||
'RC4-SHA',
|
||||
'HIGH',
|
||||
'!aNULL',
|
||||
'!eNULL',
|
||||
'!EXPORT',
|
||||
'!DES',
|
||||
'!3DES',
|
||||
'!MD5',
|
||||
'!PSK'
|
||||
));
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
$options = array(
|
||||
'ssl' => array(
|
||||
'ciphers' => $ciphers,
|
||||
'verify_peer' => true,
|
||||
'verify_depth' => 7,
|
||||
'SNI_enabled' => true,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* 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($this->options['ssl']['cafile'])) {
|
||||
$result = self::getSystemCaRootBundlePath();
|
||||
if ($result) {
|
||||
if (preg_match('{^phar://}', $result)) {
|
||||
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem';
|
||||
|
||||
// use stream_copy_to_stream instead of copy
|
||||
// to work around https://bugs.php.net/bug.php?id=64634
|
||||
$source = fopen($result, 'r');
|
||||
$target = fopen($targetPath, 'w+');
|
||||
stream_copy_to_stream($source, $target);
|
||||
fclose($source);
|
||||
fclose($target);
|
||||
unset($source, $target);
|
||||
|
||||
$options['ssl']['cafile'] = $targetPath;
|
||||
} else {
|
||||
if (is_dir($result)) {
|
||||
$options['ssl']['capath'] = $result;
|
||||
} elseif ($result) {
|
||||
$options['ssl']['cafile'] = $result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new TransportException('A valid cafile could not be located automatically.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable TLS compression to prevent CRIME attacks where supported.
|
||||
*/
|
||||
if (PHP_VERSION_ID >= 50413) {
|
||||
$options['ssl']['disable_compression'] = true;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was adapted from Sslurp.
|
||||
* https://github.com/EvanDotPro/Sslurp
|
||||
*
|
||||
* (c) Evan Coury <me@evancoury.com>
|
||||
*
|
||||
* For the full copyright and license information, please see below:
|
||||
*
|
||||
* Copyright (c) 2013, Evan Coury
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
private static function getSystemCaRootBundlePath()
|
||||
{
|
||||
static $caPath = null;
|
||||
|
||||
if ($caPath !== null) {
|
||||
return $caPath;
|
||||
}
|
||||
|
||||
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
|
||||
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
|
||||
$envCertFile = getenv('SSL_CERT_FILE');
|
||||
if ($envCertFile && is_readable($envCertFile) && self::validateCaFile(file_get_contents($envCertFile))) {
|
||||
// Possibly throw exception instead of ignoring SSL_CERT_FILE if it's invalid?
|
||||
return $caPath = $envCertFile;
|
||||
}
|
||||
|
||||
$caBundlePaths = array(
|
||||
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
|
||||
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
|
||||
'/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
|
||||
'/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
|
||||
'/usr/ssl/certs/ca-bundle.crt', // Cygwin
|
||||
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
|
||||
'/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
|
||||
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
|
||||
'/etc/ssl/cert.pem', // OpenBSD
|
||||
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
|
||||
__DIR__.'/../../../res/cacert.pem', // Bundled with Composer
|
||||
);
|
||||
|
||||
$configured = ini_get('openssl.cafile');
|
||||
if ($configured && strlen($configured) > 0 && is_readable($caBundle) && self::validateCaFile(file_get_contents($caBundle))) {
|
||||
return $caPath = $configured;
|
||||
}
|
||||
|
||||
foreach ($caBundlePaths as $caBundle) {
|
||||
if (@is_readable($caBundle) && self::validateCaFile(file_get_contents($caBundle))) {
|
||||
return $caPath = $caBundle;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($caBundlePaths as $caBundle) {
|
||||
$caBundle = dirname($caBundle);
|
||||
if (is_dir($caBundle) && glob($caBundle.'/*')) {
|
||||
return $caPath = $caBundle;
|
||||
}
|
||||
}
|
||||
|
||||
return $caPath = false;
|
||||
}
|
||||
|
||||
private static function validateCaFile($contents)
|
||||
{
|
||||
// assume the CA is valid if php is vulnerable to
|
||||
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
|
||||
if (
|
||||
PHP_VERSION_ID <= 50327
|
||||
|| (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
|
||||
|| (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
|
||||
) {
|
||||
return !empty($contents);
|
||||
}
|
||||
|
||||
return (bool) openssl_x509_parse($contents);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,4 +170,20 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$this->assertEquals(array('https'), $config->get('github-protocols'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group TLS
|
||||
*/
|
||||
public function testDisableTlsCanBeOverridden()
|
||||
{
|
||||
$config = new Config;
|
||||
$config->merge(
|
||||
array('config' => array('disable-tls' => 'false'))
|
||||
);
|
||||
$this->assertFalse($config->get('disable-tls'));
|
||||
$config->merge(
|
||||
array('config' => array('disable-tls' => 'true'))
|
||||
);
|
||||
$this->assertTrue($config->get('disable-tls'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?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\Test;
|
||||
|
||||
use Composer\Config;
|
||||
|
||||
class DefaultConfigTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @group TLS
|
||||
*/
|
||||
public function testDefaultValuesAreAsExpected()
|
||||
{
|
||||
$config = new Config;
|
||||
$this->assertFalse($config->get('disable-tls'));
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ namespace Composer\Test\Downloader;
|
|||
|
||||
use Composer\Downloader\XzDownloader;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
class XzDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -63,7 +64,7 @@ class XzDownloaderTest extends \PHPUnit_Framework_TestCase
|
|||
->method('get')
|
||||
->with('vendor-dir')
|
||||
->will($this->returnValue($this->testDir));
|
||||
$downloader = new XzDownloader($io, $config);
|
||||
$downloader = new XzDownloader($io, $config, null, null, null, new RemoteFilesystem($io));
|
||||
|
||||
try {
|
||||
$downloader->download($packageMock, sys_get_temp_dir().'/composer-xz-test');
|
||||
|
|
|
@ -55,7 +55,15 @@ class ZipDownloaderTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$io = $this->getMock('Composer\IO\IOInterface');
|
||||
$config = $this->getMock('Composer\Config');
|
||||
$config->expects($this->any())
|
||||
$config->expects($this->at(0))
|
||||
->method('get')
|
||||
->with('disable-tls')
|
||||
->will($this->returnValue(false));
|
||||
$config->expects($this->at(1))
|
||||
->method('get')
|
||||
->with('cafile')
|
||||
->will($this->returnValue(null));
|
||||
$config->expects($this->at(2))
|
||||
->method('get')
|
||||
->with('vendor-dir')
|
||||
->will($this->returnValue($this->testDir));
|
||||
|
|
|
@ -33,7 +33,7 @@ class FactoryMock extends Factory
|
|||
return $config;
|
||||
}
|
||||
|
||||
protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
|
||||
protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -165,11 +165,41 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
|
|||
unlink($file);
|
||||
}
|
||||
|
||||
protected function callGetOptionsForUrl($io, array $args = array(), array $options = array())
|
||||
/**
|
||||
* @group TLS
|
||||
*/
|
||||
public function testGetOptionsForUrlCreatesSecureTlsDefaults()
|
||||
{
|
||||
$io = $this->getMock('Composer\IO\IOInterface');
|
||||
|
||||
$res = $this->callGetOptionsForUrl($io, array('example.org', array('ssl'=>array('cafile'=>'/some/path/file.crt'))), array(), 'http://www.example.org');
|
||||
|
||||
$this->assertTrue(isset($res['ssl']['ciphers']));
|
||||
$this->assertRegExp("|!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK|", $res['ssl']['ciphers']);
|
||||
$this->assertTrue($res['ssl']['verify_peer']);
|
||||
$this->assertTrue($res['ssl']['SNI_enabled']);
|
||||
$this->assertEquals(7, $res['ssl']['verify_depth']);
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
$this->assertEquals('www.example.org', $res['ssl']['CN_match']);
|
||||
$this->assertEquals('www.example.org', $res['ssl']['SNI_server_name']);
|
||||
}
|
||||
$this->assertEquals('/some/path/file.crt', $res['ssl']['cafile']);
|
||||
if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
|
||||
$this->assertTrue($res['ssl']['disable_compression']);
|
||||
} else {
|
||||
$this->assertFalse(isset($res['ssl']['disable_compression']));
|
||||
}
|
||||
}
|
||||
|
||||
protected function callGetOptionsForUrl($io, array $args = array(), array $options = array(), $fileUrl = '')
|
||||
{
|
||||
$fs = new RemoteFilesystem($io, null, $options);
|
||||
$ref = new \ReflectionMethod($fs, 'getOptionsForUrl');
|
||||
$prop = new \ReflectionProperty($fs, 'fileUrl');
|
||||
$ref->setAccessible(true);
|
||||
$prop->setAccessible(true);
|
||||
|
||||
$prop->setValue($fs, $fileUrl);
|
||||
|
||||
return $ref->invokeArgs($fs, $args);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue