diff --git a/composer.lock b/composer.lock
index dec364ac4..be13e4253 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "composer/ca-bundle",
- "version": "1.2.4",
+ "version": "1.2.6",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527"
+ "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
- "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e",
+ "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e",
"shasum": ""
},
"require": {
@@ -28,7 +28,7 @@
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
"psr/log": "^1.0",
- "symfony/process": "^2.5 || ^3.0 || ^4.0"
+ "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
},
"type": "library",
"extra": {
@@ -46,28 +46,34 @@
"MIT"
],
"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",
- "version": "1.5.0",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
- "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
+ "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
- "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
+ "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
+ "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "^4.5 || ^5.0.5",
- "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
+ "phpunit/phpunit": "^4.5 || ^5.0.5"
},
"type": "library",
"extra": {
@@ -85,7 +91,13 @@
"MIT"
],
"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",
@@ -387,16 +399,16 @@
},
{
"name": "seld/phar-utils",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/phar-utils.git",
- "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a"
+ "reference": "84715761c35808076b00908a20317a3a8a67d17e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a",
- "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a",
+ "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/84715761c35808076b00908a20317a3a8a67d17e",
+ "reference": "84715761c35808076b00908a20317a3a8a67d17e",
"shasum": ""
},
"require": {
@@ -418,7 +430,10 @@
"MIT"
],
"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",
@@ -486,7 +501,7 @@
},
{
"name": "symfony/debug",
- "version": "v2.8.50",
+ "version": "v2.8.52",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
@@ -651,16 +666,16 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.12.0",
+ "version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
+ "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
- "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+ "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"shasum": ""
},
"require": {
@@ -672,7 +687,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.12-dev"
+ "dev-master": "1.13-dev"
}
},
"autoload": {
@@ -689,20 +704,26 @@
],
"description": "Symfony polyfill for ctype functions",
"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",
- "version": "v1.12.0",
+ "version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+ "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
- "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
+ "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
"shasum": ""
},
"require": {
@@ -714,7 +735,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.12-dev"
+ "dev-master": "1.13-dev"
}
},
"autoload": {
@@ -731,7 +752,14 @@
],
"description": "Symfony polyfill for the Mbstring extension",
"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",
diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md
index cc5ac5459..e1faa6318 100644
--- a/doc/01-basic-usage.md
+++ b/doc/01-basic-usage.md
@@ -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.
After adding the [`autoload`](04-schema.md#autoload) field, you have to re-run
-this command :
+this command :
```sh
php composer.phar dump-autoload
```
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
the return value of the include call in a variable and add more namespaces.
diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md
index 1185bd689..751d3c492 100644
--- a/doc/articles/scripts.md
+++ b/doc/articles/scripts.md
@@ -342,6 +342,21 @@ JSON array of commands.
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.
+## 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.
You can set custom script descriptions with the following in your `composer.json`:
diff --git a/res/composer-schema.json b/res/composer-schema.json
index bd04c5d5f..c83109151 100644
--- a/res/composer-schema.json
+++ b/res/composer-schema.json
@@ -41,7 +41,7 @@
"version": {
"type": "string",
"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": {
"type": "string",
diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php
index 3bcc665dc..d953809a7 100644
--- a/src/Composer/Command/DiagnoseCommand.php
+++ b/src/Composer/Command/DiagnoseCommand.php
@@ -575,6 +575,13 @@ EOT
$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)) {
foreach ($errors as $error => $current) {
switch ($error) {
@@ -684,6 +691,11 @@ EOT
$text .= " xdebug.profiler_enabled = 0";
$displayIniMessage = true;
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');
}
diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php
index 35f01eb68..47263c28a 100644
--- a/src/Composer/Downloader/SvnDownloader.php
+++ b/src/Composer/Downloader/SvnDownloader.php
@@ -138,11 +138,11 @@ class SvnDownloader extends VcsDownloader
$this->io->writeError(sprintf(' The package has modified file%s:', $countChanges === 1 ? '' : 's'));
$this->io->writeError(array_slice($changes, 0, 10));
if ($countChanges > 10) {
- $remaingChanges = $countChanges - 10;
+ $remainingChanges = $countChanges - 10;
$this->io->writeError(
sprintf(
- ' '.$remaingChanges.' more file%s modified, choose "v" to view the full list',
- $remaingChanges === 1 ? '' : 's'
+ ' '.$remainingChanges.' more file%s modified, choose "v" to view the full list',
+ $remainingChanges === 1 ? '' : 's'
)
);
}
diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php
index f4e54611b..1a5b5b5e8 100644
--- a/src/Composer/EventDispatcher/EventDispatcher.php
+++ b/src/Composer/EventDispatcher/EventDispatcher.php
@@ -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);
} else {
$finder = new PhpExecutableFinder();
@@ -492,7 +496,7 @@ class EventDispatcher
*/
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);
}
/**
diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php
index 09d9d1663..c62412ea6 100644
--- a/src/Composer/IO/BaseIO.php
+++ b/src/Composer/IO/BaseIO.php
@@ -64,6 +64,22 @@ abstract class BaseIO implements IOInterface
$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.
*
diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php
index 8b29177d5..925a528be 100644
--- a/src/Composer/IO/ConsoleIO.php
+++ b/src/Composer/IO/ConsoleIO.php
@@ -129,13 +129,29 @@ class ConsoleIO extends BaseIO
$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 bool $newline
* @param bool $stderr
* @param int $verbosity
*/
- private function doWrite($messages, $newline, $stderr, $verbosity)
+ private function doWrite($messages, $newline, $stderr, $verbosity, $raw = false)
{
$sfVerbosity = $this->verbosityMap[$verbosity];
if ($sfVerbosity > $this->output->getVerbosity()) {
@@ -149,6 +165,14 @@ class ConsoleIO extends BaseIO
$sfVerbosity = OutputInterface::OUTPUT_NORMAL;
}
+ if ($raw) {
+ if ($sfVerbosity === OutputInterface::OUTPUT_NORMAL) {
+ $sfVerbosity = OutputInterface::OUTPUT_RAW;
+ } else {
+ $sfVerbosity |= OutputInterface::OUTPUT_RAW;
+ }
+ }
+
if (null !== $this->startTime) {
$memoryUsage = memory_get_usage() / 1024 / 1024;
$timeSpent = microtime(true) - $this->startTime;
diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php
index 420f866f0..25b1b21b0 100644
--- a/src/Composer/Repository/VcsRepository.php
+++ b/src/Composer/Repository/VcsRepository.php
@@ -49,6 +49,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
/** @var VersionCacheInterface */
private $versionCache;
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)
{
@@ -131,6 +132,11 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
return $this->emptyReferences;
}
+ public function getVersionTransportExceptions()
+ {
+ return $this->versionTransportExceptions;
+ }
+
protected function initialize()
{
parent::initialize();
@@ -232,11 +238,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
$this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
} catch (\Exception $e) {
- if ($e instanceof TransportException && $e->getCode() === 404) {
- $this->emptyReferences[] = $identifier;
+ if ($e instanceof TransportException) {
+ $this->versionTransportExceptions['tags'][$tag] = $e;
+ if ($e->getCode() === 404) {
+ $this->emptyReferences[] = $identifier;
+ }
}
if ($isVeryVerbose) {
- $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'');
+ $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'');
}
continue;
}
@@ -312,11 +321,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
}
$this->addPackage($package);
} catch (TransportException $e) {
+ $this->versionTransportExceptions['branches'][$branch] = $e;
if ($e->getCode() === 404) {
$this->emptyReferences[] = $identifier;
}
if ($isVeryVerbose) {
- $this->io->writeError('Skipped branch '.$branch.', no composer file was found');
+ $this->io->writeError('Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)');
}
continue;
} catch (\Exception $e) {
diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php
index 8cf6241a6..3681ad5c7 100644
--- a/src/Composer/Util/Hg.php
+++ b/src/Composer/Util/Hg.php
@@ -53,7 +53,7 @@ class Hg
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])) {
$auth = $this->io->getAuthentication($match[5]);
$authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6]) ? $match[6] : null);
diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php
index a6cb112be..adf60f0f7 100644
--- a/src/Composer/Util/NoProxyPattern.php
+++ b/src/Composer/Util/NoProxyPattern.php
@@ -12,34 +12,76 @@
namespace Composer\Util;
+use stdClass;
+
/**
- * Tests URLs against no_proxy patterns.
+ * Tests URLs against NO_PROXY patterns
*/
class NoProxyPattern
{
/**
* @var string[]
*/
+ protected $hostNames = array();
+
+ /**
+ * @var object[]
+ */
protected $rules = array();
/**
- * @param string $pattern no_proxy pattern
+ * @var bool
+ */
+ protected $noproxy;
+
+ /**
+ * @param string $pattern NO_PROXY 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
*
- * @return bool true if the URL matches one of the rules.
+ * @return bool
*/
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);
if (empty($port)) {
@@ -53,95 +95,341 @@ class NoProxyPattern
}
}
- foreach ($this->rules as $rule) {
- if ($rule == '*') {
- return true;
- }
+ $hostName = $host . ($port ? ':' . $port : '');
+ list($host, $port, $err) = $this->splitHostPort($hostName);
- $match = false;
-
- 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
- if ($match && strpos($rule, ':') !== false) {
- list(, $rulePort) = explode(':', $rule);
- if (!empty($rulePort) && $port != $rulePort) {
- $match = false;
- }
- }
-
- if ($match) {
- return true;
- }
+ if ($err || !$this->ipCheckData($host, $ipdata)) {
+ return false;
}
- return false;
+ return $this->makeData($host, $port, $ipdata);
}
/**
- * Check an IP address against a CIDR
+ * Returns true if the url is matched by a rule
*
- * http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php
- *
- * @param string $cidr IPv4 block in CIDR notation
- * @param string $ip IPv4 address
+ * @param int $index
+ * @param string $hostName
+ * @param string $url
*
* @return bool
*/
- private static function inCIDRBlock($cidr, $ip)
+ protected function match($index, $hostName, $url)
{
- // Get the base and the bits from the CIDR
- list($base, $bits) = explode('/', $cidr);
+ if (!$rule = $this->getRule($index, $hostName)) {
+ // Data must have been misformatted
+ return false;
+ }
- // Now split it up into it's classes
- list($a, $b, $c, $d) = explode('.', $base);
+ if ($rule->ipdata) {
+ // Match ipdata first
+ if (!$url->ipdata) {
+ return false;
+ }
- // Now do some bit shifting/switching to convert to ints
- $i = ($a << 24) + ($b << 16) + ($c << 8) + $d;
- $mask = $bits == 0 ? 0 : (~0 << (32 - $bits));
+ if ($rule->ipdata->netmask) {
+ return $this->matchRange($rule->ipdata, $url->ipdata);
+ }
- // Here's our lowest int
- $low = $i & $mask;
+ $match = $rule->ipdata->ip === $url->ipdata->ip;
+ } else {
+ // Match host and port
+ $haystack = substr($url->name, - strlen($rule->name));
+ $match = stripos($haystack, $rule->name) === 0;
+ }
- // Here's our highest int
- $high = $i | (~$mask & 0xFFFFFFFF);
+ if ($match && $rule->port) {
+ $match = $rule->port === $url->port;
+ }
- // Now split the ip we're checking against up into classes
- list($a, $b, $c, $d) = explode('.', $ip);
+ return $match;
+ }
- // Now convert the ip we're checking against to an int
- $check = ($a << 24) + ($b << 16) + ($c << 8) + $d;
+ /**
+ * 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);
- // If the ip is within the range, including highest/lowest values,
- // then it's within the CIDR range
- return $check >= $low && $check <= $high;
+ for ($i = 1; $i < 17; ++$i) {
+ if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) {
+ return false;
+ }
+ }
+
+ 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;
+ }
+
+ // 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;
+ }
+
+ /**
+ * Returns an array of the IP in_addr and its byte size
+ *
+ * IPv4 addresses are always mapped to IPv6, which simplifies handling
+ * and comparison.
+ *
+ * @param string $host
+ *
+ * @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 function ipGetNetwork($rangeIp, $size, $prefix)
+ {
+ $netmask = $this->ipGetMask($prefix, $size);
+
+ // Get the network from the address and mask
+ $mask = unpack('C*', $netmask);
+ $ip = unpack('C*', $rangeIp);
+ $net = '';
+
+ for ($i = 1; $i < 17; ++$i) {
+ $net .= chr($ip[$i] & $mask[$i]);
+ }
+
+ return array($net, $netmask);
+ }
+
+ /**
+ * 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;
+ }
+
+ return $binary;
+ }
+
+ /**
+ * 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)
+ );
+
+ return false !== filter_var($int, FILTER_VALIDATE_INT, $options);
}
}
diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php
index 00b2e7547..83f19cf2d 100644
--- a/src/Composer/Util/ProcessExecutor.php
+++ b/src/Composer/Util/ProcessExecutor.php
@@ -112,10 +112,18 @@ class ProcessExecutor
return;
}
- if (Process::ERR === $type) {
- $this->io->writeError($buffer, false);
+ if (method_exists($this->io, 'writeRaw')) {
+ if (Process::ERR === $type) {
+ $this->io->writeErrorRaw($buffer, false);
+ } else {
+ $this->io->writeRaw($buffer, false);
+ }
} else {
- $this->io->write($buffer, false);
+ if (Process::ERR === $type) {
+ $this->io->writeError($buffer, false);
+ } else {
+ $this->io->write($buffer, false);
+ }
}
}
diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
index a41d745ff..804008259 100644
--- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
+++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
@@ -250,6 +250,43 @@ class EventDispatcherTest extends TestCase
$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()
{
$process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
diff --git a/tests/Composer/Test/Util/NoProxyPatternTest.php b/tests/Composer/Test/Util/NoProxyPatternTest.php
new file mode 100644
index 000000000..a3c01546c
--- /dev/null
+++ b/tests/Composer/Test/Util/NoProxyPatternTest.php
@@ -0,0 +1,143 @@
+
+ * Jordi Boggiano
+ *
+ * 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);
+ }
+}
diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php
index db16b8c02..29723b4a5 100644
--- a/tests/Composer/Test/Util/ProcessExecutorTest.php
+++ b/tests/Composer/Test/Util/ProcessExecutorTest.php
@@ -12,9 +12,14 @@
namespace Composer\Test\Util;
+use Composer\IO\ConsoleIO;
use Composer\Util\ProcessExecutor;
use Composer\Test\TestCase;
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;
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\n"));
}
+
+ public function testConsoleIODoesNotFormatSymfonyConsoleStyle()
+ {
+ $output = new BufferedOutput(OutputInterface::VERBOSITY_NORMAL, true);
+ $process = new ProcessExecutor(new ConsoleIO(new ArrayInput([]), $output, new HelperSet([])));
+
+ $process->execute('echo \'foo\'');
+ $this->assertSame('foo'.PHP_EOL, $output->fetch());
+ }
}