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']);
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
$client->get('/', [
'proxy' => [
'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 (!is_array($options['proxy'])) {
$conf[CURLOPT_PROXY] = $options['proxy'];
} elseif ($scheme = $easy->request->getUri()->getScheme()) {
$host = $easy->request->getUri()->getHost();
if (!isset($options['proxy']['no'])
||!$this->isHostInProxyArea($host, $options['proxy']['no'])) {
if (isset($options['proxy'][$scheme])) {
} else {
$scheme = $easy->request->getUri()->getScheme();
if (isset($options['proxy'][$scheme])) {
$host = $easy->request->getUri()->getHost();
if (!isset($options['proxy']['no']) ||
!\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
) {
$conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
}
}
}
}
}
}
@ -423,27 +425,6 @@ class CurlFactory implements CurlFactoryInterface
$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

View File

@ -296,7 +296,14 @@ class StreamHandler
} else {
$scheme = $request->getUri()->getScheme();
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;
}
/**
* 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->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', ['*.test2.com'], true);
$this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], false);
$this->checkNoProxyForHost('http://test.test.com', ['*.test.com'], true);
$this->checkNoProxyForHost('http://test.test.com', ['*'], false);
$this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], true);
}
private function checkNoProxyForHost($url, $noProxy, $assertUseProxy)
{
$f = new Handler\CurlFactory(3);
$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']);
}else{
} else {
$this->assertArrayNotHasKey(CURLOPT_PROXY, $_SERVER['_curl']);
}
}
/**
* @expectedException \InvalidArgumentException

View File

@ -196,6 +196,17 @@ class StreamHandlerTest extends \PHPUnit_Framework_TestCase
$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()
{
$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());
}
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