diff --git a/src/Client.php b/src/Client.php index 13c859bcc..f2bf20444 100644 --- a/src/Client.php +++ b/src/Client.php @@ -215,37 +215,9 @@ private function buildUri($uri, array $config) $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); } - if ($uri->getHost() && isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion']; - - $idnaVariant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; - $asciiHost = idn_to_ascii($uri->getHost(), $idnOptions, $idnaVariant, $info); - if ($asciiHost === false) { - $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; - - $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { - return substr($name, 0, 11) === 'IDNA_ERROR_'; - }); - - $errors = []; - foreach ($errorConstants as $errorConstant) { - if ($errorBitSet & constant($errorConstant)) { - $errors[] = $errorConstant; - } - } - - $errorMessage = 'IDN conversion failed'; - if ($errors) { - $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; - } - - throw new InvalidArgumentException($errorMessage); - } else { - if ($uri->getHost() !== $asciiHost) { - // Replace URI only if the ASCII version is different - $uri = $uri->withHost($asciiHost); - } - } + $uri = _idn_uri_convert($uri, $idnOptions); } return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; diff --git a/src/RedirectMiddleware.php b/src/RedirectMiddleware.php index 5a0edd572..2f3012618 100644 --- a/src/RedirectMiddleware.php +++ b/src/RedirectMiddleware.php @@ -13,7 +13,7 @@ * Request redirect middleware. * * Apply this middleware like other middleware using - * {@see GuzzleHttp\Middleware::redirect()}. + * {@see \GuzzleHttp\Middleware::redirect()}. */ class RedirectMiddleware { @@ -190,7 +190,13 @@ public function modifyRequest( $modify['body'] = ''; } - $modify['uri'] = $this->redirectUri($request, $response, $protocols); + $uri = $this->redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion']; + $uri = _idn_uri_convert($uri, $idnOptions); + } + + $modify['uri'] = $uri; Psr7\rewind_body($request); // Add the Referer header if it is told to do so and only diff --git a/src/functions.php b/src/functions.php index aff69d557..1924d6d33 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,10 +1,12 @@ getHost()) { + $idnaVariant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; + $asciiHost = idn_to_ascii($uri->getHost(), $options, $idnaVariant, $info); + if ($asciiHost === false) { + $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; + } + + throw new InvalidArgumentException($errorMessage); + } else { + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + } + + return $uri; +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 771c5e6cb..bc4ae33b1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,11 +5,13 @@ use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\RequestOptions; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; @@ -791,4 +793,36 @@ public function testIdnBaseUri() self::assertSame('http://xn--d1acpjx3f.xn--p1ai/baz', (string) $mock->getLastRequest()->getUri()); self::assertSame('xn--d1acpjx3f.xn--p1ai', (string) $mock->getLastRequest()->getHeaderLine('Host')); } + + public function testIdnWithRedirect() + { + if (!extension_loaded('intl')) { + self::markTestSkipped('intl PHP extension is not loaded'); + } + $mockHandler = new MockHandler([ + new Response(302, ['Location' => 'http://www.tést.com/whatever']), + new Response() + ]); + $handler = HandlerStack::create($mockHandler); + $requests = []; + $handler->push(Middleware::history($requests)); + $client = new Client(['handler' => $handler]); + + $client->request('GET', 'https://яндекс.рф/images', [ + RequestOptions::ALLOW_REDIRECTS => [ + 'referer' => true, + 'track_redirects' => true + ], + 'idn_conversion' => true + ]); + + $request = $mockHandler->getLastRequest(); + + self::assertSame('http://www.xn--tst-bma.com/whatever', (string) $request->getUri()); + self::assertSame('www.xn--tst-bma.com', (string) $request->getHeaderLine('Host')); + + $request = $requests[0]['request']; + self::assertSame('https://xn--d1acpjx3f.xn--p1ai/images', (string) $request->getUri()); + self::assertSame('xn--d1acpjx3f.xn--p1ai', (string) $request->getHeaderLine('Host')); + } } diff --git a/tests/functionsTest.php b/tests/functionsTest.php index ba93f864d..e07b558e8 100644 --- a/tests/functionsTest.php +++ b/tests/functionsTest.php @@ -133,6 +133,17 @@ public function testCurrentTime() { self::assertGreaterThan(0, GuzzleHttp\_current_time()); } + + public function testIdnConvert() + { + if (!extension_loaded('intl')) { + self::markTestSkipped('intl PHP extension is not loaded'); + } + + $uri = GuzzleHttp\Psr7\uri_for('https://яндекс.рф/images'); + $uri = GuzzleHttp\_idn_uri_convert($uri); + self::assertSame('xn--d1acpjx3f.xn--p1ai', $uri->getHost()); + } } final class StrClass