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