PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.0
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / vendor_prefixed / guzzlehttp / guzzle / src / Utils.php

Utils.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.0, at vendor_prefixed/guzzlehttp/guzzle/src/Utils.php

674 lines 26.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YoastSEO_Vendor\GuzzleHttp;
4
5 use YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException;
6 use YoastSEO_Vendor\GuzzleHttp\Handler\CurlHandler;
7 use YoastSEO_Vendor\GuzzleHttp\Handler\CurlMultiHandler;
8 use YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState;
9 use YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion;
10 use YoastSEO_Vendor\GuzzleHttp\Handler\Proxy;
11 use YoastSEO_Vendor\GuzzleHttp\Handler\StreamHandler;
12 use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
13 use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
14 final class Utils
15 {
16 /**
17 * Debug function used to describe the provided value type and class.
18 *
19 * @param mixed $input
20 *
21 * @return string Returns a string containing the type of the variable and
22 * if a class is provided, the class name.
23 *
24 * @deprecated Utils::describeType() will be removed in guzzlehttp/guzzle:8.0. Use get_debug_type() instead.
25 */
26 public static function describeType($input) : string
27 {
28 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', '%s() is deprecated and will be removed in 8.0. Use get_debug_type() instead.', __METHOD__);
29 switch (\gettype($input)) {
30 case 'object':
31 return 'object(' . \get_class($input) . ')';
32 case 'array':
33 return 'array(' . \count($input) . ')';
34 default:
35 \ob_start();
36 \var_dump($input);
37 // normalize float vs double
38 /** @var string $varDumpContent */
39 $varDumpContent = \ob_get_clean();
40 return \str_replace('double(', 'float(', \rtrim($varDumpContent));
41 }
42 }
43 /**
44 * Parses an array of header lines into an associative array of headers.
45 *
46 * @param iterable $lines Header lines array of strings in the following
47 * format: "Name: Value"
48 */
49 public static function headersFromLines(iterable $lines) : array
50 {
51 $headers = [];
52 foreach ($lines as $line) {
53 $parts = \explode(':', $line, 2);
54 $headers[\trim($parts[0])][] = isset($parts[1]) ? \trim($parts[1]) : null;
55 }
56 return $headers;
57 }
58 /**
59 * Returns a debug stream based on the provided variable.
60 *
61 * @param mixed $value Optional value
62 *
63 * @return resource
64 */
65 public static function debugResource($value = null)
66 {
67 if (\is_resource($value)) {
68 return $value;
69 }
70 if (\defined('STDOUT')) {
71 return \STDOUT;
72 }
73 return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w');
74 }
75 /**
76 * Chooses and creates a default handler to use based on the environment.
77 *
78 * The returned handler is not wrapped by any default middlewares.
79 *
80 * @param array{transport_sharing?: mixed} $handlerOptions Handler constructor options.
81 *
82 * @return callable(RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
83 *
84 * @throws \RuntimeException if no viable Handler is available.
85 */
86 public static function chooseHandler(array $handlerOptions = []) : callable
87 {
88 $handler = null;
89 $sharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($handlerOptions['transport_sharing'] ?? null, 'transport_sharing');
90 $sharingRequested = $sharingMode !== \YoastSEO_Vendor\GuzzleHttp\TransportSharing::NONE;
91 $sharingRequired = $sharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::HANDLER_REQUIRE;
92 $curlHandlerOptions = [];
93 $curlSupported = \defined('CURLOPT_CUSTOMREQUEST') && \YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion::supportsCurlHandler() && (\function_exists('curl_multi_exec') || \function_exists('curl_exec'));
94 if ($sharingRequired && !$curlSupported) {
95 throw new \RuntimeException('Required transport sharing requires the PHP cURL extension, curl_exec() or curl_multi_exec(), and libcurl 7.21.2 or higher.');
96 }
97 if ($curlSupported) {
98 if ($sharingRequested) {
99 $shareState = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::fromOption($sharingMode);
100 if ($shareState !== null) {
101 $curlHandlerOptions['transport_sharing'] = $shareState;
102 }
103 }
104 if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
105 $handler = \YoastSEO_Vendor\GuzzleHttp\Handler\Proxy::wrapSync(new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlMultiHandler($curlHandlerOptions), new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlHandler($curlHandlerOptions));
106 } elseif (\function_exists('curl_exec')) {
107 $handler = new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlHandler($curlHandlerOptions);
108 } elseif (\function_exists('curl_multi_exec')) {
109 $handler = new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlMultiHandler($curlHandlerOptions);
110 }
111 }
112 if (\ini_get('allow_url_fopen')) {
113 $streamHandler = new \YoastSEO_Vendor\GuzzleHttp\Handler\StreamHandler(['transport_sharing' => $sharingMode]);
114 $handler = $handler ? \YoastSEO_Vendor\GuzzleHttp\Handler\Proxy::wrapStreaming($handler, $streamHandler) : $streamHandler;
115 } elseif (!$handler) {
116 throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
117 }
118 return $handler;
119 }
120 /**
121 * Get the default User-Agent string to use with Guzzle.
122 */
123 public static function defaultUserAgent() : string
124 {
125 return \sprintf('GuzzleHttp/%d', \YoastSEO_Vendor\GuzzleHttp\ClientInterface::MAJOR_VERSION);
126 }
127 /**
128 * Returns the default cacert bundle for the current system.
129 *
130 * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
131 * If those settings are not configured, then the common locations for
132 * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
133 * and Windows are checked. If any of these file locations are found on
134 * disk, they will be utilized.
135 *
136 * Note: the result of this function is cached for subsequent calls.
137 *
138 * @throws \RuntimeException if no bundle can be found.
139 *
140 * @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+.
141 */
142 public static function defaultCaBundle() : string
143 {
144 static $cached = null;
145 static $cafiles = [
146 // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
147 '/etc/pki/tls/certs/ca-bundle.crt',
148 // Ubuntu, Debian (provided by the ca-certificates package)
149 '/etc/ssl/certs/ca-certificates.crt',
150 // FreeBSD (provided by the ca_root_nss package)
151 '/usr/local/share/certs/ca-root-nss.crt',
152 // SLES 12 (provided by the ca-certificates package)
153 '/var/lib/ca-certificates/ca-bundle.pem',
154 // OS X provided by homebrew (using the default path)
155 '/usr/local/etc/openssl/cert.pem',
156 // Google app engine
157 '/etc/ca-certificates.crt',
158 // Windows?
159 'C:\\windows\\system32\\curl-ca-bundle.crt',
160 'C:\\windows\\curl-ca-bundle.crt',
161 ];
162 if ($cached) {
163 return $cached;
164 }
165 if ($ca = \ini_get('openssl.cafile')) {
166 return $cached = $ca;
167 }
168 if ($ca = \ini_get('curl.cainfo')) {
169 return $cached = $ca;
170 }
171 foreach ($cafiles as $filename) {
172 if (\file_exists($filename)) {
173 return $cached = $filename;
174 }
175 }
176 throw new \RuntimeException(<<<EOT
177 No system CA bundle could be found in any of the the common system locations.
178 PHP versions earlier than 5.6 are not properly configured to use the system's
179 CA bundle by default. In order to verify peer certificates, you will need to
180 supply the path on disk to a certificate bundle to the 'verify' request option:
181 https://github.com/guzzle/guzzle/blob/7.12/docs/request-options.md#verify. If
182 you do not need a specific certificate bundle, then Mozilla provides a commonly
183 used CA bundle which can be downloaded here (provided by the maintainer of
184 cURL): https://curl.se/ca/cacert.pem. Once you have a CA bundle available on
185 disk, you can set the 'openssl.cafile' PHP ini setting to point to the path to
186 the file, allowing you to omit the 'verify' request option. See
187 https://curl.se/docs/sslcerts.html for more information.
188 EOT
189 );
190 }
191 /**
192 * Creates an associative array of lowercase header names to the actual
193 * header casing.
194 */
195 public static function normalizeHeaderKeys(array $headers) : array
196 {
197 $result = [];
198 foreach (\array_keys($headers) as $key) {
199 $result[\strtolower((string) $key)] = $key;
200 }
201 return $result;
202 }
203 /**
204 * @param mixed $protocols
205 *
206 * @return string[]
207 *
208 * @throws InvalidArgumentException
209 */
210 public static function normalizeProtocols($protocols) : array
211 {
212 if (!\is_array($protocols) || $protocols === []) {
213 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('protocols must be a non-empty array of "http" and/or "https"');
214 }
215 $normalized = [];
216 foreach ($protocols as $protocol) {
217 if (!\is_string($protocol)) {
218 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('protocols must contain only strings');
219 }
220 if ($protocol !== 'http' && $protocol !== 'https') {
221 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('protocols may only contain "http" and "https"');
222 }
223 $normalized[$protocol] = \true;
224 }
225 return \array_keys($normalized);
226 }
227 /**
228 * Returns true if the provided host matches any of the no proxy areas.
229 *
230 * This method will strip a port from the host if it is present. Domain
231 * patterns are matched case-insensitively. Exact IP literal patterns are
232 * matched by their normalized binary address.
233 *
234 * Areas are matched in the following cases:
235 * 1. "*" (without quotes) always matches any hosts.
236 * 2. An exact domain or IP literal match.
237 * 3. A bare domain matches itself and its subdomains. e.g. 'mit.edu' will
238 * match 'mit.edu' and 'foo.mit.edu'.
239 * 4. The area starts with "." and the area is the last part of the host. e.g.
240 * '.mit.edu' will match any host that ends with '.mit.edu'.
241 * 5. IP CIDR entries match IP literal hosts. e.g. '192.168.0.0/16' will
242 * match '192.168.1.10' and 'fd00::/8' will match '[fd00::1]'.
243 *
244 * @param string $host Host to check against the patterns.
245 * @param string[] $noProxyArray An array of host or CIDR patterns.
246 *
247 * @throws InvalidArgumentException
248 */
249 public static function isHostInNoProxy(string $host, array $noProxyArray) : bool
250 {
251 if (\strlen($host) === 0) {
252 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Empty host provided');
253 }
254 $target = self::parseNoProxyHostString($host);
255 if ($target === null) {
256 return \false;
257 }
258 return self::matchesNoProxyList($target, $noProxyArray);
259 }
260 /**
261 * Returns true if the provided URI matches any of the no proxy areas.
262 *
263 * Matching follows the same rules as isHostInNoProxy(), with the
264 * addition that areas may carry a port (e.g. "example.com:8080" or
265 * "[::1]:8080") which is compared against the URI port (or the scheme
266 * default port when the URI has none).
267 *
268 * @param mixed $noProxy No-proxy host, host-and-port, or CIDR patterns.
269 *
270 * @internal
271 */
272 public static function isUriInNoProxy(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, $noProxy) : bool
273 {
274 if (\is_string($noProxy)) {
275 $noProxy = \explode(',', $noProxy);
276 }
277 if (!\is_array($noProxy)) {
278 return \false;
279 }
280 $target = self::parseNoProxyTarget($uri);
281 if ($target === null) {
282 return \false;
283 }
284 return self::matchesNoProxyList($target, $noProxy);
285 }
286 /**
287 * @param array{type: string, value: string, port: int|null, matchesRoot: bool} $target
288 * @param array<array-key, mixed> $noProxy
289 */
290 private static function matchesNoProxyList(array $target, array $noProxy) : bool
291 {
292 foreach ($noProxy as $area) {
293 if (!\is_string($area)) {
294 continue;
295 }
296 $area = \trim($area);
297 // Always match on wildcards.
298 if ($area === '*') {
299 return \true;
300 }
301 $rule = self::parseNoProxyRule($area);
302 if ($rule !== null && self::noProxyRuleMatches($target, $rule)) {
303 return \true;
304 }
305 }
306 return \false;
307 }
308 /**
309 * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
310 */
311 private static function parseNoProxyTarget(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : ?array
312 {
313 $host = $uri->getHost();
314 if ($host === '') {
315 return null;
316 }
317 return self::parseNoProxyHost($host, $uri->getPort() ?? self::getDefaultPort($uri->getScheme()), \true);
318 }
319 /**
320 * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
321 */
322 private static function parseNoProxyHostString(string $host) : ?array
323 {
324 $hostAndPort = self::splitNoProxyHostAndPort($host);
325 if ($hostAndPort === null) {
326 return null;
327 }
328 [$host] = $hostAndPort;
329 return self::parseNoProxyHost($host, null, \true);
330 }
331 /**
332 * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|array{type: string, value: string, prefix: int}|null
333 */
334 private static function parseNoProxyRule(string $area) : ?array
335 {
336 $area = \trim($area);
337 if ($area === '' || $area === '*') {
338 return null;
339 }
340 if (\strpos($area, '/') !== \false) {
341 return self::parseNoProxyCidrRule($area);
342 }
343 $matchesRoot = \true;
344 if ($area[0] === '.') {
345 $matchesRoot = \false;
346 $area = \substr($area, 1);
347 }
348 $hostAndPort = self::splitNoProxyHostAndPort($area);
349 if ($hostAndPort === null) {
350 return null;
351 }
352 [$host, $port] = $hostAndPort;
353 if ($host === '*') {
354 if (!$matchesRoot) {
355 return null;
356 }
357 return ['type' => 'wildcard', 'value' => '*', 'port' => $port, 'matchesRoot' => \true];
358 }
359 $rule = self::parseNoProxyHost($host, $port, $matchesRoot);
360 if ($rule !== null && !$matchesRoot && $rule['type'] === 'ip') {
361 return null;
362 }
363 return $rule;
364 }
365 /**
366 * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null
367 */
368 private static function parseNoProxyHost(string $host, ?int $port, bool $matchesRoot) : ?array
369 {
370 if ($host !== '' && $host[0] === '[') {
371 if (\substr($host, -1) !== ']') {
372 return null;
373 }
374 $address = \substr($host, 1, -1);
375 if (!\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
376 return null;
377 }
378 $host = $address;
379 }
380 $packedIp = self::packIpAddress($host);
381 if ($packedIp !== \false) {
382 return ['type' => 'ip', 'value' => $packedIp, 'port' => $port, 'matchesRoot' => $matchesRoot];
383 }
384 if ($host === '' || \strpos($host, ':') !== \false) {
385 return null;
386 }
387 // Normalize a single DNS root dot for no-proxy domain matching.
388 if (\substr($host, -1) === '.') {
389 $host = \substr($host, 0, -1);
390 if ($host === '') {
391 return null;
392 }
393 }
394 return ['type' => 'domain', 'value' => \strtolower($host), 'port' => $port, 'matchesRoot' => $matchesRoot];
395 }
396 /**
397 * @return array{0: string, 1: int|null}|null
398 */
399 private static function splitNoProxyHostAndPort(string $area) : ?array
400 {
401 if ($area !== '' && $area[0] === '[') {
402 $closingBracket = \strpos($area, ']');
403 if ($closingBracket === \false) {
404 return null;
405 }
406 $host = \substr($area, 0, $closingBracket + 1);
407 $tail = \substr($area, $closingBracket + 1);
408 if ($tail === '') {
409 return [$host, null];
410 }
411 if ($tail[0] !== ':') {
412 return null;
413 }
414 $port = self::parseNoProxyPort(\substr($tail, 1));
415 return $port === null ? null : [$host, $port];
416 }
417 if (self::packIpAddress($area) !== \false) {
418 return [$area, null];
419 }
420 $colon = \strrpos($area, ':');
421 if ($colon === \false) {
422 return [$area, null];
423 }
424 $port = self::parseNoProxyPort(\substr($area, $colon + 1));
425 if ($port === null) {
426 return null;
427 }
428 return [\substr($area, 0, $colon), $port];
429 }
430 private static function parseNoProxyPort(string $port) : ?int
431 {
432 return self::parseBoundedUnsignedInteger($port, 65535);
433 }
434 /**
435 * @return array{type: string, value: string, prefix: int}|null
436 */
437 private static function parseNoProxyCidrRule(string $area) : ?array
438 {
439 $slash = \strpos($area, '/');
440 if ($slash === \false) {
441 return null;
442 }
443 $prefix = \substr($area, $slash + 1);
444 $network = \substr($area, 0, $slash);
445 if ($network !== '' && $network[0] === '[' && \substr($network, -1) === ']') {
446 $network = \substr($network, 1, -1);
447 }
448 $network = self::packIpAddress($network);
449 if ($network === \false) {
450 return null;
451 }
452 $prefix = self::parseBoundedUnsignedInteger($prefix, \strlen($network) * 8);
453 if ($prefix === null) {
454 return null;
455 }
456 return ['type' => 'cidr', 'value' => $network, 'prefix' => $prefix];
457 }
458 private static function parseBoundedUnsignedInteger(string $value, int $max) : ?int
459 {
460 if ($value === '' || !\ctype_digit($value)) {
461 return null;
462 }
463 $normalized = \ltrim($value, '0');
464 $normalized = $normalized === '' ? '0' : $normalized;
465 $limit = (string) $max;
466 if (\strlen($normalized) > \strlen($limit) || \strlen($normalized) === \strlen($limit) && \strcmp($normalized, $limit) > 0) {
467 return null;
468 }
469 return (int) $normalized;
470 }
471 /**
472 * @param array{type: string, value: string, port: int|null, matchesRoot: bool} $target
473 * @param array{type: string, value: string, port?: int|null, matchesRoot?: bool, prefix?: int|null} $rule
474 */
475 private static function noProxyRuleMatches(array $target, array $rule) : bool
476 {
477 if ($rule['type'] === 'wildcard') {
478 return ($rule['port'] ?? null) === null || $rule['port'] === $target['port'];
479 }
480 if ($rule['type'] === 'cidr') {
481 if ($target['type'] !== 'ip' || !isset($rule['prefix'])) {
482 return \false;
483 }
484 if (\strlen($target['value']) !== \strlen($rule['value'])) {
485 return \false;
486 }
487 return self::ipMatchesPrefix($target['value'], $rule['value'], $rule['prefix']);
488 }
489 if (($rule['port'] ?? null) !== null && $rule['port'] !== $target['port']) {
490 return \false;
491 }
492 if ($rule['type'] !== $target['type']) {
493 return \false;
494 }
495 if ($rule['type'] === 'ip') {
496 return $rule['value'] === $target['value'];
497 }
498 if (($rule['matchesRoot'] ?? \false) && $target['value'] === $rule['value']) {
499 return \true;
500 }
501 $suffix = '.' . $rule['value'];
502 return \substr($target['value'], -\strlen($suffix)) === $suffix;
503 }
504 /**
505 * @return string|false
506 */
507 private static function packIpAddress(string $ip)
508 {
509 if (!\filter_var($ip, \FILTER_VALIDATE_IP)) {
510 return \false;
511 }
512 return \inet_pton($ip);
513 }
514 private static function ipMatchesPrefix(string $address, string $network, int $prefix) : bool
515 {
516 $fullBytes = \intdiv($prefix, 8);
517 $remainingBits = $prefix % 8;
518 if ($fullBytes > 0 && \substr($address, 0, $fullBytes) !== \substr($network, 0, $fullBytes)) {
519 return \false;
520 }
521 if ($remainingBits === 0) {
522 return \true;
523 }
524 $mask = 0xff << 8 - $remainingBits & 0xff;
525 return (\ord($address[$fullBytes]) & $mask) === (\ord($network[$fullBytes]) & $mask);
526 }
527 private static function getDefaultPort(string $scheme) : ?int
528 {
529 if ($scheme === 'http') {
530 return 80;
531 }
532 if ($scheme === 'https') {
533 return 443;
534 }
535 return null;
536 }
537 /**
538 * Wrapper for json_decode that throws when an error occurs.
539 *
540 * @param string $json JSON data to parse
541 * @param bool $assoc When true, returned objects will be converted
542 * into associative arrays.
543 * @param int $depth User specified recursion depth.
544 * @param int $options Bitmask of JSON decode options.
545 *
546 * @return object|array|string|int|float|bool|null
547 *
548 * @throws InvalidArgumentException if the JSON cannot be decoded.
549 *
550 * @see https://www.php.net/manual/en/function.json-decode.php
551 */
552 public static function jsonDecode(string $json, bool $assoc = \false, int $depth = 512, int $options = 0)
553 {
554 if ($depth < 1) {
555 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('json_decode error: Maximum stack depth exceeded');
556 }
557 $data = \json_decode($json, $assoc, $depth, $options);
558 if (\JSON_ERROR_NONE !== \json_last_error()) {
559 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('json_decode error: ' . \json_last_error_msg());
560 }
561 return $data;
562 }
563 /**
564 * Wrapper for JSON encoding that throws when an error occurs.
565 *
566 * @param mixed $value The value being encoded
567 * @param int $options JSON encode option bitmask
568 * @param int $depth Set the maximum depth. Must be greater than zero.
569 *
570 * @throws InvalidArgumentException if the JSON cannot be encoded.
571 *
572 * @see https://www.php.net/manual/en/function.json-encode.php
573 */
574 public static function jsonEncode($value, int $options = 0, int $depth = 512) : string
575 {
576 $json = \json_encode($value, $options, $depth);
577 if (\JSON_ERROR_NONE !== \json_last_error()) {
578 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('json_encode error: ' . \json_last_error_msg());
579 }
580 /** @var string */
581 return $json;
582 }
583 /**
584 * Wrapper for the hrtime() or microtime() functions
585 * (depending on the PHP version, one of the two is used)
586 *
587 * @return float UNIX timestamp
588 *
589 * @internal
590 */
591 public static function currentTime() : float
592 {
593 return (float) \function_exists('hrtime') ? \hrtime(\true) / 1000000000.0 : \microtime(\true);
594 }
595 /**
596 * @param mixed $value
597 *
598 * @internal
599 */
600 public static function normalizeIdnConversionOption($value) : ?int
601 {
602 if ($value === null || $value === \false) {
603 return null;
604 }
605 if ($value === \true) {
606 return \IDNA_DEFAULT;
607 }
608 if (\is_int($value)) {
609 return $value;
610 }
611 if (\is_string($value) && \is_numeric($value) || \is_float($value) && \is_finite($value)) {
612 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing %s as the "idn_conversion" request option is deprecated; guzzlehttp/guzzle 8.0 will reject values that are not true, false, null, or an integer IDNA_* bitmask.', \get_debug_type($value));
613 return (int) $value;
614 }
615 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('idn_conversion must be true, false, null, or an integer IDNA_* bitmask');
616 }
617 /**
618 * @throws InvalidArgumentException
619 *
620 * @internal
621 */
622 public static function idnUriConvert(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, int $options = 0) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
623 {
624 if ($uri->getHost()) {
625 $asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
626 if ($asciiHost === \false) {
627 $errorBitSet = $info['errors'] ?? 0;
628 $errorConstants = \array_filter(\array_keys(\get_defined_constants()), static function (string $name) : bool {
629 return \substr($name, 0, 11) === 'IDNA_ERROR_';
630 });
631 $errors = [];
632 foreach ($errorConstants as $errorConstant) {
633 if ($errorBitSet & \constant($errorConstant)) {
634 $errors[] = $errorConstant;
635 }
636 }
637 $errorMessage = 'IDN conversion failed';
638 if ($errors) {
639 $errorMessage .= ' (errors: ' . \implode(', ', $errors) . ')';
640 }
641 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException($errorMessage);
642 }
643 if ($uri->getHost() !== $asciiHost) {
644 // Replace URI only if the ASCII version is different
645 $uri = $uri->withHost($asciiHost);
646 }
647 }
648 return $uri;
649 }
650 /**
651 * @internal
652 */
653 public static function getenv(string $name) : ?string
654 {
655 if (isset($_SERVER[$name])) {
656 return (string) $_SERVER[$name];
657 }
658 if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== \false && $value !== null) {
659 return (string) $value;
660 }
661 return null;
662 }
663 /**
664 * @return string|false
665 */
666 private static function idnToAsci(string $domain, int $options, ?array &$info = [])
667 {
668 if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
669 return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info);
670 }
671 throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old');
672 }
673 }
674