PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
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
← All changes | vendor_prefixed/guzzlehttp/guzzle/src/Client.php +722 -30 27.6 → trunk View file →
@@ -2,14 +2,18 @@
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;
9 +use YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState;
10 +use YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion;
8 11 use YoastSEO_Vendor\GuzzleHttp\Promise as P;
9 12 use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface;
10 13 use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
11 14 use YoastSEO_Vendor\Psr\Http\Message\ResponseInterface;
15 +use YoastSEO_Vendor\Psr\Http\Message\StreamInterface;
12 16 use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
13 17 /**
14 18 * @final
15 19 */
@@ -44,8 +48,24 @@
44 48 * that enables all of the request options below by attaching all of the
45 49 * default middleware to the handler.
46 50 * - base_uri: (string|UriInterface) Base URI of the client that is merged
47 51 * into relative URIs. Can be a string or instance of UriInterface.
52 + * - transport_sharing: (string|null) Transport sharing mode for the
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.
48 68 * - **: any request option
49 69 *
50 70 * @param array $config Client configuration settings.
51 71 *
@@ -52,12 +72,37 @@
52 72 * @see RequestOptions for a list of available request options.
53 73 */
54 74 public function __construct(array $config = [])
55 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;
88 + $transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null;
89 + $transportSharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
90 + unset($config['transport_sharing']);
56 91 if (!isset($config['handler'])) {
57 - $config['handler'] = \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create();
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));
58 99 } elseif (!\is_callable($config['handler'])) {
59 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.');
103 + } elseif ($transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::HANDLER_REQUIRE) {
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.');
60 105 }
61 106 // Convert the base_uri to a UriInterface
62 107 if (isset($config['base_uri'])) {
63 108 $config['base_uri'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']);
@@ -73,32 +118,36 @@
73 118 * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
74 119 */
75 120 public function __call($method, $args)
76 121 {
122 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s::%s() is deprecated and will be removed in 8.0.', __CLASS__, __FUNCTION__);
77 123 if (\count($args) < 1) {
78 124 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Magic request methods require a URI and optional options array');
79 125 }
80 126 $uri = $args[0];
81 127 $opts = $args[1] ?? [];
82 - return \substr($method, -5) === 'Async' ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) : $this->request($method, $uri, $opts);
128 + $isAsync = \substr($method, -5) === 'Async';
129 + $method = $isAsync ? \substr($method, 0, -5) : $method;
130 + $method = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method);
131 + return $isAsync ? $this->requestAsync($method, $uri, $opts) : $this->request($method, $uri, $opts);
83 132 }
84 133 /**
85 134 * Asynchronously send an HTTP request.
86 135 *
87 136 * @param array $options Request options to apply to the given
88 - * request and to the transfer. See \GuzzleHttp\RequestOptions.
137 + * request and to the transfer. See {@see RequestOptions}.
89 138 */
90 139 public function sendAsync(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
91 140 {
92 141 // Merge the base URI into the request URI if needed.
93 142 $options = $this->prepareDefaults($options);
94 - 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);
95 144 }
96 145 /**
97 146 * Send an HTTP request.
98 147 *
99 148 * @param array $options Request options to apply to the given
100 - * request and to the transfer. See \GuzzleHttp\RequestOptions.
149 + * request and to the transfer. See {@see RequestOptions}.
101 150 *
102 151 * @throws GuzzleException
103 152 */
104 153 public function send(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
@@ -127,22 +176,32 @@
127 176 * template and additional variables to use in the URL template expansion.
128 177 *
129 178 * @param string $method HTTP method
130 179 * @param string|UriInterface $uri URI object or string.
131 - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
180 + * @param array $options Request options to apply. See {@see RequestOptions}.
132 181 */
133 182 public function requestAsync(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
134 183 {
184 + $normalizedMethod = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method);
185 + if ($method !== $normalizedMethod) {
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.');
187 + $method = $normalizedMethod;
188 + }
135 189 $options = $this->prepareDefaults($options);
136 190 // Remove request modifying parameter because it can be done up-front.
137 191 $headers = $options['headers'] ?? [];
192 + $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
193 + if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
194 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
195 + }
138 196 $body = $options['body'] ?? null;
139 - $version = $options['version'] ?? '1.1';
197 + $version = self::normalizeProtocolVersion($options['version'] ?? '1.1');
140 198 // Merge the URI into the base URI.
141 199 $uri = $this->buildUri(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($uri), $options);
142 200 if (\is_array($body)) {
143 201 throw $this->invalidBody();
144 202 }
203 + $body = self::createBodyStream($body);
145 204 $request = new \YoastSEO_Vendor\GuzzleHttp\Psr7\Request($method, $uri, $headers, $body, $version);
146 205 // Remove the option so that they are not doubly-applied.
147 206 unset($options['headers'], $options['body'], $options['version']);
148 207 return $this->transfer($request, $options);
@@ -155,14 +214,19 @@
155 214 * contain the query string as well.
156 215 *
157 216 * @param string $method HTTP method.
158 217 * @param string|UriInterface $uri URI object or string.
159 - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
218 + * @param array $options Request options to apply. See {@see RequestOptions}.
160 219 *
161 220 * @throws GuzzleException
162 221 */
163 222 public function request(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
164 223 {
224 + $normalizedMethod = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToUpper($method);
225 + if ($method !== $normalizedMethod) {
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.');
227 + $method = $normalizedMethod;
228 + }
165 229 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
166 230 return $this->requestAsync($method, $uri, $options)->wait();
167 231 }
168 232 /**
@@ -174,10 +238,8 @@
174 238 *
175 239 * @param string|null $option The config option to retrieve.
176 240 *
177 241 * @return mixed
178 - *
179 - * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
180 242 */
181 243 public function getConfig(?string $option = null)
182 244 {
183 245 return $option === null ? $this->config : $this->config[$option] ?? null;
@@ -186,20 +248,43 @@
186 248 {
187 249 if (isset($config['base_uri'])) {
188 250 $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\UriResolver::resolve(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']), $uri);
189 251 }
190 - if (isset($config['idn_conversion']) && $config['idn_conversion'] !== \false) {
191 - $idnOptions = $config['idn_conversion'] === \true ? \IDNA_DEFAULT : $config['idn_conversion'];
252 + $idnOptions = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null);
253 + if ($idnOptions !== null) {
192 254 $uri = \YoastSEO_Vendor\GuzzleHttp\Utils::idnUriConvert($uri, $idnOptions);
193 255 }
194 - return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
256 + if ($uri->getScheme() === '' && $uri->getHost() !== '') {
257 + $uri = $uri->withScheme('http');
258 + }
259 + return $uri;
195 260 }
196 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 + /**
197 282 * Configures the default options for a client.
198 283 */
199 284 private function configureDefaults(array $config) : void
200 285 {
201 - $defaults = ['allow_redirects' => \YoastSEO_Vendor\GuzzleHttp\RedirectMiddleware::$defaultSettings, 'http_errors' => \true, 'decode_content' => \true, 'verify' => \true, 'cookies' => \false, 'idn_conversion' => \false];
286 + $defaults = ['allow_redirects' => \YoastSEO_Vendor\GuzzleHttp\RedirectMiddleware::$defaultSettings, 'http_errors' => \true, 'decode_content' => \true, 'verify' => \true, 'cookies' => \false, 'idn_conversion' => \false, 'protocols' => ['http', 'https']];
202 287 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
203 288 // We can only trust the HTTP_PROXY environment variable in a CLI
204 289 // process due to the fact that PHP has no reliable mechanism to
205 290 // get environment variables that start with "HTTP_".
@@ -221,15 +306,23 @@
221 306 if (!isset($this->config['headers'])) {
222 307 $this->config['headers'] = ['User-Agent' => \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent()];
223 308 } else {
224 309 // Add the User-Agent header if one was not already set.
310 + $hasUserAgent = \false;
225 311 foreach (\array_keys($this->config['headers']) as $name) {
226 - if (\strtolower($name) === 'user-agent') {
227 - return;
312 + if (\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower((string) $name) === 'user-agent') {
313 + $hasUserAgent = \true;
314 + break;
228 315 }
229 316 }
230 - $this->config['headers']['User-Agent'] = \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent();
317 + if (!$hasUserAgent) {
318 + $this->config['headers']['User-Agent'] = \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent();
319 + }
231 320 }
321 + if (\is_array($this->config['headers'])) {
322 + self::warnAboutInvalidHeaderOptionTypes($this->config['headers']);
323 + self::castDeprecatedHeaderOptionValues($this->config['headers']);
324 + }
232 325 }
233 326 /**
234 327 * Merges default options into the array.
235 328 *
@@ -236,8 +329,9 @@
236 329 * @param array $options Options to modify by reference
237 330 */
238 331 private function prepareDefaults(array $options) : array
239 332 {
333 + self::warnAboutRequestLevelHandler($options);
240 334 $defaults = $this->config;
241 335 if (!empty($defaults['headers'])) {
242 336 // Default headers are only added if they are not present.
243 337 $defaults['_conditional'] = $defaults['headers'];
@@ -261,21 +355,503 @@
261 355 if ($v === null) {
262 356 unset($result[$k]);
263 357 }
264 358 }
265 - return $result;
359 + self::warnAboutInvalidRequestOptionTypes($result);
360 + return self::normalizeDeprecatedRequestOptionValues($result);
266 361 }
267 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 + }
475 + private static function warnAboutRequestLevelHandler(array $options) : void
476 + {
477 + if (!\array_key_exists('handler', $options)) {
478 + return;
479 + }
480 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing the "handler" request option is deprecated; guzzlehttp/guzzle 8.0 will ignore request-level handlers. Configure the handler when creating the Client, or use a separate Client instance for requests that need a different handler.');
481 + }
482 + private static function warnAboutInvalidRequestOptionTypes(array $options) : void
483 + {
484 + if (isset($options['handler']) && !\is_callable($options['handler'])) {
485 + self::warnInvalidRequestOptionType('handler', 'callable', $options['handler']);
486 + }
487 + if (isset($options['allow_redirects']) && \is_array($options['allow_redirects'])) {
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');
491 + }
492 + if (isset($options['auth'])) {
493 + self::warnAboutInvalidAuthOptionTypes($options['auth']);
494 + }
495 + if (isset($options['body']) && \is_array($options['body'])) {
496 + self::warnInvalidRequestOptionType('body', 'resource|string|null|int|float|bool|StreamInterface|(callable&object)|\\Iterator|\\Stringable', $options['body']);
497 + }
498 + self::warnAboutInvalidTlsFileOptionTypes($options, 'cert');
499 + self::warnIfPresentAndNotString($options, 'cert_type');
500 + self::warnIfPresentAndNotNumber($options, 'connect_timeout');
501 + self::warnIfPresentAndNotInt($options, 'crypto_method');
502 + self::warnIfPresentAndNotInt($options, 'crypto_method_max', null, '7.13');
503 + self::warnIfPresentAndNotBoolOrResource($options, 'debug');
504 + self::warnIfPresentAndNotBoolOrString($options, 'decode_content');
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 + }
512 + self::warnIfPresentAndNotBoolOrInt($options, 'expect');
513 + if (isset($options['form_params'])) {
514 + self::warnAboutInvalidFormParamTypes($options['form_params']);
515 + }
516 + if (isset($options['force_ip_resolve']) && !\is_string($options['force_ip_resolve'])) {
517 + self::warnInvalidRequestOptionType('force_ip_resolve', 'string', $options['force_ip_resolve']);
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 + }
522 + if (isset($options['headers'])) {
523 + self::warnAboutInvalidHeaderOptionTypes($options['headers']);
524 + }
525 + self::warnIfPresentAndNotBool($options, 'http_errors');
526 + if (isset($options['multipart'])) {
527 + self::warnAboutInvalidMultipartOptionTypes($options['multipart']);
528 + }
529 + self::warnIfPresentAndNotCallable($options, 'on_headers');
530 + self::warnIfPresentAndNotCallable($options, 'on_stats');
531 + self::warnIfPresentAndNotCallable($options, 'on_trailers', null, '7.14');
532 + self::warnIfPresentAndNotCallable($options, 'progress');
533 + self::warnIfPresentAndNotStringArray($options, 'protocols', \true);
534 + self::warnAboutInvalidProtocolValues($options, 'protocols');
535 + self::warnAboutInvalidProxyOptionTypes($options);
536 + self::warnIfPresentAndNotNumber($options, 'read_timeout');
537 + self::warnIfPresentAndNotInt($options, 'retries');
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) {
539 + self::warnInvalidRequestOptionType('sink', 'resource|string|StreamInterface', $options['sink']);
540 + }
541 + self::warnAboutInvalidTlsFileOptionTypes($options, 'ssl_key');
542 + self::warnIfPresentAndNotString($options, 'ssl_key_type');
543 + self::warnIfPresentAndNotBool($options, 'stream');
544 + self::warnIfPresentAndNotArray($options, 'stream_context', 'array<array-key, mixed>');
545 + self::warnIfPresentAndNotBool($options, 'synchronous');
546 + self::warnIfPresentAndNotNumber($options, 'timeout');
547 + self::warnIfPresentAndNotBoolOrString($options, 'verify');
548 + self::warnIfPresentAndNotStringOrNumber($options, 'version');
549 + self::warnIfPresentAndNotArray($options, 'curl', 'array<int|string, mixed>');
550 + if (isset($options['cookies']) && $options['cookies'] === \true) {
551 + self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']);
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 + }
556 + }
557 + private static function warnAboutInvalidAllowRedirectsOptionTypes(array $allowRedirects) : void
558 + {
559 + self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max');
560 + self::warnIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict');
561 + self::warnIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer');
562 + self::warnIfPresentAndNotStringArray($allowRedirects, 'protocols', \true, 'allow_redirects.protocols');
563 + self::warnAboutInvalidProtocolValues($allowRedirects, 'protocols', 'allow_redirects.protocols');
564 + self::warnIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect');
565 + self::warnIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects');
566 + }
567 + /**
568 + * @param mixed $auth
569 + */
570 + private static function warnAboutInvalidAuthOptionTypes($auth) : void
571 + {
572 + if ($auth === \false || \is_string($auth) || $auth === []) {
573 + return;
574 + }
575 + if (!\is_array($auth)) {
576 + self::warnInvalidRequestOptionType('auth', 'array{0: string, 1: string, 2?: string|null}|string|false|null', $auth);
577 + return;
578 + }
579 + if (!\array_key_exists(0, $auth) || !\is_string($auth[0])) {
580 + self::warnInvalidRequestOptionType('auth.0', 'string', $auth[0] ?? null);
581 + }
582 + if (!\array_key_exists(1, $auth) || !\is_string($auth[1])) {
583 + self::warnInvalidRequestOptionType('auth.1', 'string', $auth[1] ?? null);
584 + }
585 + if (\array_key_exists(2, $auth) && $auth[2] !== null && !\is_string($auth[2])) {
586 + self::warnInvalidRequestOptionType('auth.2', 'string|null', $auth[2]);
587 + }
588 + }
589 + /**
590 + * @param mixed $value
591 + */
592 + private static function warnAboutInvalidFormParamTypes($value) : void
593 + {
594 + if (!\is_array($value)) {
595 + self::warnInvalidRequestOptionType('form_params', 'array<array-key, string|int|float|bool|null|array>', $value);
596 + return;
597 + }
598 + self::warnAboutInvalidFormParamArray($value, 'form_params');
599 + }
600 + private static function warnAboutInvalidFormParamArray(array $values, string $path) : bool
601 + {
602 + foreach ($values as $key => $item) {
603 + $itemPath = $path . '.' . (string) $key;
604 + if (\is_array($item)) {
605 + if (!self::warnAboutInvalidFormParamArray($item, $itemPath)) {
606 + return \false;
607 + }
608 + continue;
609 + }
610 + if ($item !== null && !\is_scalar($item)) {
611 + self::warnInvalidRequestOptionType($itemPath, 'string|int|float|bool|null|array', $item);
612 + return \false;
613 + }
614 + }
615 + return \true;
616 + }
617 + /**
618 + * @param mixed $headers
619 + */
620 + private static function warnAboutInvalidHeaderOptionTypes($headers) : void
621 + {
622 + if (!\is_array($headers)) {
623 + self::warnInvalidRequestOptionType('headers', 'array<array-key, string|non-empty-array<array-key, string>>|null', $headers);
624 + return;
625 + }
626 + foreach ($headers as $name => $value) {
627 + $path = 'headers.' . (string) $name;
628 + if (\is_array($value)) {
629 + if ($value === []) {
630 + self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
631 + break;
632 + }
633 + foreach ($value as $index => $item) {
634 + if (!\is_string($item)) {
635 + self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item);
636 + break 2;
637 + }
638 + }
639 + } elseif (!\is_string($value)) {
640 + self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
641 + break;
642 + }
643 + }
644 + }
645 + /**
646 + * @param mixed $multipart
647 + */
648 + private static function warnAboutInvalidMultipartOptionTypes($multipart) : void
649 + {
650 + if (!\is_array($multipart)) {
651 + self::warnInvalidRequestOptionType('multipart', 'array<array-key, array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}>', $multipart);
652 + return;
653 + }
654 + foreach ($multipart as $index => $part) {
655 + $path = 'multipart.' . (string) $index;
656 + if (!\is_array($part)) {
657 + self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
658 + return;
659 + }
660 + if (!\array_key_exists('name', $part) || !\is_string($part['name']) && !\is_int($part['name'])) {
661 + self::warnInvalidRequestOptionType($path . '.name', 'string|int', $part['name'] ?? null);
662 + }
663 + if (!\array_key_exists('contents', $part)) {
664 + self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
665 + }
666 + if (\array_key_exists('headers', $part)) {
667 + if (!\is_array($part['headers'])) {
668 + self::warnInvalidRequestOptionType($path . '.headers', 'array<array-key, string>', $part['headers']);
669 + } else {
670 + foreach ($part['headers'] as $name => $value) {
671 + if (!\is_string($value)) {
672 + self::warnInvalidRequestOptionType($path . '.headers.' . (string) $name, 'string', $value);
673 + break 2;
674 + }
675 + }
676 + }
677 + }
678 + if (\array_key_exists('filename', $part) && !\is_string($part['filename'])) {
679 + self::warnInvalidRequestOptionType($path . '.filename', 'string', $part['filename']);
680 + }
681 + }
682 + }
683 + private static function warnAboutInvalidProxyOptionTypes(array $options) : void
684 + {
685 + if (!isset($options['proxy'])) {
686 + return;
687 + }
688 + if (!\is_string($options['proxy']) && !\is_array($options['proxy'])) {
689 + self::warnInvalidRequestOptionType('proxy', 'string|array{http?: string|null, https?: string|null, no?: string|array<array-key, string>|null}', $options['proxy']);
690 + return;
691 + }
692 + if (!\is_array($options['proxy'])) {
693 + return;
694 + }
695 + foreach (['http', 'https'] as $scheme) {
696 + if (\array_key_exists($scheme, $options['proxy']) && $options['proxy'][$scheme] !== null && !\is_string($options['proxy'][$scheme])) {
697 + self::warnInvalidRequestOptionType('proxy.' . $scheme, 'string|null', $options['proxy'][$scheme]);
698 + }
699 + }
700 + if (!\array_key_exists('no', $options['proxy']) || $options['proxy']['no'] === null) {
701 + return;
702 + }
703 + if (\is_string($options['proxy']['no'])) {
704 + return;
705 + }
706 + if (!\is_array($options['proxy']['no'])) {
707 + self::warnInvalidRequestOptionType('proxy.no', 'string|array<array-key, string>|null', $options['proxy']['no']);
708 + return;
709 + }
710 + foreach ($options['proxy']['no'] as $index => $noProxy) {
711 + if (!\is_string($noProxy)) {
712 + self::warnInvalidRequestOptionType('proxy.no.' . (string) $index, 'string', $noProxy);
713 + return;
714 + }
715 + }
716 + }
717 + private static function warnAboutInvalidTlsFileOptionTypes(array $options, string $option) : void
718 + {
719 + if (!isset($options[$option])) {
720 + return;
721 + }
722 + if (\is_string($options[$option])) {
723 + return;
724 + }
725 + if (!\is_array($options[$option])) {
726 + self::warnInvalidRequestOptionType($option, 'string|array{0: string, 1?: string}', $options[$option]);
727 + return;
728 + }
729 + if (!\array_key_exists(0, $options[$option]) || !\is_string($options[$option][0])) {
730 + self::warnInvalidRequestOptionType($option . '.0', 'string', $options[$option][0] ?? null);
731 + }
732 + if (\array_key_exists(1, $options[$option]) && $options[$option][1] !== null && !\is_string($options[$option][1])) {
733 + self::warnInvalidRequestOptionType($option . '.1', 'string|null', $options[$option][1]);
734 + }
735 + }
736 + private static function warnIfPresentAndNotArray(array $options, string $option, string $expected) : void
737 + {
738 + if (\array_key_exists($option, $options) && !\is_array($options[$option])) {
739 + self::warnInvalidRequestOptionType($option, $expected, $options[$option]);
740 + }
741 + }
742 + private static function warnIfPresentAndNotBool(array $options, string $option, ?string $path = null) : void
743 + {
744 + if (\array_key_exists($option, $options) && !\is_bool($options[$option])) {
745 + self::warnInvalidRequestOptionType($path ?? $option, 'bool', $options[$option]);
746 + }
747 + }
748 + private static function warnIfPresentAndNotBoolOrInt(array $options, string $option) : void
749 + {
750 + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_int($options[$option])) {
751 + self::warnInvalidRequestOptionType($option, 'bool|int', $options[$option]);
752 + }
753 + }
754 + private static function warnIfPresentAndNotBoolOrResource(array $options, string $option) : void
755 + {
756 + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_resource($options[$option])) {
757 + self::warnInvalidRequestOptionType($option, 'bool|resource', $options[$option]);
758 + }
759 + }
760 + private static function warnIfPresentAndNotBoolOrString(array $options, string $option) : void
761 + {
762 + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) {
763 + self::warnInvalidRequestOptionType($option, 'bool|string', $options[$option]);
764 + }
765 + }
766 + private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null, string $since = '7.11') : void
767 + {
768 + if (\array_key_exists($option, $options) && !\is_callable($options[$option])) {
769 + self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option], $since);
770 + }
771 + }
772 + private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null, string $since = '7.11') : void
773 + {
774 + if (\array_key_exists($option, $options) && !\is_int($options[$option])) {
775 + self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option], $since);
776 + }
777 + }
778 + private static function warnIfPresentAndNotNumber(array $options, string $option) : void
779 + {
780 + if (\array_key_exists($option, $options) && !\is_int($options[$option]) && !\is_float($options[$option])) {
781 + self::warnInvalidRequestOptionType($option, 'int|float', $options[$option]);
782 + }
783 + }
784 + private static function warnIfPresentAndNotString(array $options, string $option) : void
785 + {
786 + if (\array_key_exists($option, $options) && !\is_string($options[$option])) {
787 + self::warnInvalidRequestOptionType($option, 'string', $options[$option]);
788 + }
789 + }
790 + private static function warnIfPresentAndNotStringArray(array $options, string $option, bool $nonEmpty, ?string $path = null) : void
791 + {
792 + if (!\array_key_exists($option, $options)) {
793 + return;
794 + }
795 + $path = $path ?? $option;
796 + $expected = ($nonEmpty ? 'non-empty-' : '') . 'array<array-key, string>';
797 + if (!\is_array($options[$option]) || $nonEmpty && $options[$option] === []) {
798 + self::warnInvalidRequestOptionType($path, $expected, $options[$option]);
799 + return;
800 + }
801 + foreach ($options[$option] as $index => $item) {
802 + if (!\is_string($item)) {
803 + self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item);
804 + return;
805 + }
806 + }
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 + }
823 + private static function warnIfPresentAndNotStringOrNumber(array $options, string $option) : void
824 + {
825 + if (\array_key_exists($option, $options) && !\is_string($options[$option]) && !\is_int($options[$option]) && !\is_float($options[$option])) {
826 + self::warnInvalidRequestOptionType($option, 'string|int|float', $options[$option]);
827 + }
828 + }
829 + /**
830 + * @param mixed $value
831 + */
832 + private static function warnInvalidRequestOptionType(string $option, string $expected, $value, string $since = '7.11') : void
833 + {
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);
835 + }
836 + /**
268 837 * Transfers the given request and applies request options.
269 838 *
270 839 * The URI of the request is not modified and the request options are used
271 840 * as-is without merging in default options.
272 841 *
273 - * @param array $options See \GuzzleHttp\RequestOptions.
842 + * @param array $options See {@see RequestOptions}.
274 843 */
275 844 private function transfer(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
276 845 {
277 846 $request = $this->applyOptions($request, $options);
847 + $protocolVersion = $request->getProtocolVersion();
848 + if ('' === $protocolVersion) {
849 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
850 + $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, ['version' => '1.1']);
851 + } elseif (!self::isProtocolVersionValid($protocolVersion)) {
852 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with a malformed protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject malformed protocol versions.');
853 + }
278 854 /** @var HandlerStack $handler */
279 855 $handler = $options['handler'];
280 856 try {
281 857 return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($handler($request, $options));
@@ -292,9 +868,14 @@
292 868 if (isset($options['headers'])) {
293 869 if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) {
294 870 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The headers array must have header name as keys.');
295 871 }
296 - $modify['set_headers'] = $options['headers'];
872 + $headers = $options['headers'];
873 + $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
874 + if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
875 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
876 + }
877 + $modify['set_headers'] = $headers;
297 878 unset($options['headers']);
298 879 }
299 880 if (isset($options['form_params'])) {
300 881 if (isset($options['multipart'])) {
@@ -299,9 +880,9 @@
299 880 if (isset($options['form_params'])) {
300 881 if (isset($options['multipart'])) {
301 882 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('You cannot use ' . 'form_params and multipart at the same time. Use the ' . 'form_params option if you want to send application/' . 'x-www-form-urlencoded requests, and the multipart ' . 'option to send multipart/form-data requests.');
302 883 }
303 - $options['body'] = \http_build_query($options['form_params'], '', '&');
884 + $options['body'] = \http_build_query(self::normalizeNonFiniteFloats($options['form_params'], 'form_params'), '', '&');
304 885 unset($options['form_params']);
305 886 // Ensure that we don't have the header in different case and set the new value.
306 887 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
307 888 $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
@@ -310,29 +891,34 @@
310 891 $options['body'] = new \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream($options['multipart']);
311 892 unset($options['multipart']);
312 893 }
313 894 if (isset($options['json'])) {
314 - $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;
315 901 unset($options['json']);
316 902 // Ensure that we don't have the header in different case and set the new value.
317 903 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
318 904 $options['_conditional']['Content-Type'] = 'application/json';
319 905 }
320 - if (!empty($options['decode_content']) && $options['decode_content'] !== \true) {
906 + if (isset($options['decode_content']) && \is_string($options['decode_content'])) {
321 907 // Ensure that we don't have the header in different case and set the new value.
322 908 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
323 - $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
909 + $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content'];
324 910 }
325 911 if (isset($options['body'])) {
326 912 if (\is_array($options['body'])) {
327 913 throw $this->invalidBody();
328 914 }
329 - $modify['body'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($options['body']);
915 + $modify['body'] = self::createBodyStream($options['body']);
330 916 unset($options['body']);
331 917 }
332 918 if (!empty($options['auth']) && \is_array($options['auth'])) {
333 919 $value = $options['auth'];
334 - $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
920 + $type = isset($value[2]) ? \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($value[2]) : 'basic';
335 921 switch ($type) {
336 922 case 'basic':
337 923 // Ensure that we don't have the header in different case and set the new value.
338 924 $modify['set_headers'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
@@ -343,8 +929,12 @@
343 929 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
344 930 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
345 931 break;
346 932 case 'ntlm':
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 + }
347 937 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
348 938 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
349 939 break;
350 940 }
@@ -351,9 +941,9 @@
351 941 }
352 942 if (isset($options['query'])) {
353 943 $value = $options['query'];
354 944 if (\is_array($value)) {
355 - $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
945 + $value = \http_build_query(self::normalizeNonFiniteFloats($value, 'query'), '', '&', \PHP_QUERY_RFC3986);
356 946 }
357 947 if (!\is_string($value)) {
358 948 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('query must be a string or array');
359 949 }
@@ -367,9 +957,9 @@
367 957 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('sink must not be a boolean');
368 958 }
369 959 }
370 960 if (isset($options['version'])) {
371 - $modify['version'] = $options['version'];
961 + $modify['version'] = self::normalizeProtocolVersion($options['version']);
372 962 }
373 963 $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
374 964 if ($request->getBody() instanceof \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream) {
375 965 // Use a multipart/form-data POST if a Content-Type is not set.
@@ -381,10 +971,11 @@
381 971 if (isset($options['_conditional'])) {
382 972 // Build up the changes so it's in a single clone of the message.
383 973 $modify = [];
384 974 foreach ($options['_conditional'] as $k => $v) {
385 - if (!$request->hasHeader($k)) {
386 - $modify['set_headers'][$k] = $v;
975 + $name = (string) $k;
976 + if (!$request->hasHeader($name)) {
977 + $modify['set_headers'][$name] = $v;
387 978 }
388 979 }
389 980 $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
390 981 // Don't pass this internal value along to middleware/handlers.
@@ -390,8 +981,109 @@
390 981 // Don't pass this internal value along to middleware/handlers.
391 982 unset($options['_conditional']);
392 983 }
393 984 return $request;
985 + }
986 + /**
987 + * @param array<array-key, mixed> $headers
988 + *
989 + * @return list<string>
990 + */
991 + private static function castDeprecatedHeaderOptionValues(array &$headers) : array
992 + {
993 + $droppedHeaderNames = [];
994 + foreach ($headers as $name => $value) {
995 + if (\is_array($value)) {
996 + if ($value === []) {
997 + $droppedHeaderNames[] = (string) $name;
998 + unset($headers[$name]);
999 + continue;
1000 + }
1001 + foreach ($value as $index => $item) {
1002 + if ($item === null || !\is_string($item) && \is_scalar($item)) {
1003 + if (\is_float($item) && !\is_finite($item)) {
1004 + $item = \is_nan($item) ? 'NAN' : ($item > 0 ? 'INF' : '-INF');
1005 + }
1006 + $value[$index] = (string) $item;
1007 + }
1008 + }
1009 + $headers[$name] = $value;
1010 + continue;
1011 + }
1012 + if ($value === null || !\is_string($value) && \is_scalar($value)) {
1013 + if (\is_float($value) && !\is_finite($value)) {
1014 + $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
1015 + }
1016 + $headers[$name] = (string) $value;
1017 + }
1018 + }
1019 + return $droppedHeaderNames;
1020 + }
1021 + /**
1022 + * @param mixed $body
1023 + */
1024 + private static function createBodyStream($body) : \YoastSEO_Vendor\Psr\Http\Message\StreamInterface
1025 + {
1026 + if ($body instanceof \YoastSEO_Vendor\Psr\Http\Message\StreamInterface) {
1027 + return $body;
1028 + }
1029 + if (\is_resource($body) || $body === null || \is_string($body) || $body instanceof \Iterator) {
1030 + return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($body);
1031 + }
1032 + if (\is_scalar($body)) {
1033 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing a non-string scalar to the "body" request option is deprecated; guzzlehttp/guzzle 8.0 will reject non-string scalar bodies.');
1034 + return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor(self::stringifyScalar($body));
1035 + }
1036 + if (\is_object($body) && \method_exists($body, '__toString')) {
1037 + return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor((string) $body);
1038 + }
1039 + if (\is_callable($body)) {
1040 + return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($body);
1041 + }
1042 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException(\sprintf('Passing %s to request option "body" is invalid; expected resource|string|null|int|float|bool|StreamInterface|callable&object|Iterator|Stringable.', \get_debug_type($body)));
1043 + }
1044 + /**
1045 + * @param bool|float|int|string $value
1046 + */
1047 + private static function stringifyScalar($value) : string
1048 + {
1049 + // Normalize non-finite floats to dodge PHP 8.5's (string) NAN
1050 + // coercion warning while the value is still accepted.
1051 + if (\is_float($value) && !\is_finite($value)) {
1052 + $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
1053 + }
1054 + return (string) $value;
1055 + }
1056 + /**
1057 + * Converts non-finite floats in the array to the strings PHP coerces
1058 + * them to, as implicit coercion of NAN emits a warning on PHP 8.5.
1059 + */
1060 + private static function normalizeNonFiniteFloats(array $values, string $option) : array
1061 + {
1062 + foreach ($values as $key => $value) {
1063 + if (\is_array($value)) {
1064 + $values[$key] = self::normalizeNonFiniteFloats($value, $option);
1065 + } elseif (\is_float($value) && !\is_finite($value)) {
1066 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing a non-finite float in the "%s" request option is deprecated; guzzlehttp/guzzle 8.0 will reject non-finite floats.', $option);
1067 + $values[$key] = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
1068 + }
1069 + }
1070 + return $values;
1071 + }
1072 + /**
1073 + * @param string|int|float $version
1074 + */
1075 + private static function normalizeProtocolVersion($version) : string
1076 + {
1077 + if ('' === $version) {
1078 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing an empty "version" request option is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.');
1079 + return '1.1';
1080 + }
1081 + return \is_float($version) ? \number_format($version, 1, '.', '') : (string) $version;
1082 + }
1083 + private static function isProtocolVersionValid(string $version) : bool
1084 + {
1085 + return 1 === \preg_match('/^\\d+(?:\\.\\d+)?$/D', $version);
394 1086 }
395 1087 /**
396 1088 * Return an InvalidArgumentException with pre-set message.
397 1089 */