Merge branch 'master' into 2.0

main
Jordi Boggiano 5 years ago
commit 23359f2db6
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC

90
composer.lock generated

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.2.4", "version": "1.2.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e",
"reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -28,7 +28,7 @@
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0" "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -46,28 +46,34 @@
"MIT" "MIT"
], ],
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"time": "2019-08-30T08:44:50+00:00" "keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"time": "2020-01-13T10:02:55+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
"version": "1.5.0", "version": "1.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/semver.git", "url": "https://github.com/composer/semver.git",
"reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
"reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.3.2 || ^7.0" "php": "^5.3.2 || ^7.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.5 || ^5.0.5", "phpunit/phpunit": "^4.5 || ^5.0.5"
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -85,7 +91,13 @@
"MIT" "MIT"
], ],
"description": "Semver library that offers utilities, version constraint parsing and validation.", "description": "Semver library that offers utilities, version constraint parsing and validation.",
"time": "2019-03-19T17:25:45+00:00" "keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"time": "2020-01-13T12:06:48+00:00"
}, },
{ {
"name": "composer/spdx-licenses", "name": "composer/spdx-licenses",
@ -387,16 +399,16 @@
}, },
{ {
"name": "seld/phar-utils", "name": "seld/phar-utils",
"version": "1.0.1", "version": "1.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/phar-utils.git", "url": "https://github.com/Seldaek/phar-utils.git",
"reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" "reference": "84715761c35808076b00908a20317a3a8a67d17e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/84715761c35808076b00908a20317a3a8a67d17e",
"reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", "reference": "84715761c35808076b00908a20317a3a8a67d17e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -418,7 +430,10 @@
"MIT" "MIT"
], ],
"description": "PHAR file format utilities, for when PHP phars you up", "description": "PHAR file format utilities, for when PHP phars you up",
"time": "2015-10-13T18:44:15+00:00" "keywords": [
"phra"
],
"time": "2020-01-13T10:41:09+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
@ -486,7 +501,7 @@
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v2.8.50", "version": "v2.8.52",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
@ -651,16 +666,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.12.0", "version": "v1.13.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4" "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4", "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -672,7 +687,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.12-dev" "dev-master": "1.13-dev"
} }
}, },
"autoload": { "autoload": {
@ -689,20 +704,26 @@
], ],
"description": "Symfony polyfill for ctype functions", "description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-08-06T08:03:45+00:00" "keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2019-11-27T13:56:44+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.12.0", "version": "v1.13.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
"reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -714,7 +735,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.12-dev" "dev-master": "1.13-dev"
} }
}, },
"autoload": { "autoload": {
@ -731,7 +752,14 @@
], ],
"description": "Symfony polyfill for the Mbstring extension", "description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-08-06T08:03:45+00:00" "keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2019-11-27T14:18:11+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",

