1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-03-14 19:29:49 +01:00

Cleaning up no_proxy support

This commit is contained in:
Michael Dowling 2015-08-15 13:48:15 -07:00
parent cae1fec097
commit d687700d60
7 changed files with 136 additions and 40 deletions

View File

@ -638,14 +638,24 @@ Pass a string to specify a proxy for all protocols.
$client->get('/', ['proxy' => 'tcp://localhost:8125']); $client->get('/', ['proxy' => 'tcp://localhost:8125']);
Pass an associative array to specify HTTP proxies for specific URI schemes Pass an associative array to specify HTTP proxies for specific URI schemes
(i.e., "http", "https"). (i.e., "http", "https"). Provide a ``no`` key value pair to provide a list of
host names that should not be proxied to.
.. note::
Guzzle will automatically populate this value with your environment's
``NO_PROXY`` environment variable. However, when providing a ``proxy``
request option, it is up to your to provide the ``no`` value parsed from
the ``NO_PROXY`` environment variable
(e.g., ``explode(',', getenv('NO_PROXY'))``).
.. code-block:: php .. code-block:: php
$client->get('/', [ $client->get('/', [
'proxy' => [ 'proxy' => [
'http' => 'tcp://localhost:8125', // Use this proxy with "http" 'http' => 'tcp://localhost:8125', // Use this proxy with "http"
'https' => 'tcp://localhost:9124' // Use this proxy with "https" 'https' => 'tcp://localhost:9124', // Use this proxy with "https",
'no' => ['.mit.edu', 'foo.com'] // Don't use a proxy with these
] ]
]); ]);

View File

@ -361,14 +361,16 @@ class CurlFactory implements CurlFactoryInterface
if (isset($options['proxy'])) { if (isset($options['proxy'])) {
if (!is_array($options['proxy'])) { if (!is_array($options['proxy'])) {
$conf[CURLOPT_PROXY] = $options['proxy']; $conf[CURLOPT_PROXY] = $options['proxy'];
} elseif ($scheme = $easy->request->getUri()->getScheme()) { } else {
$host = $easy->request->getUri()->getHost(); $scheme = $easy->request->getUri()->getScheme();
if (!isset($options['proxy']['no']) if (isset($options['proxy'][$scheme])) {
||!$this->isHostInProxyArea($host, $options['proxy']['no'])) { $host = $easy->request->getUri()->getHost();
if (isset($options['proxy'][$scheme])) { if (!isset($options['proxy']['no']) ||
!\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
) {
$conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
} }
} }
} }
} }
@ -423,27 +425,6 @@ class CurlFactory implements CurlFactoryInterface
$conf[CURLOPT_VERBOSE] = true; $conf[CURLOPT_VERBOSE] = true;
} }
} }
private function isHostInProxyArea($host, array $areas)
{
$matches = false;
foreach ($areas as $area) {
$areaToMatch = $area;
// if include all subdomains
if (!empty($area) && substr($area, 0, 1) === '.'){
$areaToMatch = "*$area";
}
// Use wilcards
$delimiter = '#';
$areaToMatch = preg_quote($areaToMatch, $delimiter);
$areaToMatch = str_replace('\*', '.*', $areaToMatch);
if (preg_match("$delimiter^$areaToMatch$$delimiter", $host)) {
$matches = true;
break;
}
}
return $matches;
}
/** /**
* This function ensures that a response was set on a transaction. If one * This function ensures that a response was set on a transaction. If one

View File

@ -296,7 +296,14 @@ class StreamHandler
} else { } else {
$scheme = $request->getUri()->getScheme(); $scheme = $request->getUri()->getScheme();
if (isset($value[$scheme])) { if (isset($value[$scheme])) {
$options['http']['proxy'] = $value[$scheme]; if (!isset($value['no'])
|| !\GuzzleHttp\is_host_in_noproxy(
$request->getUri()->getHost(),
$value['no']
)
) {
$options['http']['proxy'] = $value[$scheme];
}
} }
} }
} }

View File

@ -225,3 +225,56 @@ function normalize_header_keys(array $headers)
return $result; return $result;
} }
/**
* Returns true if the provided host matches any of the no proxy areas.
*
* This method will strip a port from the host if it is present. Each pattern
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
* "baz.foo.com", but ".foo.com" != "foo.com").
*
* Areas are matched in the following cases:
* 1. "*" (without quotes) always matches any hosts.
* 2. An exact match.
* 3. The area starts with "." and the area is the last part of the host. e.g.
* '.mit.edu' will match any host that ends with '.mit.edu'.
*
* @param string $host Host to check against the patterns.
* @param array $noProxyArray An array of host patterns.
*
* @return bool
*/
function is_host_in_noproxy($host, array $noProxyArray)
{
if (strlen($host) === 0) {
throw new \InvalidArgumentException('Empty host provided');
}
// Strip port if present.
if (strpos($host, ':')) {
$host = explode($host, ':', 2)[0];
}
foreach ($noProxyArray as $area) {
// Always match on wildcards.
if ($area === '*') {
return true;
} elseif (empty($area)) {
// Don't match on empty values.
continue;
} elseif ($area === $host) {
// Exact matches.
return true;
} else {
// Special match if the area when prefixed with ".". Remove any
// existing leading "." and add a new leading ".".
$area = '.' . ltrim($area, '.');
if (substr($host, -(strlen($area))) === $area) {
return true;
}
}
}
return false;
}

