[HttpClient] fix using proxies with NativeHttpClient

This commit is contained in:
Nicolas Grekas 2020-10-01 20:00:40 +02:00
parent abe0ecac91
commit 28f301bf03
1 changed files with 42 additions and 17 deletions

View File

@ -171,7 +171,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
[$host, $port, $url['authority']] = self::dnsResolve($url, $this->multi, $info, $onProgress);
[$host, $port] = self::parseHostPort($url, $info);
if (!isset($options['normalized_headers']['host'])) {
$options['headers'][] = 'Host: '.$host.$port;
@ -198,7 +198,6 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
'follow_location' => false, // We follow redirects ourselves - the native logic is too limited
],
'ssl' => array_filter([
'peer_name' => $host,
'verify_peer' => $options['verify_peer'],
'verify_peer_name' => $options['verify_host'],
'cafile' => $options['cafile'],
@ -225,7 +224,11 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$resolveRedirect = self::createRedirectResolver($options, $host, $proxy, $noProxy, $info, $onProgress);
$context = stream_context_create($context, ['notification' => $notification]);
self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, $noProxy, 'https:' === $url['scheme']);
if (!self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, $noProxy, 'https:' === $url['scheme'])) {
$ip = self::dnsResolve($host, $this->multi, $info, $onProgress);
$url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host));
}
return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolveRedirect, $onProgress, $this->logger);
}
@ -306,9 +309,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
}
/**
* Resolves the IP of the host using the local DNS cache if possible.
* Extracts the host and the port from the URL.
*/
private static function dnsResolve(array $url, NativeClientState $multi, array &$info, ?\Closure $onProgress): array
private static function parseHostPort(array $url, array &$info): array
{
if ($port = parse_url($url['authority'], \PHP_URL_PORT) ?: '') {
$info['primary_port'] = $port;
@ -317,8 +320,14 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$info['primary_port'] = 'http:' === $url['scheme'] ? 80 : 443;
}
$host = parse_url($url['authority'], \PHP_URL_HOST);
return [parse_url($url['authority'], \PHP_URL_HOST), $port];
}
/**
* Resolves the IP of the host using the local DNS cache if possible.
*/
private static function dnsResolve($host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string
{
if (null === $ip = $multi->dnsCache[$host] ?? null) {
$info['debug'] .= "* Hostname was NOT found in DNS cache\n";
$now = microtime(true);
@ -341,7 +350,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$onProgress();
}
return [$host, $port, substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host))];
return $ip;
}
/**
@ -404,24 +413,33 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
}
}
[$host, $port, $url['authority']] = self::dnsResolve($url, $multi, $info, $onProgress);
stream_context_set_option($context, 'ssl', 'peer_name', $host);
[$host, $port] = self::parseHostPort($url, $info);
if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) {
// Authorization and Cookie headers MUST NOT follow except for the initial host name
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
$requestHeaders[] = 'Host: '.$host.$port;
self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, $noProxy, 'https:' === $url['scheme']);
$dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, $noProxy, 'https:' === $url['scheme']);
} else {
$dnsResolve = isset(stream_context_get_options($context)['ssl']['peer_name']);
}
if ($dnsResolve) {
$ip = self::dnsResolve($host, $multi, $info, $onProgress);
$url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host));
}
return implode('', $url);
};
}
private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, array $noProxy, bool $isSsl)
private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, array $noProxy, bool $isSsl): bool
{
if (null === $proxy) {
return stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'ssl', 'peer_name', $host);
return false;
}
// Matching "no_proxy" should follow the behavior of curl
@ -430,17 +448,24 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$dotRule = '.'.ltrim($rule, '.');
if ('*' === $rule || $host === $rule || substr($host, -\strlen($dotRule)) === $dotRule) {
return stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'http', 'proxy', null);
stream_context_set_option($context, 'http', 'request_fulluri', false);
stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'ssl', 'peer_name', $host);
return false;
}
}
stream_context_set_option($context, 'http', 'proxy', $proxy['url']);
stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl);
if (null !== $proxy['auth']) {
$requestHeaders[] = 'Proxy-Authorization: '.$proxy['auth'];
}
return stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'http', 'proxy', $proxy['url']);
stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl);
stream_context_set_option($context, 'http', 'header', $requestHeaders);
stream_context_set_option($context, 'ssl', 'peer_name', null);
return true;
}
}