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. diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php new file mode 100644 index 000000000..a5e74d802 --- /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) { + if ($rule == '*') { + return true; + } + + $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 { + $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)) {