View File

@ -145,25 +145,28 @@ class CurlFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]); $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]);
$this->checkNoProxyForHost('http://test.test.com', ['test.test.com'], false); $this->checkNoProxyForHost('http://test.test.com', ['test.test.com'], false);
$this->checkNoProxyForHost('http://test.test.com', ['.test.com'], false); $this->checkNoProxyForHost('http://test.test.com', ['.test.com'], false);
$this->checkNoProxyForHost('http://test.test.com', ['*.test.com'], false); $this->checkNoProxyForHost('http://test.test.com', ['*.test.com'], true);
$this->checkNoProxyForHost('http://test.test.com', ['*.test2.com'], true); $this->checkNoProxyForHost('http://test.test.com', ['*'], false);
$this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], false); $this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], true);
} }
private function checkNoProxyForHost($url, $noProxy, $assertUseProxy) private function checkNoProxyForHost($url, $noProxy, $assertUseProxy)
{ {
$f = new Handler\CurlFactory(3); $f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', $url), [ $f->create(new Psr7\Request('GET', $url), [
'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t', 'no' => $noProxy], 'proxy' => [
'http' => 'http://bar.com',
'https' => 'https://t',
'no' => $noProxy
],
]); ]);
if($assertUseProxy) { if ($assertUseProxy) {
$this->assertArrayHasKey(CURLOPT_PROXY, $_SERVER['_curl']); $this->assertArrayHasKey(CURLOPT_PROXY, $_SERVER['_curl']);
}else{ } else {
$this->assertArrayNotHasKey(CURLOPT_PROXY, $_SERVER['_curl']); $this->assertArrayNotHasKey(CURLOPT_PROXY, $_SERVER['_curl']);
} }
} }
/** /**
* @expectedException \InvalidArgumentException * @expectedException \InvalidArgumentException

View File

@ -196,6 +196,17 @@ class StreamHandlerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($url, $opts['http']['proxy']); $this->assertEquals($url, $opts['http']['proxy']);
} }
public function testAddsProxyButHonorsNoProxy()
{
$url = str_replace('http', 'tcp', Server::$url);
$res = $this->getSendResult(['proxy' => [
'http' => $url,
'no' => ['*']
]]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertTrue(empty($opts['http']['proxy']));
}
public function testAddsTimeout() public function testAddsTimeout()
{ {
$res = $this->getSendResult(['stream' => true, 'timeout' => 200]); $res = $this->getSendResult(['stream' => true, 'timeout' => 200]);

View File

@ -70,6 +70,37 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
{ {
$this->assertFileExists(GuzzleHttp\default_ca_bundle()); $this->assertFileExists(GuzzleHttp\default_ca_bundle());
} }
public function noProxyProvider()
{
return [
['mit.edu', ['.mit.edu'], false],
['foo.mit.edu', ['.mit.edu'], true],
['mit.edu', ['mit.edu'], true],
['mit.edu', ['baz', 'mit.edu'], true],
['mit.edu', ['', '', 'mit.edu'], true],
['mit.edu', ['baz', '*'], true],
];
}
/**
* @dataProvider noproxyProvider
*/
public function testChecksNoProxyList($host, $list, $result)
{
$this->assertSame(
$result,
\GuzzleHttp\is_host_in_noproxy($host, $list)
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresNoProxyCheckHostIsSet()
{
\GuzzleHttp\is_host_in_noproxy('', []);
}
} }
final class StrClass final class StrClass