1<?php 2namespace GuzzleHttp; 3 4use GuzzleHttp\Exception\InvalidArgumentException; 5use Psr\Http\Message\UriInterface; 6use Symfony\Polyfill\Intl\Idn\Idn; 7 8final class Utils 9{ 10 /** 11 * Wrapper for the hrtime() or microtime() functions 12 * (depending on the PHP version, one of the two is used) 13 * 14 * @return float|mixed UNIX timestamp 15 * 16 * @internal 17 */ 18 public static function currentTime() 19 { 20 return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); 21 } 22 23 /** 24 * @param int $options 25 * 26 * @return UriInterface 27 * @throws InvalidArgumentException 28 * 29 * @internal 30 */ 31 public static function idnUriConvert(UriInterface $uri, $options = 0) 32 { 33 if ($uri->getHost()) { 34 $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); 35 if ($asciiHost === false) { 36 $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; 37 38 $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { 39 return substr($name, 0, 11) === 'IDNA_ERROR_'; 40 }); 41 42 $errors = []; 43 foreach ($errorConstants as $errorConstant) { 44 if ($errorBitSet & constant($errorConstant)) { 45 $errors[] = $errorConstant; 46 } 47 } 48 49 $errorMessage = 'IDN conversion failed'; 50 if ($errors) { 51 $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; 52 } 53 54 throw new InvalidArgumentException($errorMessage); 55 } else { 56 if ($uri->getHost() !== $asciiHost) { 57 // Replace URI only if the ASCII version is different 58 $uri = $uri->withHost($asciiHost); 59 } 60 } 61 } 62 63 return $uri; 64 } 65 66 /** 67 * @param string $domain 68 * @param int $options 69 * @param array $info 70 * 71 * @return string|false 72 */ 73 private static function idnToAsci($domain, $options, &$info = []) 74 { 75 if (\preg_match('%^[ -~]+$%', $domain) === 1) { 76 return $domain; 77 } 78 79 if (\extension_loaded('intl') && defined('INTL_IDNA_VARIANT_UTS46')) { 80 return \idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $info); 81 } 82 83 /* 84 * The Idn class is marked as @internal. Verify that class and method exists. 85 */ 86 if (method_exists(Idn::class, 'idn_to_ascii')) { 87 return Idn::idn_to_ascii($domain, $options, Idn::INTL_IDNA_VARIANT_UTS46, $info); 88 } 89 90 throw new \RuntimeException('ext-intl or symfony/polyfill-intl-idn not loaded or too old'); 91 } 92} 93