1 <?php 2 3 namespace GuzzleHttp; 4 5 use GuzzleHttp\Exception\InvalidArgumentException; 6 use GuzzleHttp\Handler\CurlHandler; 7 use GuzzleHttp\Handler\CurlMultiHandler; 8 use GuzzleHttp\Handler\Proxy; 9 use GuzzleHttp\Handler\StreamHandler; 10 use Psr\Http\Message\UriInterface; 11 12 final class Utils 13 { 14 /** 15 * Debug function used to describe the provided value type and class. 16 * 17 * @param mixed $input 18 * 19 * @return string Returns a string containing the type of the variable and 20 * if a class is provided, the class name. 21 */ 22 public static function describeType($input): string 23 { 24 switch (\gettype($input)) { 25 case 'object': 26 return 'object(' . \get_class($input) . ')'; 27 case 'array': 28 return 'array(' . \count($input) . ')'; 29 default: 30 \ob_start(); 31 \var_dump($input); 32 // normalize float vs double 33 /** @var string $varDumpContent */ 34 $varDumpContent = \ob_get_clean(); 35 36 return \str_replace('double(', 'float(', \rtrim($varDumpContent)); 37 } 38 } 39 40 /** 41 * Parses an array of header lines into an associative array of headers. 42 * 43 * @param iterable $lines Header lines array of strings in the following 44 * format: "Name: Value" 45 */ 46 public static function headersFromLines(iterable $lines): array 47 { 48 $headers = []; 49 50 foreach ($lines as $line) { 51 $parts = \explode(':', $line, 2); 52 $headers[\trim($parts[0])][] = isset($parts[1]) ? \trim($parts[1]) : null; 53 } 54 55 return $headers; 56 } 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 74 return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w'); 75 } 76 77 /** 78 * Chooses and creates a default handler to use based on the environment. 79 * 80 * The returned handler is not wrapped by any default middlewares. 81 * 82 * @throws \RuntimeException if no viable Handler is available. 83 * 84 * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system. 85 */ 86 public static function chooseHandler(): callable 87 { 88 $handler = null; 89 90 if (\defined('CURLOPT_CUSTOMREQUEST')) { 91 if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) { 92 $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); 93 } elseif (\function_exists('curl_exec')) { 94 $handler = new CurlHandler(); 95 } elseif (\function_exists('curl_multi_exec')) { 96 $handler = new CurlMultiHandler(); 97 } 98 } 99 100 if (\ini_get('allow_url_fopen')) { 101 $handler = $handler 102 ? Proxy::wrapStreaming($handler, new StreamHandler()) 103 : new StreamHandler(); 104 } elseif (!$handler) { 105 throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.'); 106 } 107 108 return $handler; 109 } 110 111 /** 112 * Get the default User-Agent string to use with Guzzle. 113 */ 114 public static function defaultUserAgent(): string 115 { 116 return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION); 117 } 118 119 /** 120 * Returns the default cacert bundle for the current system. 121 * 122 * First, the openssl.cafile and curl.cainfo php.ini settings are checked. 123 * If those settings are not configured, then the common locations for 124 * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X 125 * and Windows are checked. If any of these file locations are found on 126 * disk, they will be utilized. 127 * 128 * Note: the result of this function is cached for subsequent calls. 129 * 130 * @throws \RuntimeException if no bundle can be found. 131 * 132 * @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+. 133 */ 134 public static function defaultCaBundle(): string 135 { 136 static $cached = null; 137 static $cafiles = [ 138 // Red Hat, CentOS, Fedora (provided by the ca-certificates package) 139 '/etc/pki/tls/certs/ca-bundle.crt', 140 // Ubuntu, Debian (provided by the ca-certificates package) 141 '/etc/ssl/certs/ca-certificates.crt', 142 // FreeBSD (provided by the ca_root_nss package) 143 '/usr/local/share/certs/ca-root-nss.crt', 144 // SLES 12 (provided by the ca-certificates package) 145 '/var/lib/ca-certificates/ca-bundle.pem', 146 // OS X provided by homebrew (using the default path) 147 '/usr/local/etc/openssl/cert.pem', 148 // Google app engine 149 '/etc/ca-certificates.crt', 150 // Windows? 151 'C:\\windows\\system32\\curl-ca-bundle.crt', 152 'C:\\windows\\curl-ca-bundle.crt', 153 ]; 154 155 if ($cached) { 156 return $cached; 157 } 158 159 if ($ca = \ini_get('openssl.cafile')) { 160 return $cached = $ca; 161 } 162 163 if ($ca = \ini_get('curl.cainfo')) { 164 return $cached = $ca; 165 } 166 167 foreach ($cafiles as $filename) { 168 if (\file_exists($filename)) { 169 return $cached = $filename; 170 } 171 } 172 173 throw new \RuntimeException( 174 <<< EOT 175 No system CA bundle could be found in any of the the common system locations. 176 PHP versions earlier than 5.6 are not properly configured to use the system's 177 CA bundle by default. In order to verify peer certificates, you will need to 178 supply the path on disk to a certificate bundle to the 'verify' request 179 option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not 180 need a specific certificate bundle, then Mozilla provides a commonly used CA 181 bundle which can be downloaded here (provided by the maintainer of cURL): 182 https://curl.haxx.se/ca/cacert.pem. Once 183 you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP 184 ini setting to point to the path to the file, allowing you to omit the 'verify' 185 request option. See https://curl.haxx.se/docs/sslcerts.html for more 186 information. 187 EOT 188 ); 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($key)] = $key; 200 } 201 202 return $result; 203 } 204 205 /** 206 * Returns true if the provided host matches any of the no proxy areas. 207 * 208 * This method will strip a port from the host if it is present. Each pattern 209 * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a 210 * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == 211 * "baz.foo.com", but ".foo.com" != "foo.com"). 212 * 213 * Areas are matched in the following cases: 214 * 1. "*" (without quotes) always matches any hosts. 215 * 2. An exact match. 216 * 3. The area starts with "." and the area is the last part of the host. e.g. 217 * '.mit.edu' will match any host that ends with '.mit.edu'. 218 * 219 * @param string $host Host to check against the patterns. 220 * @param string[] $noProxyArray An array of host patterns. 221 * 222 * @throws InvalidArgumentException 223 */ 224 public static function isHostInNoProxy(string $host, array $noProxyArray): bool 225 { 226 if (\strlen($host) === 0) { 227 throw new InvalidArgumentException('Empty host provided'); 228 } 229 230 // Strip port if present. 231 [$host] = \explode(':', $host, 2); 232 233 foreach ($noProxyArray as $area) { 234 // Always match on wildcards. 235 if ($area === '*') { 236 return true; 237 } 238 239 if (empty($area)) { 240 // Don't match on empty values. 241 continue; 242 } 243 244 if ($area === $host) { 245 // Exact matches. 246 return true; 247 } 248 // Special match if the area when prefixed with ".". Remove any 249 // existing leading "." and add a new leading ".". 250 $area = '.' . \ltrim($area, '.'); 251 if (\substr($host, -(\strlen($area))) === $area) { 252 return true; 253 } 254 } 255 256 return false; 257 } 258 259 /** 260 * Wrapper for json_decode that throws when an error occurs. 261 * 262 * @param string $json JSON data to parse 263 * @param bool $assoc When true, returned objects will be converted 264 * into associative arrays. 265 * @param int $depth User specified recursion depth. 266 * @param int $options Bitmask of JSON decode options. 267 * 268 * @return object|array|string|int|float|bool|null 269 * 270 * @throws InvalidArgumentException if the JSON cannot be decoded. 271 * 272 * @link https://www.php.net/manual/en/function.json-decode.php 273 */ 274 public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0) 275 { 276 $data = \json_decode($json, $assoc, $depth, $options); 277 if (\JSON_ERROR_NONE !== \json_last_error()) { 278 throw new InvalidArgumentException('json_decode error: ' . \json_last_error_msg()); 279 } 280 281 return $data; 282 } 283 284 /** 285 * Wrapper for JSON encoding that throws when an error occurs. 286 * 287 * @param mixed $value The value being encoded 288 * @param int $options JSON encode option bitmask 289 * @param int $depth Set the maximum depth. Must be greater than zero. 290 * 291 * @throws InvalidArgumentException if the JSON cannot be encoded. 292 * 293 * @link https://www.php.net/manual/en/function.json-encode.php 294 */ 295 public static function jsonEncode($value, int $options = 0, int $depth = 512): string 296 { 297 $json = \json_encode($value, $options, $depth); 298 if (\JSON_ERROR_NONE !== \json_last_error()) { 299 throw new InvalidArgumentException('json_encode error: ' . \json_last_error_msg()); 300 } 301 302 /** @var string */ 303 return $json; 304 } 305 306 /** 307 * Wrapper for the hrtime() or microtime() functions 308 * (depending on the PHP version, one of the two is used) 309 * 310 * @return float UNIX timestamp 311 * 312 * @internal 313 */ 314 public static function currentTime(): float 315 { 316 return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true); 317 } 318 319 /** 320 * @throws InvalidArgumentException 321 * 322 * @internal 323 */ 324 public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface 325 { 326 if ($uri->getHost()) { 327 $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); 328 if ($asciiHost === false) { 329 $errorBitSet = $info['errors'] ?? 0; 330 331 $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { 332 return substr($name, 0, 11) === 'IDNA_ERROR_'; 333 }); 334 335 $errors = []; 336 foreach ($errorConstants as $errorConstant) { 337 if ($errorBitSet & constant($errorConstant)) { 338 $errors[] = $errorConstant; 339 } 340 } 341 342 $errorMessage = 'IDN conversion failed'; 343 if ($errors) { 344 $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; 345 } 346 347 throw new InvalidArgumentException($errorMessage); 348 } 349 if ($uri->getHost() !== $asciiHost) { 350 // Replace URI only if the ASCII version is different 351 $uri = $uri->withHost($asciiHost); 352 } 353 } 354 355 return $uri; 356 } 357 358 /** 359 * @internal 360 */ 361 public static function getenv(string $name): ?string 362 { 363 if (isset($_SERVER[$name])) { 364 return (string) $_SERVER[$name]; 365 } 366 367 if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { 368 return (string) $value; 369 } 370 371 return null; 372 } 373 374 /** 375 * @return string|false 376 */ 377 private static function idnToAsci(string $domain, int $options, ?array &$info = []) 378 { 379 if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { 380 return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); 381 } 382 383 throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); 384 } 385 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body