[HttpClient] Don't throw InvalidArgumentException on bad Location header

This commit is contained in:
Nicolas Grekas 2019-06-04 10:18:38 +02:00
parent f6a6fb6834
commit 4acca42330
5 changed files with 49 additions and 10 deletions

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\Internal\CurlClientState; use Symfony\Component\HttpClient\Internal\CurlClientState;
use Symfony\Component\HttpClient\Internal\PushedResponse; use Symfony\Component\HttpClient\Internal\PushedResponse;
@ -392,14 +393,20 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
} }
return static function ($ch, string $location) use ($redirectHeaders) { return static function ($ch, string $location) use ($redirectHeaders) {
if ($redirectHeaders && $host = parse_url($location, PHP_URL_HOST)) { try {
$location = self::parseUrl($location);
} catch (InvalidArgumentException $e) {
return null;
}
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], PHP_URL_HOST)) {
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders); curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
} }
$url = self::parseUrl(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); $url = self::parseUrl(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
return implode('', self::resolveUrl(self::parseUrl($location), $url)); return implode('', self::resolveUrl($location, $url));
}; };
} }
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\HttpClient;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareTrait;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\Internal\NativeClientState; use Symfony\Component\HttpClient\Internal\NativeClientState;
use Symfony\Component\HttpClient\Response\NativeResponse; use Symfony\Component\HttpClient\Response\NativeResponse;
@ -352,7 +353,15 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
return null; return null;
} }
$url = self::resolveUrl(self::parseUrl($location), $info['url']); try {
$url = self::parseUrl($location);
} catch (InvalidArgumentException $e) {
$info['redirect_url'] = null;
return null;
}
$url = self::resolveUrl($url, $info['url']);
$info['redirect_url'] = implode('', $url); $info['redirect_url'] = implode('', $url);
if ($info['redirect_count'] >= $maxRedirects) { if ($info['redirect_count'] >= $maxRedirects) {

View File

@ -310,14 +310,19 @@ final class CurlResponse implements ResponseInterface
$info['redirect_url'] = null; $info['redirect_url'] = null;
if (300 <= $statusCode && $statusCode < 400 && null !== $location) { if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
$info['redirect_url'] = $resolveRedirect($ch, $location); if (null === $info['redirect_url'] = $resolveRedirect($ch, $location)) {
$url = parse_url($location ?? ':'); $options['max_redirects'] = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_MAXREDIRS, $options['max_redirects']);
} else {
$url = parse_url($location ?? ':');
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) { if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
// Populate DNS cache for redirects if needed // Populate DNS cache for redirects if needed
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), PHP_URL_SCHEME)) ? 80 : 443); $port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), PHP_URL_SCHEME)) ? 80 : 443);
curl_setopt($ch, CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]); curl_setopt($ch, CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port"; $multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
}
} }
} }

View File

@ -55,6 +55,10 @@ switch ($vars['REQUEST_URI']) {
header('Location: http://foo.example.', true, 301); header('Location: http://foo.example.', true, 301);
break; break;
case '/301/invalid':
header('Location: //?foo=bar', true, 301);
break;
case '/302': case '/302':
if (!isset($vars['HTTP_AUTHORIZATION'])) { if (!isset($vars['HTTP_AUTHORIZATION'])) {
header('Location: http://localhost:8057/', true, 302); header('Location: http://localhost:8057/', true, 302);

View File

@ -259,6 +259,20 @@ abstract class HttpClientTestCase extends TestCase
$this->assertSame($expected, $filteredHeaders); $this->assertSame($expected, $filteredHeaders);
} }
public function testInvalidRedirect()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301/invalid');
$this->assertSame(301, $response->getStatusCode());
$this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']);
$this->assertSame(0, $response->getInfo('redirect_count'));
$this->assertNull($response->getInfo('redirect_url'));
$this->expectException(RedirectionExceptionInterface::class);
$response->getHeaders();
}
public function testRelativeRedirects() public function testRelativeRedirects()
{ {
$client = $this->getHttpClient(__FUNCTION__); $client = $this->getHttpClient(__FUNCTION__);