| @@ -2,11 +2,13 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace YoastSEO_Vendor\GuzzleHttp; |
| 4 | 4 | |
| 5 | 5 | use YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJar; |
| 6 | +use YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJarInterface; | |
| 6 | 7 | use YoastSEO_Vendor\GuzzleHttp\Exception\GuzzleException; |
| 7 | 8 | use YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException; |
| 8 | 9 | use YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState; |
| 10 | +use YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion; | |
| 9 | 11 | use YoastSEO_Vendor\GuzzleHttp\Promise as P; |
| 10 | 12 | use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface; |
| 11 | 13 | use YoastSEO_Vendor\Psr\Http\Message\RequestInterface; |
| 12 | 14 | use YoastSEO_Vendor\Psr\Http\Message\ResponseInterface; |
| @@ -48,8 +50,22 @@ | ||
| 48 | 50 | * - base_uri: (string|UriInterface) Base URI of the client that is merged |
| 49 | 51 | * into relative URIs. Can be a string or instance of UriInterface. |
| 50 | 52 | * - transport_sharing: (string|null) Transport sharing mode for the |
| 51 | 53 | * default handler. Accepts TransportSharing::* or null. Defaults to null. |
| 54 | + * - max_host_connections: (int|null) Maximum concurrent connections per | |
| 55 | + * host, applied by the default CurlMultiHandler. The default stream | |
| 56 | + * fallback receives the cap as a marker only: it rejects enabled | |
| 57 | + * response streaming ("stream" => true) and does not limit overlapping | |
| 58 | + * buffered calls. | |
| 59 | + * - max_total_connections: (int|null) Maximum concurrent connections | |
| 60 | + * overall, applied by the default CurlMultiHandler. The default stream | |
| 61 | + * fallback receives the cap as a marker only: it rejects enabled | |
| 62 | + * response streaming ("stream" => true) and does not limit overlapping | |
| 63 | + * buffered calls. | |
| 64 | + * - multiplex: (string|null) Multiplexing::NONE to disable multiplexing on | |
| 65 | + * the default CurlMultiHandler; the value also becomes the default | |
| 66 | + * "multiplex" request option. Other Multiplexing::* values act as the | |
| 67 | + * default request option only. | |
| 52 | 68 | * - **: any request option |
| 53 | 69 | * |
| 54 | 70 | * @param array $config Client configuration settings. |
| 55 | 71 | * |
| @@ -56,15 +72,35 @@ | ||
| 56 | 72 | * @see RequestOptions for a list of available request options. |
| 57 | 73 | */ |
| 58 | 74 | public function __construct(array $config = []) |
| 59 | 75 | { |
| 76 | + $handlerOptions = []; | |
| 77 | + foreach (['max_host_connections', 'max_total_connections'] as $capOption) { | |
| 78 | + if (\array_key_exists($capOption, $config)) { | |
| 79 | + if ($config[$capOption] !== null) { | |
| 80 | + $handlerOptions[$capOption] = $config[$capOption]; | |
| 81 | + } | |
| 82 | + unset($config[$capOption]); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + // Deliberately not unset: the value also becomes the default | |
| 86 | + // "multiplex" request option, which the configured handler accepts. | |
| 87 | + $handlerMultiplex = ($config['multiplex'] ?? null) === \YoastSEO_Vendor\GuzzleHttp\Multiplexing::NONE; | |
| 60 | 88 | $transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null; |
| 61 | 89 | $transportSharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); |
| 62 | 90 | unset($config['transport_sharing']); |
| 63 | 91 | if (!isset($config['handler'])) { |
| 64 | - $config['handler'] = $transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::NONE ? \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create() : \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create(\YoastSEO_Vendor\GuzzleHttp\Utils::chooseHandler(['transport_sharing' => $transportSharingMode])); | |
| 92 | + if ($transportSharingMode !== \YoastSEO_Vendor\GuzzleHttp\TransportSharing::NONE) { | |
| 93 | + $handlerOptions['transport_sharing'] = $transportSharingMode; | |
| 94 | + } | |
| 95 | + if ($handlerMultiplex) { | |
| 96 | + $handlerOptions['multiplex'] = \YoastSEO_Vendor\GuzzleHttp\Multiplexing::NONE; | |
| 97 | + } | |
| 98 | + $config['handler'] = $handlerOptions === [] ? \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create() : \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create(\YoastSEO_Vendor\GuzzleHttp\Utils::chooseHandler($handlerOptions)); | |
| 65 | 99 | } elseif (!\is_callable($config['handler'])) { |
| 66 | 100 | throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('handler must be a callable'); |
| 101 | + } elseif ($handlerOptions !== []) { | |
| 102 | + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The "max_host_connections" and "max_total_connections" client options require Guzzle to create the default handler. Configure the options on the CurlMultiHandler constructor to apply numeric connection caps, or on the StreamHandler constructor to reject enabled response streaming, when providing a custom handler.'); | |
| 67 | 103 | } elseif ($transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::HANDLER_REQUIRE) { |
| 68 | 104 | throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler or CurlMultiHandler when providing a custom cURL handler.'); |
| 69 | 105 | } |
| 70 | 106 | // Convert the base_uri to a UriInterface |
| @@ -82,8 +118,9 @@ | ||
| 82 | 118 | * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. |
| 83 | 119 | */ |
| 84 | 120 | public function __call($method, $args) |
| 85 | 121 | { |
| 122 | + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s::%s() is deprecated and will be removed in 8.0.', __CLASS__, __FUNCTION__); | |
| 86 | 123 | if (\count($args) < 1) { |
| 87 | 124 | throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Magic request methods require a URI and optional options array'); |
| 88 | 125 | } |
| 89 | 126 | $uri = $args[0]; |
| @@ -89,9 +126,9 @@ | ||
| 89 | 126 | $uri = $args[0]; |
| 90 | 127 | $opts = $args[1] ?? []; |
| 91 | 128 | $isAsync = \substr($method, -5) === 'Async'; |
| 92 | 129 | $method = $isAsync ? \substr($method, 0, -5) : $method; |
| 93 | - $method = \strtoupper($method); | |
| 130 | + $method = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method); | |
| 94 | 131 | return $isAsync ? $this->requestAsync($method, $uri, $opts) : $this->request($method, $uri, $opts); |
| 95 | 132 | } |
| 96 | 133 | /** |
| 97 | 134 | * Asynchronously send an HTTP request. |
| @@ -102,9 +139,9 @@ | ||
| 102 | 139 | public function sendAsync(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface |
| 103 | 140 | { |
| 104 | 141 | // Merge the base URI into the request URI if needed. |
| 105 | 142 | $options = $this->prepareDefaults($options); |
| 106 | - return $this->transfer($request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), $options); | |
| 143 | + return $this->transfer($request->withUri($this->buildUri($request->getUri(), $options), self::shouldPreserveHost($request)), $options); | |
| 107 | 144 | } |
| 108 | 145 | /** |
| 109 | 146 | * Send an HTTP request. |
| 110 | 147 | * |
| @@ -143,9 +180,9 @@ | ||
| 143 | 180 | * @param array $options Request options to apply. See {@see RequestOptions}. |
| 144 | 181 | */ |
| 145 | 182 | public function requestAsync(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface |
| 146 | 183 | { |
| 147 | - $normalizedMethod = \strtoupper($method); | |
| 184 | + $normalizedMethod = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method); | |
| 148 | 185 | if ($method !== $normalizedMethod) { |
| 149 | 186 | \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::requestAsync() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'); |
| 150 | 187 | $method = $normalizedMethod; |
| 151 | 188 | } |
| @@ -183,9 +220,9 @@ | ||
| 183 | 220 | * @throws GuzzleException |
| 184 | 221 | */ |
| 185 | 222 | public function request(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface |
| 186 | 223 | { |
| 187 | - $normalizedMethod = \strtoupper($method); | |
| 224 | + $normalizedMethod = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method); | |
| 188 | 225 | if ($method !== $normalizedMethod) { |
| 189 | 226 | \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::request() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'); |
| 190 | 227 | $method = $normalizedMethod; |
| 191 | 228 | } |
| @@ -215,11 +252,34 @@ | ||
| 215 | 252 | $idnOptions = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null); |
| 216 | 253 | if ($idnOptions !== null) { |
| 217 | 254 | $uri = \YoastSEO_Vendor\GuzzleHttp\Utils::idnUriConvert($uri, $idnOptions); |
| 218 | 255 | } |
| 219 | - return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; | |
| 256 | + if ($uri->getScheme() === '' && $uri->getHost() !== '') { | |
| 257 | + $uri = $uri->withScheme('http'); | |
| 258 | + } | |
| 259 | + return $uri; | |
| 220 | 260 | } |
| 221 | 261 | /** |
| 262 | + * Whether to preserve an existing Host header when the URI changes. | |
| 263 | + * | |
| 264 | + * A header matching the current URI carries no explicit override and is | |
| 265 | + * regenerated after base URI resolution or IDN conversion. Other values | |
| 266 | + * are preserved as deliberate overrides, as PSR-7 requires. | |
| 267 | + */ | |
| 268 | + private static function shouldPreserveHost(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : bool | |
| 269 | + { | |
| 270 | + if (!$request->hasHeader('Host')) { | |
| 271 | + return \false; | |
| 272 | + } | |
| 273 | + $uri = $request->getUri(); | |
| 274 | + $host = $uri->getHost(); | |
| 275 | + $port = $uri->getPort(); | |
| 276 | + if ($port !== null) { | |
| 277 | + $host .= ':' . $port; | |
| 278 | + } | |
| 279 | + return $host !== $request->getHeaderLine('Host'); | |
| 280 | + } | |
| 281 | + /** | |
| 222 | 282 | * Configures the default options for a client. |
| 223 | 283 | */ |
| 224 | 284 | private function configureDefaults(array $config) : void |
| 225 | 285 | { |
| @@ -248,9 +308,9 @@ | ||
| 248 | 308 | } else { |
| 249 | 309 | // Add the User-Agent header if one was not already set. |
| 250 | 310 | $hasUserAgent = \false; |
| 251 | 311 | foreach (\array_keys($this->config['headers']) as $name) { |
| 252 | - if (\strtolower((string) $name) === 'user-agent') { | |
| 312 | + if (\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower((string) $name) === 'user-agent') { | |
| 253 | 313 | $hasUserAgent = \true; |
| 254 | 314 | break; |
| 255 | 315 | } |
| 256 | 316 | } |
| @@ -296,10 +356,123 @@ | ||
| 296 | 356 | unset($result[$k]); |
| 297 | 357 | } |
| 298 | 358 | } |
| 299 | 359 | self::warnAboutInvalidRequestOptionTypes($result); |
| 300 | - return $result; | |
| 360 | + return self::normalizeDeprecatedRequestOptionValues($result); | |
| 301 | 361 | } |
| 362 | + /** | |
| 363 | + * Normalize values that guzzlehttp/guzzle 8.0 rejects only after the | |
| 364 | + * corresponding 7.x deprecation has already been emitted. | |
| 365 | + * | |
| 366 | + * @param array<string, mixed> $options | |
| 367 | + * | |
| 368 | + * @return array<string, mixed> | |
| 369 | + */ | |
| 370 | + private static function normalizeDeprecatedRequestOptionValues(array $options) : array | |
| 371 | + { | |
| 372 | + self::normalizeDeprecatedAuthOptionValues($options); | |
| 373 | + self::normalizeDeprecatedTlsFileOptionValues($options, 'cert'); | |
| 374 | + self::normalizeDeprecatedTlsFileOptionValues($options, 'ssl_key'); | |
| 375 | + self::normalizeDeprecatedStringOptionValues($options); | |
| 376 | + self::normalizeDeprecatedNumericOptionValues($options); | |
| 377 | + self::normalizeDeprecatedIntegerOptionValues($options); | |
| 378 | + return $options; | |
| 379 | + } | |
| 380 | + /** | |
| 381 | + * @param mixed $value | |
| 382 | + */ | |
| 383 | + private static function canStringifyDeprecatedValue($value) : bool | |
| 384 | + { | |
| 385 | + return $value === null || \is_scalar($value) || \is_object($value) && \method_exists($value, '__toString'); | |
| 386 | + } | |
| 387 | + /** | |
| 388 | + * @param mixed $value | |
| 389 | + */ | |
| 390 | + private static function stringifyDeprecatedValue($value) : string | |
| 391 | + { | |
| 392 | + if (\is_float($value) && !\is_finite($value)) { | |
| 393 | + return \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); | |
| 394 | + } | |
| 395 | + if ($value === null) { | |
| 396 | + return ''; | |
| 397 | + } | |
| 398 | + if (\is_scalar($value)) { | |
| 399 | + return (string) $value; | |
| 400 | + } | |
| 401 | + if (\is_object($value) && \method_exists($value, '__toString')) { | |
| 402 | + return $value->__toString(); | |
| 403 | + } | |
| 404 | + throw new \LogicException('Value is not stringable.'); | |
| 405 | + } | |
| 406 | + /** | |
| 407 | + * @param array<string, mixed> $options | |
| 408 | + */ | |
| 409 | + private static function normalizeDeprecatedAuthOptionValues(array &$options) : void | |
| 410 | + { | |
| 411 | + if (!isset($options['auth']) || !\is_array($options['auth']) || $options['auth'] === []) { | |
| 412 | + return; | |
| 413 | + } | |
| 414 | + foreach ([0, 1] as $index) { | |
| 415 | + if (\array_key_exists($index, $options['auth']) && !\is_string($options['auth'][$index]) && self::canStringifyDeprecatedValue($options['auth'][$index])) { | |
| 416 | + $options['auth'][$index] = self::stringifyDeprecatedValue($options['auth'][$index]); | |
| 417 | + } | |
| 418 | + } | |
| 419 | + if (\array_key_exists(2, $options['auth']) && $options['auth'][2] !== null && !\is_string($options['auth'][2]) && self::canStringifyDeprecatedValue($options['auth'][2])) { | |
| 420 | + $options['auth'][2] = self::stringifyDeprecatedValue($options['auth'][2]); | |
| 421 | + } | |
| 422 | + } | |
| 423 | + /** | |
| 424 | + * @param array<string, mixed> $options | |
| 425 | + */ | |
| 426 | + private static function normalizeDeprecatedTlsFileOptionValues(array &$options, string $option) : void | |
| 427 | + { | |
| 428 | + if (!isset($options[$option]) || !\is_array($options[$option])) { | |
| 429 | + return; | |
| 430 | + } | |
| 431 | + foreach ([0, 1] as $index) { | |
| 432 | + if (\array_key_exists($index, $options[$option]) && $options[$option][$index] !== null && !\is_string($options[$option][$index]) && self::canStringifyDeprecatedValue($options[$option][$index])) { | |
| 433 | + $options[$option][$index] = self::stringifyDeprecatedValue($options[$option][$index]); | |
| 434 | + } | |
| 435 | + } | |
| 436 | + } | |
| 437 | + /** | |
| 438 | + * @param array<string, mixed> $options | |
| 439 | + */ | |
| 440 | + private static function normalizeDeprecatedStringOptionValues(array &$options) : void | |
| 441 | + { | |
| 442 | + foreach (['cert_type', 'force_ip_resolve', 'ssl_key_type'] as $option) { | |
| 443 | + if (\array_key_exists($option, $options) && !\is_string($options[$option]) && self::canStringifyDeprecatedValue($options[$option])) { | |
| 444 | + $options[$option] = self::stringifyDeprecatedValue($options[$option]); | |
| 445 | + } | |
| 446 | + } | |
| 447 | + } | |
| 448 | + /** | |
| 449 | + * @param array<string, mixed> $options | |
| 450 | + */ | |
| 451 | + private static function normalizeDeprecatedNumericOptionValues(array &$options) : void | |
| 452 | + { | |
| 453 | + foreach (['connect_timeout', 'delay', 'read_timeout', 'timeout'] as $option) { | |
| 454 | + if (\array_key_exists($option, $options) && \is_string($options[$option]) && \is_numeric($options[$option])) { | |
| 455 | + $options[$option] = $options[$option] + 0; | |
| 456 | + } | |
| 457 | + } | |
| 458 | + } | |
| 459 | + /** | |
| 460 | + * @param array<string, mixed> $options | |
| 461 | + */ | |
| 462 | + private static function normalizeDeprecatedIntegerOptionValues(array &$options) : void | |
| 463 | + { | |
| 464 | + foreach (['crypto_method', 'crypto_method_max', 'retries'] as $option) { | |
| 465 | + if (!\array_key_exists($option, $options)) { | |
| 466 | + continue; | |
| 467 | + } | |
| 468 | + if (\is_string($options[$option]) && \preg_match('/^-?\\d+$/D', $options[$option]) === 1) { | |
| 469 | + $options[$option] = (int) $options[$option]; | |
| 470 | + } elseif (\is_float($options[$option]) && \is_finite($options[$option]) && $options[$option] === (float) (int) $options[$option]) { | |
| 471 | + $options[$option] = (int) $options[$option]; | |
| 472 | + } | |
| 473 | + } | |
| 474 | + } | |
| 302 | 475 | private static function warnAboutRequestLevelHandler(array $options) : void |
| 303 | 476 | { |
| 304 | 477 | if (!\array_key_exists('handler', $options)) { |
| 305 | 478 | return; |
| @@ -312,8 +485,10 @@ | ||
| 312 | 485 | self::warnInvalidRequestOptionType('handler', 'callable', $options['handler']); |
| 313 | 486 | } |
| 314 | 487 | if (isset($options['allow_redirects']) && \is_array($options['allow_redirects'])) { |
| 315 | 488 | self::warnAboutInvalidAllowRedirectsOptionTypes($options['allow_redirects']); |
| 489 | + } elseif (isset($options['allow_redirects']) && !\is_bool($options['allow_redirects'])) { | |
| 490 | + self::warnInvalidRequestOptionType('allow_redirects', 'bool|array', $options['allow_redirects'], '7.13'); | |
| 316 | 491 | } |
| 317 | 492 | if (isset($options['auth'])) { |
| 318 | 493 | self::warnAboutInvalidAuthOptionTypes($options['auth']); |
| 319 | 494 | } |
| @@ -323,11 +498,18 @@ | ||
| 323 | 498 | self::warnAboutInvalidTlsFileOptionTypes($options, 'cert'); |
| 324 | 499 | self::warnIfPresentAndNotString($options, 'cert_type'); |
| 325 | 500 | self::warnIfPresentAndNotNumber($options, 'connect_timeout'); |
| 326 | 501 | self::warnIfPresentAndNotInt($options, 'crypto_method'); |
| 502 | + self::warnIfPresentAndNotInt($options, 'crypto_method_max', null, '7.13'); | |
| 327 | 503 | self::warnIfPresentAndNotBoolOrResource($options, 'debug'); |
| 328 | 504 | self::warnIfPresentAndNotBoolOrString($options, 'decode_content'); |
| 329 | 505 | self::warnIfPresentAndNotNumber($options, 'delay'); |
| 506 | + if (isset($options['delay']) && \is_numeric($options['delay'])) { | |
| 507 | + $delay = (float) $options['delay']; | |
| 508 | + if (!\is_finite($delay) || $delay < 0.0) { | |
| 509 | + self::warnInvalidRequestOptionType('delay', 'finite int|float greater than or equal to 0', $options['delay'], '7.13'); | |
| 510 | + } | |
| 511 | + } | |
| 330 | 512 | self::warnIfPresentAndNotBoolOrInt($options, 'expect'); |
| 331 | 513 | if (isset($options['form_params'])) { |
| 332 | 514 | self::warnAboutInvalidFormParamTypes($options['form_params']); |
| 333 | 515 | } |
| @@ -333,8 +515,11 @@ | ||
| 333 | 515 | } |
| 334 | 516 | if (isset($options['force_ip_resolve']) && !\is_string($options['force_ip_resolve'])) { |
| 335 | 517 | self::warnInvalidRequestOptionType('force_ip_resolve', 'string', $options['force_ip_resolve']); |
| 336 | 518 | } |
| 519 | + if (isset($options['force_ip_resolve']) && \is_string($options['force_ip_resolve']) && $options['force_ip_resolve'] !== 'v4' && $options['force_ip_resolve'] !== 'v6') { | |
| 520 | + self::warnInvalidRequestOptionType('force_ip_resolve', '"v4"|"v6"', $options['force_ip_resolve'], '7.13'); | |
| 521 | + } | |
| 337 | 522 | if (isset($options['headers'])) { |
| 338 | 523 | self::warnAboutInvalidHeaderOptionTypes($options['headers']); |
| 339 | 524 | } |
| 340 | 525 | self::warnIfPresentAndNotBool($options, 'http_errors'); |
| @@ -342,10 +527,12 @@ | ||
| 342 | 527 | self::warnAboutInvalidMultipartOptionTypes($options['multipart']); |
| 343 | 528 | } |
| 344 | 529 | self::warnIfPresentAndNotCallable($options, 'on_headers'); |
| 345 | 530 | self::warnIfPresentAndNotCallable($options, 'on_stats'); |
| 531 | + self::warnIfPresentAndNotCallable($options, 'on_trailers', null, '7.14'); | |
| 346 | 532 | self::warnIfPresentAndNotCallable($options, 'progress'); |
| 347 | 533 | self::warnIfPresentAndNotStringArray($options, 'protocols', \true); |
| 534 | + self::warnAboutInvalidProtocolValues($options, 'protocols'); | |
| 348 | 535 | self::warnAboutInvalidProxyOptionTypes($options); |
| 349 | 536 | self::warnIfPresentAndNotNumber($options, 'read_timeout'); |
| 350 | 537 | self::warnIfPresentAndNotInt($options, 'retries'); |
| 351 | 538 | if (isset($options['sink']) && !\is_bool($options['sink']) && !\is_resource($options['sink']) && !\is_string($options['sink']) && !$options['sink'] instanceof \YoastSEO_Vendor\Psr\Http\Message\StreamInterface) { |
| @@ -362,8 +549,11 @@ | ||
| 362 | 549 | self::warnIfPresentAndNotArray($options, 'curl', 'array<int|string, mixed>'); |
| 363 | 550 | if (isset($options['cookies']) && $options['cookies'] === \true) { |
| 364 | 551 | self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']); |
| 365 | 552 | } |
| 553 | + if (isset($options['cookies']) && $options['cookies'] !== \false && $options['cookies'] !== \true && !$options['cookies'] instanceof \YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJarInterface) { | |
| 554 | + self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies'], '7.13'); | |
| 555 | + } | |
| 366 | 556 | } |
| 367 | 557 | private static function warnAboutInvalidAllowRedirectsOptionTypes(array $allowRedirects) : void |
| 368 | 558 | { |
| 369 | 559 | self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max'); |
| @@ -369,8 +559,9 @@ | ||
| 369 | 559 | self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max'); |
| 370 | 560 | self::warnIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict'); |
| 371 | 561 | self::warnIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer'); |
| 372 | 562 | self::warnIfPresentAndNotStringArray($allowRedirects, 'protocols', \true, 'allow_redirects.protocols'); |
| 563 | + self::warnAboutInvalidProtocolValues($allowRedirects, 'protocols', 'allow_redirects.protocols'); | |
| 373 | 564 | self::warnIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect'); |
| 374 | 565 | self::warnIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects'); |
| 375 | 566 | } |
| 376 | 567 | /** |
| @@ -571,18 +762,18 @@ | ||
| 571 | 762 | if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) { |
| 572 | 763 | self::warnInvalidRequestOptionType($option, 'bool|string', $options[$option]); |
| 573 | 764 | } |
| 574 | 765 | } |
| 575 | - private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null) : void | |
| 766 | + private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null, string $since = '7.11') : void | |
| 576 | 767 | { |
| 577 | 768 | if (\array_key_exists($option, $options) && !\is_callable($options[$option])) { |
| 578 | - self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option]); | |
| 769 | + self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option], $since); | |
| 579 | 770 | } |
| 580 | 771 | } |
| 581 | - private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null) : void | |
| 772 | + private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null, string $since = '7.11') : void | |
| 582 | 773 | { |
| 583 | 774 | if (\array_key_exists($option, $options) && !\is_int($options[$option])) { |
| 584 | - self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option]); | |
| 775 | + self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option], $since); | |
| 585 | 776 | } |
| 586 | 777 | } |
| 587 | 778 | private static function warnIfPresentAndNotNumber(array $options, string $option) : void |
| 588 | 779 | { |
| @@ -613,8 +804,23 @@ | ||
| 613 | 804 | return; |
| 614 | 805 | } |
| 615 | 806 | } |
| 616 | 807 | } |
| 808 | + /** | |
| 809 | + * @param array<array-key, mixed> $options | |
| 810 | + */ | |
| 811 | + private static function warnAboutInvalidProtocolValues(array $options, string $option, ?string $path = null) : void | |
| 812 | + { | |
| 813 | + if (!isset($options[$option]) || !\is_array($options[$option])) { | |
| 814 | + return; | |
| 815 | + } | |
| 816 | + $path = $path ?? $option; | |
| 817 | + foreach ($options[$option] as $index => $protocol) { | |
| 818 | + if (\is_string($protocol) && $protocol !== 'http' && $protocol !== 'https') { | |
| 819 | + self::warnInvalidRequestOptionType($path . '.' . (string) $index, '"http"|"https"', $protocol, '7.13'); | |
| 820 | + } | |
| 821 | + } | |
| 822 | + } | |
| 617 | 823 | private static function warnIfPresentAndNotStringOrNumber(array $options, string $option) : void |
| 618 | 824 | { |
| 619 | 825 | if (\array_key_exists($option, $options) && !\is_string($options[$option]) && !\is_int($options[$option]) && !\is_float($options[$option])) { |
| 620 | 826 | self::warnInvalidRequestOptionType($option, 'string|int|float', $options[$option]); |
| @@ -622,11 +828,11 @@ | ||
| 622 | 828 | } |
| 623 | 829 | /** |
| 624 | 830 | * @param mixed $value |
| 625 | 831 | */ |
| 626 | - private static function warnInvalidRequestOptionType(string $option, string $expected, $value) : void | |
| 832 | + private static function warnInvalidRequestOptionType(string $option, string $expected, $value, string $since = '7.11') : void | |
| 627 | 833 | { |
| 628 | - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing %s to request option "%s" is deprecated; guzzlehttp/guzzle 8.0 requires %s.', \get_debug_type($value), $option, $expected); | |
| 834 | + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', $since, 'Passing %s to request option "%s" is deprecated; guzzlehttp/guzzle 8.0 requires %s.', \get_debug_type($value), $option, $expected); | |
| 629 | 835 | } |
| 630 | 836 | /** |
| 631 | 837 | * Transfers the given request and applies request options. |
| 632 | 838 | * |
| @@ -685,15 +891,20 @@ | ||
| 685 | 891 | $options['body'] = new \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream($options['multipart']); |
| 686 | 892 | unset($options['multipart']); |
| 687 | 893 | } |
| 688 | 894 | if (isset($options['json'])) { |
| 689 | - $options['body'] = \YoastSEO_Vendor\GuzzleHttp\Utils::jsonEncode($options['json']); | |
| 895 | + $json = \json_encode($options['json']); | |
| 896 | + if (\JSON_ERROR_NONE !== \json_last_error()) { | |
| 897 | + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('json_encode error: ' . \json_last_error_msg()); | |
| 898 | + } | |
| 899 | + /** @var non-empty-string $json */ | |
| 900 | + $options['body'] = $json; | |
| 690 | 901 | unset($options['json']); |
| 691 | 902 | // Ensure that we don't have the header in different case and set the new value. |
| 692 | 903 | $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); |
| 693 | 904 | $options['_conditional']['Content-Type'] = 'application/json'; |
| 694 | 905 | } |
| 695 | - if (!empty($options['decode_content']) && $options['decode_content'] !== \true) { | |
| 906 | + if (isset($options['decode_content']) && \is_string($options['decode_content'])) { | |
| 696 | 907 | // Ensure that we don't have the header in different case and set the new value. |
| 697 | 908 | $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); |
| 698 | 909 | $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content']; |
| 699 | 910 | } |
| @@ -705,9 +916,9 @@ | ||
| 705 | 916 | unset($options['body']); |
| 706 | 917 | } |
| 707 | 918 | if (!empty($options['auth']) && \is_array($options['auth'])) { |
| 708 | 919 | $value = $options['auth']; |
| 709 | - $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; | |
| 920 | + $type = isset($value[2]) ? \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($value[2]) : 'basic'; | |
| 710 | 921 | switch ($type) { |
| 711 | 922 | case 'basic': |
| 712 | 923 | // Ensure that we don't have the header in different case and set the new value. |
| 713 | 924 | $modify['set_headers'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); |
| @@ -718,9 +929,12 @@ | ||
| 718 | 929 | $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; |
| 719 | 930 | $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}"; |
| 720 | 931 | break; |
| 721 | 932 | case 'ntlm': |
| 722 | - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing "ntlm" as the built-in auth type is deprecated; guzzlehttp/guzzle 8.0 will no longer apply NTLM through the "auth" request option. Configure NTLM with cURL HTTP authentication options instead.'); | |
| 933 | + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing "ntlm" as the built-in auth type is deprecated; guzzlehttp/guzzle 8.0 will no longer apply NTLM through the "auth" request option. NTLM is also deprecated by curl/libcurl and may be unavailable in current or future libcurl builds. Avoid NTLM; if you must use it temporarily, configure cURL HTTP authentication options directly with a libcurl build that still supports NTLM.'); | |
| 934 | + if (!\YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion::supportsNtlm()) { | |
| 935 | + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('NTLM authentication is not available because the installed curl/libcurl build does not provide NTLM support.'); | |
| 936 | + } | |
| 723 | 937 | $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; |
| 724 | 938 | $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}"; |
| 725 | 939 | break; |
| 726 | 940 | } |