From a55c9b6a88add6fd8759aec43592fe3495c8f2bd Mon Sep 17 00:00:00 2001 From: radnan Date: Wed, 19 Jun 2013 02:23:05 -0500 Subject: [PATCH 1/4] added no_proxy handler - fixes #1318 - handle no_proxy directive when building stream context - using CIDR matching from Zend library - uses parts of code provided courtesy of @hoffman --- src/Composer/Util/NoProxyPattern.php | 144 +++++++++++++++++++++ src/Composer/Util/StreamContextFactory.php | 13 ++ 2 files changed, 157 insertions(+) create mode 100644 src/Composer/Util/NoProxyPattern.php diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php new file mode 100644 index 000000000..69e52cafd --- /dev/null +++ b/src/Composer/Util/NoProxyPattern.php @@ -0,0 +1,144 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Tests URLs against no_proxy patterns. + */ +class NoProxyPattern +{ + /** + * @var string[] + */ + protected $rules = array(); + + /** + * @param string $pattern no_proxy pattern + */ + public function __construct($pattern) + { + $this->rules = preg_split("/[\s,]+/", $pattern); + } + + /** + * Test a URL against the stored pattern. + * + * @param string $url + * + * @return true if the URL matches one of the rules. + */ + public function test($url) + { + $host = parse_url($url, PHP_URL_HOST); + $port = parse_url($url, PHP_URL_PORT); + + if (empty($port)) { + switch (parse_url($url, PHP_URL_SCHEME)) { + case 'http': + $port = 80; + break; + case 'https': + $port = 443; + break; + } + } + + foreach ($this->rules as $rule) { + $match = false; + + if ($rule == '*') { + $match - true; + } else { + 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 { + $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; + } + } + + return false; + } + + /** + * Check an IP adress against a CIDR + * + * 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 + * + * @return boolean + */ + private static function inCIDRBlock($cidr, $ip) + { + // Get the base and the bits from the CIDR + list($base, $bits) = explode('/', $cidr); + + // Now split it up into it's classes + list($a, $b, $c, $d) = explode('.', $base); + + // 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)); + + // Here's our lowest int + $low = $i & $mask; + + // Here's our highest int + $high = $i | (~$mask & 0xFFFFFFFF); + + // Now split the ip we're checking against up into classes + 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; + + // If the ip is within the range, including highest/lowest values, + // then it's witin the CIDR range + if ($check >= $low && $check <= $high) { + return true; + } else { + return false; + } + } +} diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 26590ada6..1556f03fc 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -63,6 +63,19 @@ final class StreamContextFactory } $options['http']['proxy'] = $proxyURL; + + // Handle no_proxy directive + if (!empty($_SERVER['no_proxy'])) { + $host = parse_url($url, PHP_URL_HOST); + + if (!empty($host)) { + $pattern = new NoProxyPattern($_SERVER['no_proxy']); + + if ($pattern->test($url)) { + $options['http']['proxy'] = ''; + } + } + } // enabled request_fulluri unless it is explicitly disabled switch (parse_url($url, PHP_URL_SCHEME)) { From a92ceaf4fe05e989629780b1f8a517df39f85ba3 Mon Sep 17 00:00:00 2001 From: radnan Date: Wed, 19 Jun 2013 02:54:48 -0500 Subject: [PATCH 2/4] fix minor typo --- src/Composer/Util/NoProxyPattern.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php index 69e52cafd..0ace86551 100644 --- a/src/Composer/Util/NoProxyPattern.php +++ b/src/Composer/Util/NoProxyPattern.php @@ -57,7 +57,7 @@ class NoProxyPattern $match = false; if ($rule == '*') { - $match - true; + $match = true; } else { list($ruleHost) = explode(':', $rule); list($base) = explode('/', $ruleHost); From 7e584de9e84d842ab236eb96259cb745732ce203 Mon Sep 17 00:00:00 2001 From: radnan Date: Thu, 20 Jun 2013 13:38:08 -0500 Subject: [PATCH 3/4] return early if rule is * and remove one level of nesting --- src/Composer/Util/NoProxyPattern.php | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php index 0ace86551..a5e74d802 100644 --- a/src/Composer/Util/NoProxyPattern.php +++ b/src/Composer/Util/NoProxyPattern.php @@ -54,40 +54,40 @@ class NoProxyPattern } foreach ($this->rules as $rule) { + if ($rule == '*') { + return true; + } + $match = false; - if ($rule == '*') { - $match = true; - } else { - list($ruleHost) = explode(':', $rule); - list($base) = explode('/', $ruleHost); + list($ruleHost) = explode(':', $rule); + list($base) = explode('/', $ruleHost); - if (filter_var($base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - // ip or cidr match + if (filter_var($base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + // ip or cidr match - if (!isset($ip)) { - $ip = gethostbyname($host); - } + if (!isset($ip)) { + $ip = gethostbyname($host); + } - if (strpos($ruleHost, '/') === false) { - $match = $ip === $ruleHost; - } else { - $match = self::inCIDRBlock($ruleHost, $ip); - } + if (strpos($ruleHost, '/') === false) { + $match = $ip === $ruleHost; } else { - // match end of domain - - $haystack = '.' . trim($host, '.') . '.'; - $needle = '.'. trim($ruleHost, '.') .'.'; - $match = stripos(strrev($haystack), strrev($needle)) === 0; + $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; - } + // final port check + if ($match && strpos($rule, ':') !== false) { + list(, $rulePort) = explode(':', $rule); + if (!empty($rulePort) && $port != $rulePort) { + $match = false; } } From b743ea8dd4f7d99a4cbed8ec6607dac76f5fffff Mon Sep 17 00:00:00 2001 From: radnan Date: Thu, 20 Jun 2013 14:03:00 -0500 Subject: [PATCH 4/4] added documentation for no_proxy env var --- doc/03-cli.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 992808100..88f2b335a 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -417,6 +417,16 @@ some tools like git or curl will only use the lower-cased `http_proxy` version. Alternatively you can also define the git proxy using `git config --global http.proxy `. +### no_proxy + +If you are behind a proxy and would like to disable it for certain domains, you +can use the `no_proxy` env var. Simply set it to a comma separated list of +domains the proxy should *not* be used for. + +The env var accepts domains, IP addresses, and IP address blocks in CIDR +notation. You can restrict the filter to a particular port (e.g. `:80`). You +can also set it to `*` to ignore the proxy for all HTTP requests. + ### HTTP_PROXY_REQUEST_FULLURI If you use a proxy but it does not support the request_fulluri flag, then you @@ -435,7 +445,7 @@ The `COMPOSER_HOME` var allows you to change the composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects. -By default it points to `/home//.composer` on *nix, +By default it points to `/home//.composer` on \*nix, `/Users//.composer` on OSX and `C:\Users\\AppData\Roaming\Composer` on Windows.