@ -241,14 +241,14 @@ be in your project root, on the same level as `vendor` directory is. An example
filename would be `src/Foo.php` containing an `Acme\Foo` class. filename would be `src/Foo.php` containing an `Acme\Foo` class.
After adding the [`autoload`](04-schema.md#autoload) field, you have to re-run After adding the [`autoload`](04-schema.md#autoload) field, you have to re-run
this command : this command :
```sh ```sh
php composer.phar dump-autoload php composer.phar dump-autoload
``` ```
This command will re-generate the `vendor/autoload.php` file. This command will re-generate the `vendor/autoload.php` file.
See the [`dump-autoload`](03-cli.md#dump-autoload) section for more informations. See the [`dump-autoload`](03-cli.md#dump-autoload) section for more information.
Including that file will also return the autoloader instance, so you can store Including that file will also return the autoloader instance, so you can store
the return value of the include call in a variable and add more namespaces. the return value of the include call in a variable and add more namespaces.

@ -342,6 +342,21 @@ JSON array of commands.
You can also call a shell/bash script, which will have the path to You can also call a shell/bash script, which will have the path to
the PHP executable available in it as a `PHP_BINARY` env var. the PHP executable available in it as a `PHP_BINARY` env var.
## Setting environment variables
To set an environment variable in a cross-platform way, you can use `@putenv`:
```json
{
"scripts": {
"install-phpstan": [
"@putenv COMPOSER=phpstan-composer.json",
"composer install --prefer-dist"
]
}
}
```
## Custom descriptions. ## Custom descriptions.
You can set custom script descriptions with the following in your `composer.json`: You can set custom script descriptions with the following in your `composer.json`:

@ -41,7 +41,7 @@
"version": { "version": {
"type": "string", "type": "string",
"description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.", "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
"pattern": "^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?" "pattern": "^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?|^dev-"
}, },
"time": { "time": {
"type": "string", "type": "string",

@ -575,6 +575,13 @@ EOT
$warnings['xdebug_loaded'] = true; $warnings['xdebug_loaded'] = true;
} }
if (defined('PHP_WINDOWS_VERSION_BUILD')
&& (version_compare(PHP_VERSION, '7.2.23', '<')
|| (version_compare(PHP_VERSION, '7.3.0', '>=')
&& version_compare(PHP_VERSION, '7.3.10', '<')))) {
$warnings['onedrive'] = PHP_VERSION;
}
if (!empty($errors)) { if (!empty($errors)) {
foreach ($errors as $error => $current) { foreach ($errors as $error => $current) {
switch ($error) { switch ($error) {
@ -684,6 +691,11 @@ EOT
$text .= " xdebug.profiler_enabled = 0"; $text .= " xdebug.profiler_enabled = 0";
$displayIniMessage = true; $displayIniMessage = true;
break; break;
case 'onedrive':
$text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL;
$text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL;
break;
} }
$out($text, 'comment'); $out($text, 'comment');
} }

@ -138,11 +138,11 @@ class SvnDownloader extends VcsDownloader
$this->io->writeError(sprintf(' <error>The package has modified file%s:</error>', $countChanges === 1 ? '' : 's')); $this->io->writeError(sprintf(' <error>The package has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
$this->io->writeError(array_slice($changes, 0, 10)); $this->io->writeError(array_slice($changes, 0, 10));
if ($countChanges > 10) { if ($countChanges > 10) {
$remaingChanges = $countChanges - 10; $remainingChanges = $countChanges - 10;
$this->io->writeError( $this->io->writeError(
sprintf( sprintf(
' <info>'.$remaingChanges.' more file%s modified, choose "v" to view the full list</info>', ' <info>'.$remainingChanges.' more file%s modified, choose "v" to view the full list</info>',
$remaingChanges === 1 ? '' : 's' $remainingChanges === 1 ? '' : 's'
) )
); );
} }

@ -246,7 +246,11 @@ class EventDispatcher
} }
} }
if (substr($exec, 0, 5) === '@php ') { if (substr($exec, 0, 8) === '@putenv ') {
putenv(substr($exec, 8));
continue;
} elseif (substr($exec, 0, 5) === '@php ') {
$exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5); $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5);
} else { } else {
$finder = new PhpExecutableFinder(); $finder = new PhpExecutableFinder();
@ -492,7 +496,7 @@ class EventDispatcher
*/ */
protected function isComposerScript($callable) protected function isComposerScript($callable)
{ {
return '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5); return '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5) && '@putenv ' !== substr($callable, 0, 8);
} }
/** /**

@ -64,6 +64,22 @@ abstract class BaseIO implements IOInterface
$this->authentications[$repositoryName] = array('username' => $username, 'password' => $password); $this->authentications[$repositoryName] = array('username' => $username, 'password' => $password);
} }
/**
* {@inheritDoc}
*/
public function writeRaw($messages, $newline = true, $verbosity = self::NORMAL)
{
$this->write($messages, $newline, $verbosity);
}
/**
* {@inheritDoc}
*/
public function writeErrorRaw($messages, $newline = true, $verbosity = self::NORMAL)
{
$this->writeError($messages, $newline, $verbosity);
}
/** /**
* Check for overwrite and set the authentication information for the repository. * Check for overwrite and set the authentication information for the repository.
* *

@ -129,13 +129,29 @@ class ConsoleIO extends BaseIO
$this->doWrite($messages, $newline, true, $verbosity); $this->doWrite($messages, $newline, true, $verbosity);
} }
/**
* {@inheritDoc}
*/
public function writeRaw($messages, $newline = true, $verbosity = self::NORMAL)
{
$this->doWrite($messages, $newline, false, $verbosity, true);
}
/**
* {@inheritDoc}
*/
public function writeErrorRaw($messages, $newline = true, $verbosity = self::NORMAL)
{
$this->doWrite($messages, $newline, true, $verbosity, true);
}
/** /**
* @param array|string $messages * @param array|string $messages
* @param bool $newline * @param bool $newline
* @param bool $stderr * @param bool $stderr
* @param int $verbosity * @param int $verbosity
*/ */
private function doWrite($messages, $newline, $stderr, $verbosity) private function doWrite($messages, $newline, $stderr, $verbosity, $raw = false)
{ {
$sfVerbosity = $this->verbosityMap[$verbosity]; $sfVerbosity = $this->verbosityMap[$verbosity];
if ($sfVerbosity > $this->output->getVerbosity()) { if ($sfVerbosity > $this->output->getVerbosity()) {
@ -149,6 +165,14 @@ class ConsoleIO extends BaseIO
$sfVerbosity = OutputInterface::OUTPUT_NORMAL; $sfVerbosity = OutputInterface::OUTPUT_NORMAL;
} }
if ($raw) {
if ($sfVerbosity === OutputInterface::OUTPUT_NORMAL) {
$sfVerbosity = OutputInterface::OUTPUT_RAW;
} else {
$sfVerbosity |= OutputInterface::OUTPUT_RAW;
}
}
if (null !== $this->startTime) { if (null !== $this->startTime) {
$memoryUsage = memory_get_usage() / 1024 / 1024; $memoryUsage = memory_get_usage() / 1024 / 1024;
$timeSpent = microtime(true) - $this->startTime; $timeSpent = microtime(true) - $this->startTime;

@ -49,6 +49,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
/** @var VersionCacheInterface */ /** @var VersionCacheInterface */
private $versionCache; private $versionCache;
private $emptyReferences = array(); private $emptyReferences = array();
private $versionTransportExceptions = array();
public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, 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)
{ {
@ -131,6 +132,11 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
return $this->emptyReferences; return $this->emptyReferences;
} }
public function getVersionTransportExceptions()
{
return $this->versionTransportExceptions;
}
protected function initialize() protected function initialize()
{ {
parent::initialize(); parent::initialize();
@ -232,11 +238,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
$this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
} catch (\Exception $e) { } catch (\Exception $e) {
if ($e instanceof TransportException && $e->getCode() === 404) { if ($e instanceof TransportException) {
$this->emptyReferences[] = $identifier; $this->versionTransportExceptions['tags'][$tag] = $e;
if ($e->getCode() === 404) {
$this->emptyReferences[] = $identifier;
}
} }
if ($isVeryVerbose) { if ($isVeryVerbose) {
$this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'</warning>'); $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'</warning>');
} }
continue; continue;
} }
@ -312,11 +321,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
} }
$this->addPackage($package); $this->addPackage($package);
} catch (TransportException $e) { } catch (TransportException $e) {
$this->versionTransportExceptions['branches'][$branch] = $e;
if ($e->getCode() === 404) { if ($e->getCode() === 404) {
$this->emptyReferences[] = $identifier; $this->emptyReferences[] = $identifier;
} }
if ($isVeryVerbose) { if ($isVeryVerbose) {
$this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found</warning>'); $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)</warning>');
} }
continue; continue;
} catch (\Exception $e) { } catch (\Exception $e) {

@ -53,7 +53,7 @@ class Hg
return; return;
} }
// Try with the authentication informations available // Try with the authentication information available
if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) { if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) {
$auth = $this->io->getAuthentication($match[5]); $auth = $this->io->getAuthentication($match[5]);
$authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6]) ? $match[6] : null); $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6]) ? $match[6] : null);

