From 19964371e8da95dfe021a226c8c4ddcc5c775052 Mon Sep 17 00:00:00 2001 From: Mponos George Date: Wed, 11 Dec 2019 19:52:13 +0200 Subject: [PATCH 1/3] Fix issue 2423 and issue 2448 --- src/Client.php | 31 ++----------------------- src/RedirectMiddleware.php | 10 +++++++-- src/functions.php | 46 ++++++++++++++++++++++++++++++++++++++ tests/ClientTest.php | 34 ++++++++++++++++++++++++++++ tests/functionsTest.php | 11 +++++++++ 5 files changed, 101 insertions(+), 31 deletions(-) diff --git a/src/Client.php b/src/Client.php index db4062f94..fa9fc7a3c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -215,36 +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']; - - $asciiHost = idn_to_ascii($uri->getHost(), $idnOptions, INTL_IDNA_VARIANT_UTS46, $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..97f9a33ff 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,10 +1,12 @@ getHost()) { + $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; + $asciiHost = idn_to_ascii($uri->getHost(), $options, $variant, $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 From f2807491d46b25125310c9f216c3e710a504c98c Mon Sep 17 00:00:00 2001 From: Mponos George Date: Wed, 11 Dec 2019 20:32:35 +0200 Subject: [PATCH 2/3] Fix idn according to suggestions from review --- src/functions.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/functions.php b/src/functions.php index 97f9a33ff..4332996b0 100644 --- a/src/functions.php +++ b/src/functions.php @@ -358,8 +358,9 @@ function _current_time() function _idn_uri_convert(UriInterface $uri, $options = 0) { if ($uri->getHost()) { - $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; - $asciiHost = idn_to_ascii($uri->getHost(), $options, $variant, $info); + $asciiHost = defined('INTL_IDNA_VARIANT_UTS46') + ? idn_to_ascii($uri->getHost(), $options, INTL_IDNA_VARIANT_UTS46, $info) + : idn_to_ascii($uri->getHost(), $options); if ($asciiHost === false) { $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; From d56612f41dce5145e88174e5cd146f6b881c8e1f Mon Sep 17 00:00:00 2001 From: Mponos George Date: Sun, 15 Dec 2019 10:08:06 +0200 Subject: [PATCH 3/3] Revert a check that is not needed --- src/functions.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/functions.php b/src/functions.php index 4332996b0..cc68dc78f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -358,9 +358,7 @@ function _current_time() function _idn_uri_convert(UriInterface $uri, $options = 0) { if ($uri->getHost()) { - $asciiHost = defined('INTL_IDNA_VARIANT_UTS46') - ? idn_to_ascii($uri->getHost(), $options, INTL_IDNA_VARIANT_UTS46, $info) - : idn_to_ascii($uri->getHost(), $options); + $asciiHost = idn_to_ascii($uri->getHost(), $options, INTL_IDNA_VARIANT_UTS46, $info); if ($asciiHost === false) { $errorBitSet = isset($info['errors']) ? $info['errors'] : 0;