@ -12,34 +12,76 @@
namespace Composer\Util; namespace Composer\Util;
use stdClass;
/** /**
* Tests URLs against no_proxy patterns. * Tests URLs against NO_PROXY patterns
*/ */
class NoProxyPattern class NoProxyPattern
{ {
/** /**
* @var string[] * @var string[]
*/ */
protected $hostNames = array();
/**
* @var object[]
*/
protected $rules = array(); protected $rules = array();
/** /**
* @param string $pattern no_proxy pattern * @var bool
*/
protected $noproxy;
/**
* @param string $pattern NO_PROXY pattern
*/ */
public function __construct($pattern) public function __construct($pattern)
{ {
$this->rules = preg_split("/[\s,]+/", $pattern); $this->hostNames = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY);
$this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0];
} }
/** /**
* Test a URL against the stored pattern. * Returns true if a URL matches the NO_PROXY pattern
* *
* @param string $url * @param string $url
* *
* @return bool true if the URL matches one of the rules. * @return bool
*/ */
public function test($url) public function test($url)
{ {
$host = parse_url($url, PHP_URL_HOST); if ($this->noproxy) {
return true;
}
if (!$urlData = $this->getUrlData($url)) {
return false;
}
foreach ($this->hostNames as $index => $hostName) {
if ($this->match($index, $hostName, $urlData)) {
return true;
}
}
return false;
}
/**
* Returns false is the url cannot be parsed, otherwise a data object
*
* @param string $url
*
* @return bool|stdclass
*/
protected function getUrlData($url)
{
if (!$host = parse_url($url, PHP_URL_HOST)) {
return false;
}
$port = parse_url($url, PHP_URL_PORT); $port = parse_url($url, PHP_URL_PORT);
if (empty($port)) { if (empty($port)) {
@ -53,95 +95,341 @@ class NoProxyPattern
} }
} }
foreach ($this->rules as $rule) { $hostName = $host . ($port ? ':' . $port : '');
if ($rule == '*') { list($host, $port, $err) = $this->splitHostPort($hostName);
return true;
if ($err || !$this->ipCheckData($host, $ipdata)) {
return false;
}
return $this->makeData($host, $port, $ipdata);
}
/**
* Returns true if the url is matched by a rule
*
* @param int $index
* @param string $hostName
* @param string $url
*
* @return bool
*/
protected function match($index, $hostName, $url)
{
if (!$rule = $this->getRule($index, $hostName)) {
// Data must have been misformatted
return false;
}
if ($rule->ipdata) {
// Match ipdata first
if (!$url->ipdata) {
return false;
} }
$match = false; if ($rule->ipdata->netmask) {
return $this->matchRange($rule->ipdata, $url->ipdata);
list($ruleHost) = explode(':', $rule);
list($base) = explode('/', $ruleHost);
if (filter_var($base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
// ip or cidr match
if (!isset($ip)) {
$ip = gethostbyname($host);
}
if (strpos($ruleHost, '/') === false) {
$match = $ip === $ruleHost;
} else {
// gethostbyname() failed to resolve $host to an ip, so we assume
// it must be proxied to let the proxy's DNS resolve it
if ($ip === $host) {
$match = false;
} else {
// match resolved IP against the rule
$match = self::inCIDRBlock($ruleHost, $ip);
}
}
} else {
// match end of domain
$haystack = '.' . trim($host, '.') . '.';
$needle = '.'. trim($ruleHost, '.') .'.';
$match = stripos(strrev($haystack), strrev($needle)) === 0;
} }
// final port check $match = $rule->ipdata->ip === $url->ipdata->ip;
if ($match && strpos($rule, ':') !== false) { } else {
list(, $rulePort) = explode(':', $rule); // Match host and port
if (!empty($rulePort) && $port != $rulePort) { $haystack = substr($url->name, - strlen($rule->name));
$match = false; $match = stripos($haystack, $rule->name) === 0;
} }
if ($match && $rule->port) {
$match = $rule->port === $url->port;
}
return $match;
}
/**
* Returns true if the target ip is in the network range
*
* @param stdClass $network
* @param stdClass $target
*
* @return bool
*/
protected function matchRange(stdClass $network, stdClass $target)
{
$net = unpack('C*', $network->ip);
$mask = unpack('C*', $network->netmask);
$ip = unpack('C*', $target->ip);
for ($i = 1; $i < 17; ++$i) {
if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) {
return false;
} }
}
if ($match) { return true;
return true; }
/**
* Finds or creates rule data for a hostname
*
* @param int $index
* @param string $hostName
*
* @return {null|stdClass} Null if the hostname is invalid
*/
private function getRule($index, $hostName)
{
if (array_key_exists($index, $this->rules)) {
return $this->rules[$index];
}
$this->rules[$index] = null;
list($host, $port, $err) = $this->splitHostPort($hostName);
if ($err || !$this->ipCheckData($host, $ipdata, true)) {
return null;
}
$this->rules[$index] = $this->makeData($host, $port, $ipdata);
return $this->rules[$index];
}
/**
* Creates an object containing IP data if the host is an IP address
*
* @param string $host
* @param null|stdclass $ipdata Set by method if IP address found
* @param bool $allowPrefix Whether a CIDR prefix-length is expected
*
* @return bool False if the host contains invalid data
*/
private function ipCheckData($host, &$ipdata, $allowPrefix = false)
{
$ipdata = null;
$netmask = null;
$prefix = null;
$modified = false;
// Check for a CIDR prefix-length
if (strpos($host, '/') !== false) {
list($host, $prefix) = explode('/', $host);
if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) {
return false;
} }
$prefix = (int) $prefix;
$modified = true;
} }
return false; // See if this is an ip address
if (!filter_var($host, FILTER_VALIDATE_IP)) {
return !$modified;
}
list($ip, $size) = $this->ipGetAddr($host);
if ($prefix !== null) {
// Check for a valid prefix
if ($prefix > $size * 8) {
return false;
}
list($ip, $netmask) = $this->ipGetNetwork($ip, $size, $prefix);
}
$ipdata = $this->makeIpData($ip, $size, $netmask);
return true;
} }
/** /**
* Check an IP address against a CIDR * Returns an array of the IP in_addr and its byte size
* *
* http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php * IPv4 addresses are always mapped to IPv6, which simplifies handling
* and comparison.
* *
* @param string $cidr IPv4 block in CIDR notation * @param string $host
* @param string $ip IPv4 address
* *
* @return bool * @return mixed[] in_addr, size
*/
private function ipGetAddr($host)
{
$ip = inet_pton($host);
$size = strlen($ip);
$mapped = $this->ipMapTo6($ip, $size);
return array($mapped, $size);
}
/**
* Returns the binary network mask mapped to IPv6
*
* @param string $prefix CIDR prefix-length
* @param int $size Byte size of in_addr
*
* @return string
*/
private function ipGetMask($prefix, $size)
{
$mask = '';
if ($ones = floor($prefix / 8)) {
$mask = str_repeat(chr(255), $ones);
}
if ($remainder = $prefix % 8) {
$mask .= chr(0xff ^ (0xff >> $remainder));
}
$mask = str_pad($mask, $size, chr(0));
return $this->ipMapTo6($mask, $size);
}
/**
* Calculates and returns the network and mask
*
* @param string $rangeIp IP in_addr
* @param int $size Byte size of in_addr
* @param string $prefix CIDR prefix-length
*
* @return string[] network in_addr, binary mask
*/ */
private static function inCIDRBlock($cidr, $ip) private function ipGetNetwork($rangeIp, $size, $prefix)
{ {
// Get the base and the bits from the CIDR $netmask = $this->ipGetMask($prefix, $size);
list($base, $bits) = explode('/', $cidr);
// Now split it up into it's classes // Get the network from the address and mask
list($a, $b, $c, $d) = explode('.', $base); $mask = unpack('C*', $netmask);
$ip = unpack('C*', $rangeIp);
$net = '';
// Now do some bit shifting/switching to convert to ints for ($i = 1; $i < 17; ++$i) {
$i = ($a << 24) + ($b << 16) + ($c << 8) + $d; $net .= chr($ip[$i] & $mask[$i]);
$mask = $bits == 0 ? 0 : (~0 << (32 - $bits)); }
// Here's our lowest int return array($net, $netmask);
$low = $i & $mask; }
// Here's our highest int /**
$high = $i | (~$mask & 0xFFFFFFFF); * Maps an IPv4 address to IPv6
*
* @param string $binary in_addr
* @param int $size Byte size of in_addr
*
* @return string Mapped or existing in_addr
*/
private function ipMapTo6($binary, $size)
{
if ($size === 4) {
$prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2);
$binary = $prefix . $binary;
}
// Now split the ip we're checking against up into classes return $binary;
list($a, $b, $c, $d) = explode('.', $ip); }
// Now convert the ip we're checking against to an int /**
$check = ($a << 24) + ($b << 16) + ($c << 8) + $d; * Creates a rule data object
*
* @param string $host
* @param int $port
* @param null|stdclass $ipdata
*
* @return stdclass
*/
private function makeData($host, $port, $ipdata)
{
return (object) array(
'host' => $host,
'name' => '.' . ltrim($host, '.'),
'port' => $port,
'ipdata' => $ipdata,
);
}
/**
* Creates an ip data object
*
* @param string $ip in_addr
* @param int $size Byte size of in_addr
* @param null|string $netmask Network mask
*
* @return stdclass
*/
private function makeIpData($ip, $size, $netmask)
{
return (object) array(
'ip' => $ip,
'size' => $size,
'netmask' => $netmask,
);
}
/**
* Splits the hostname into host and port components
*
* @param string $hostName
*
* @return mixed[] host, port, if there was error
*/
private function splitHostPort($hostName)
{
// host, port, err
$error = array('', '', true);
$port = 0;
$ip6 = '';
// Check for square-bracket notation
if ($hostName[0] === '[') {
$index = strpos($hostName, ']');
// The smallest ip6 address is ::
if (false === $index || $index < 3) {
return $error;
}
$ip6 = substr($hostName, 1, $index - 1);
$hostName = substr($hostName, $index + 1);
if (strpbrk($hostName, '[]') !== false
|| substr_count($hostName, ':') > 1) {
return $error;
}
}
if (substr_count($hostName, ':') === 1) {
$index = strpos($hostName, ':');
$port = substr($hostName, $index + 1);
$hostName = substr($hostName, 0, $index);
if (!$this->validateInt($port, 1, 65535)) {
return $error;
}
$port = (int) $port;
}
$host = $ip6 . $hostName;
return array($host, $port, false);
}
/**
* Wrapper around filter_var FILTER_VALIDATE_INT
*
* @param string $int
* @param int $min
* @param int $max
*/
private function validateInt($int, $min, $max)
{
$options = array(
'options' => array(
'min_range' => $min,
'max_range' => $max)
);
// If the ip is within the range, including highest/lowest values, return false !== filter_var($int, FILTER_VALIDATE_INT, $options);
// then it's within the CIDR range
return $check >= $low && $check <= $high;
} }
} }

@ -112,10 +112,18 @@ class ProcessExecutor
return; return;
} }
if (Process::ERR === $type) { if (method_exists($this->io, 'writeRaw')) {
$this->io->writeError($buffer, false); if (Process::ERR === $type) {
$this->io->writeErrorRaw($buffer, false);
} else {
$this->io->writeRaw($buffer, false);
}
} else { } else {
$this->io->write($buffer, false); if (Process::ERR === $type) {
$this->io->writeError($buffer, false);
} else {
$this->io->write($buffer, false);
}
} }
} }

@ -250,6 +250,43 @@ class EventDispatcherTest extends TestCase
$this->assertEquals($expected, $io->getOutput()); $this->assertEquals($expected, $io->getOutput());
} }
public function testDispatcherCanPutEnv()
{
$process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->createComposerInstance(),
$io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
$process,
))
->setMethods(array(
'getListeners',
))
->getMock();
$listeners = array(
'@putenv ABC=123',
'Composer\\Test\\EventDispatcher\\EventDispatcherTest::getTestEnv',
);
$dispatcher->expects($this->atLeastOnce())
->method('getListeners')
->will($this->returnValue($listeners));
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
$expected = '> post-install-cmd: @putenv ABC=123'.PHP_EOL.
'> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::getTestEnv'.PHP_EOL;
$this->assertEquals($expected, $io->getOutput());
}
static public function getTestEnv() {
$val = getenv('ABC');
if ($val !== '123') {
throw new \Exception('getenv() did not return the expected value. expected 123 got '. var_export($val, true));
}
}
public function testDispatcherCanExecuteComposerScriptGroups() public function testDispatcherCanExecuteComposerScriptGroups()
{ {
$process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();

@ -0,0 +1,143 @@
<?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\Util;
use Composer\Util\NoProxyPattern;
use PHPUnit\Framework\TestCase;
class NoProxyPatternTest extends TestCase
{
/**
* @dataProvider dataHostName
*/
public function testHostName($noproxy, $url, $expected)
{
$matcher = new NoProxyPattern($noproxy);
$url = $this->getUrl($url);
$this->assertEquals($expected, $matcher->test($url));
}
public function dataHostName()
{
$noproxy = 'foobar.com, .barbaz.net';
// noproxy, url, expected
return array(
'match as foobar.com' => array($noproxy, 'foobar.com', true),
'match foobar.com' => array($noproxy, 'www.foobar.com', true),
'no match foobar.com' => array($noproxy, 'foofoobar.com', false),
'match .barbaz.net 1' => array($noproxy, 'barbaz.net', true),
'match .barbaz.net 2' => array($noproxy, 'www.barbaz.net', true),
'no match .barbaz.net' => array($noproxy, 'barbarbaz.net', false),
'no match wrong domain' => array($noproxy, 'barbaz.com', false),
'no match FQDN' => array($noproxy, 'foobar.com.', false),
);
}
/**
* @dataProvider dataIpAddress
*/
public function testIpAddress($noproxy, $url, $expected)
{
$matcher = new NoProxyPattern($noproxy);
$url = $this->getUrl($url);
$this->assertEquals($expected, $matcher->test($url));
}
public function dataIpAddress()
{
$noproxy = '192.168.1.1, 2001:db8::52:0:1';
// noproxy, url, expected
return array(
'match exact IPv4' => array($noproxy, '192.168.1.1', true),
'no match IPv4' => array($noproxy, '192.168.1.4', false),
'match exact IPv6' => array($noproxy, '[2001:db8:0:0:0:52:0:1]', true),
'no match IPv6' => array($noproxy, '[2001:db8:0:0:0:52:0:2]', false),
'match mapped IPv4' => array($noproxy, '[::FFFF:C0A8:0101]', true),
'no match mapped IPv4' => array($noproxy, '[::FFFF:C0A8:0104]', false),
);
}
/**
* @dataProvider dataIpRange
*/
public function testIpRange($noproxy, $url, $expected)
{
$matcher = new NoProxyPattern($noproxy);
$url = $this->getUrl($url);
$this->assertEquals($expected, $matcher->test($url));
}
public function dataIpRange()
{
$noproxy = '10.0.0.0/30, 2002:db8:a::45/121';
// noproxy, url, expected
return array(
'match IPv4/CIDR' => array($noproxy, '10.0.0.2', true),
'no match IPv4/CIDR' => array($noproxy, '10.0.0.4', false),
'match IPv6/CIDR' => array($noproxy, '[2002:db8:a:0:0:0:0:7f]', true),
'no match IPv6' => array($noproxy, '[2002:db8:a:0:0:0:0:ff]', false),
'match mapped IPv4' => array($noproxy, '[::FFFF:0A00:0002]', true),
'no match mapped IPv4' => array($noproxy, '[::FFFF:0A00:0004]', false),
);
}
/**
* @dataProvider dataPort
*/
public function testPort($noproxy, $url, $expected)
{
$matcher = new NoProxyPattern($noproxy);
$url = $this->getUrl($url);
$this->assertEquals($expected, $matcher->test($url));
}
public function dataPort()
{
$noproxy = '192.168.1.2:81, 192.168.1.3:80, [2001:db8::52:0:2]:443, [2001:db8::52:0:3]:80';
// noproxy, url, expected
return array(
'match IPv4 port' => array($noproxy, '192.168.1.3', true),
'no match IPv4 port' => array($noproxy, '192.168.1.2', false),
'match IPv6 port' => array($noproxy, '[2001:db8::52:0:3]', true),
'no match IPv6 port' => array($noproxy, '[2001:db8::52:0:2]', false),
);
}
/**
* Appends a scheme to the test url if it is missing
*
* @param string $url
*/
private function getUrl($url)
{
if (parse_url($url, PHP_URL_SCHEME)) {
return $url;
}
$scheme = 'http';
if (strpos($url, '[') !== 0 && strrpos($url, ':') !== false) {
list(, $port) = explode(':', $url);
if ($port === '443') {
$scheme = 'https';
}
}
return sprintf('%s://%s', $scheme, $url);
}
}

@ -12,9 +12,14 @@
namespace Composer\Test\Util; namespace Composer\Test\Util;
use Composer\IO\ConsoleIO;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\IO\BufferIO; use Composer\IO\BufferIO;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Output\StreamOutput;
class ProcessExecutorTest extends TestCase class ProcessExecutorTest extends TestCase
@ -99,4 +104,13 @@ class ProcessExecutorTest extends TestCase
$this->assertEquals(array('foo', 'bar'), $process->splitLines("foo\r\nbar")); $this->assertEquals(array('foo', 'bar'), $process->splitLines("foo\r\nbar"));
$this->assertEquals(array('foo', 'bar'), $process->splitLines("foo\r\nbar\n")); $this->assertEquals(array('foo', 'bar'), $process->splitLines("foo\r\nbar\n"));
} }
public function testConsoleIODoesNotFormatSymfonyConsoleStyle()
{
$output = new BufferedOutput(OutputInterface::VERBOSITY_NORMAL, true);
$process = new ProcessExecutor(new ConsoleIO(new ArrayInput([]), $output, new HelperSet([])));
$process->execute('echo \'<error>foo</error>\'');
$this->assertSame('<error>foo</error>'.PHP_EOL, $output->fetch());
}
} }

Loading…
Cancel
Save