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 +793 -123 19.0 → trunk View file →
@@ -2,31 +2,28 @@
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 -use YoastSEO_Vendor\GuzzleHttp\Promise;
8 -use YoastSEO_Vendor\GuzzleHttp\Psr7;
8 +use YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException;
9 +use YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState;
10 +use YoastSEO_Vendor\GuzzleHttp\Handler\CurlVersion;
11 +use YoastSEO_Vendor\GuzzleHttp\Promise as P;
12 +use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface;
9 13 use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
10 14 use YoastSEO_Vendor\Psr\Http\Message\ResponseInterface;
15 +use YoastSEO_Vendor\Psr\Http\Message\StreamInterface;
11 16 use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
12 17 /**
13 - * @method ResponseInterface get(string|UriInterface $uri, array $options = [])
14 - * @method ResponseInterface head(string|UriInterface $uri, array $options = [])
15 - * @method ResponseInterface put(string|UriInterface $uri, array $options = [])
16 - * @method ResponseInterface post(string|UriInterface $uri, array $options = [])
17 - * @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
18 - * @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
19 - * @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
20 - * @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
21 - * @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
22 - * @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
23 - * @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
24 - * @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
18 + * @final
25 19 */
26 -class Client implements \YoastSEO_Vendor\GuzzleHttp\ClientInterface
20 +class Client implements \YoastSEO_Vendor\GuzzleHttp\ClientInterface, \YoastSEO_Vendor\Psr\Http\Client\ClientInterface
27 21 {
28 - /** @var array Default request options */
22 + use ClientTrait;
23 + /**
24 + * @var array Default request options
25 + */
29 26 private $config;
30 27 /**
31 28 * Clients accept an array of constructor parameters.
32 29 *
@@ -51,24 +48,65 @@
51 48 * that enables all of the request options below by attaching all of the
52 49 * default middleware to the handler.
53 50 * - base_uri: (string|UriInterface) Base URI of the client that is merged
54 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.
55 68 * - **: any request option
56 69 *
57 70 * @param array $config Client configuration settings.
58 71 *
59 - * @see \GuzzleHttp\RequestOptions for a list of available request options.
72 + * @see RequestOptions for a list of available request options.
60 73 */
61 74 public function __construct(array $config = [])
62 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']);
63 91 if (!isset($config['handler'])) {
64 - $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));
65 99 } elseif (!\is_callable($config['handler'])) {
66 - throw new \InvalidArgumentException('handler must be a callable');
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.');
67 105 }
68 106 // Convert the base_uri to a UriInterface
69 107 if (isset($config['base_uri'])) {
70 - $config['base_uri'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\uri_for($config['base_uri']);
108 + $config['base_uri'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']);
71 109 }
72 110 $this->configureDefaults($config);
73 111 }
74 112 /**
@@ -74,48 +112,63 @@
74 112 /**
75 113 * @param string $method
76 114 * @param array $args
77 115 *
78 - * @return Promise\PromiseInterface
116 + * @return PromiseInterface|ResponseInterface
117 + *
118 + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
79 119 */
80 120 public function __call($method, $args)
81 121 {
122 + \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.1', '%s::%s() is deprecated and will be removed in 8.0.', __CLASS__, __FUNCTION__);
82 123 if (\count($args) < 1) {
83 - throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
124 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Magic request methods require a URI and optional options array');
84 125 }
85 126 $uri = $args[0];
86 - $opts = isset($args[1]) ? $args[1] : [];
87 - return \substr($method, -5) === 'Async' ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) : $this->request($method, $uri, $opts);
127 + $opts = $args[1] ?? [];
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);
88 132 }
89 133 /**
90 134 * Asynchronously send an HTTP request.
91 135 *
92 136 * @param array $options Request options to apply to the given
93 - * request and to the transfer. See \GuzzleHttp\RequestOptions.
94 - *
95 - * @return Promise\PromiseInterface
137 + * request and to the transfer. See {@see RequestOptions}.
96 138 */
97 - public function sendAsync(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = [])
139 + public function sendAsync(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
98 140 {
99 141 // Merge the base URI into the request URI if needed.
100 142 $options = $this->prepareDefaults($options);
101 - 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);
102 144 }
103 145 /**
104 146 * Send an HTTP request.
105 147 *
106 148 * @param array $options Request options to apply to the given
107 - * request and to the transfer. See \GuzzleHttp\RequestOptions.
149 + * request and to the transfer. See {@see RequestOptions}.
108 150 *
109 - * @return ResponseInterface
110 151 * @throws GuzzleException
111 152 */
112 - public function send(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = [])
153 + public function send(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
113 154 {
114 155 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
115 156 return $this->sendAsync($request, $options)->wait();
116 157 }
117 158 /**
159 + * The HttpClient PSR (PSR-18) specify this method.
160 + *
161 + * {@inheritDoc}
162 + */
163 + public function sendRequest(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
164 + {
165 + $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
166 + $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::ALLOW_REDIRECTS] = \false;
167 + $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::HTTP_ERRORS] = \false;
168 + return $this->sendAsync($request, $options)->wait();
169 + }
170 + /**
118 171 * Create and send an asynchronous HTTP request.
119 172 *
120 173 * Use an absolute path to override the base path of the client, or a
121 174 * relative path to append to the base path of the client. The URL can
@@ -123,24 +176,32 @@
123 176 * template and additional variables to use in the URL template expansion.
124 177 *
125 178 * @param string $method HTTP method
126 179 * @param string|UriInterface $uri URI object or string.
127 - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
128 - *
129 - * @return Promise\PromiseInterface
180 + * @param array $options Request options to apply. See {@see RequestOptions}.
130 181 */
131 - public function requestAsync($method, $uri = '', array $options = [])
182 + public function requestAsync(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
132 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 + }
133 189 $options = $this->prepareDefaults($options);
134 190 // Remove request modifying parameter because it can be done up-front.
135 - $headers = isset($options['headers']) ? $options['headers'] : [];
136 - $body = isset($options['body']) ? $options['body'] : null;
137 - $version = isset($options['version']) ? $options['version'] : '1.1';
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 + }
196 + $body = $options['body'] ?? null;
197 + $version = self::normalizeProtocolVersion($options['version'] ?? '1.1');
138 198 // Merge the URI into the base URI.
139 - $uri = $this->buildUri($uri, $options);
199 + $uri = $this->buildUri(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($uri), $options);
140 200 if (\is_array($body)) {
141 - $this->invalidBody();
201 + throw $this->invalidBody();
142 202 }
203 + $body = self::createBodyStream($body);
143 204 $request = new \YoastSEO_Vendor\GuzzleHttp\Psr7\Request($method, $uri, $headers, $body, $version);
144 205 // Remove the option so that they are not doubly-applied.
145 206 unset($options['headers'], $options['body'], $options['version']);
146 207 return $this->transfer($request, $options);
@@ -153,15 +214,19 @@
153 214 * contain the query string as well.
154 215 *
155 216 * @param string $method HTTP method.
156 217 * @param string|UriInterface $uri URI object or string.
157 - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
218 + * @param array $options Request options to apply. See {@see RequestOptions}.
158 219 *
159 - * @return ResponseInterface
160 220 * @throws GuzzleException
161 221 */
162 - public function request($method, $uri = '', array $options = [])
222 + public function request(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
163 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 + }
164 229 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
165 230 return $this->requestAsync($method, $uri, $options)->wait();
166 231 }
167 232 /**
@@ -174,50 +239,63 @@
174 239 * @param string|null $option The config option to retrieve.
175 240 *
176 241 * @return mixed
177 242 */
178 - public function getConfig($option = null)
243 + public function getConfig(?string $option = null)
179 244 {
180 - return $option === null ? $this->config : (isset($this->config[$option]) ? $this->config[$option] : null);
245 + return $option === null ? $this->config : $this->config[$option] ?? null;
181 246 }
247 + private function buildUri(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, array $config) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
248 + {
249 + if (isset($config['base_uri'])) {
250 + $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\UriResolver::resolve(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']), $uri);
251 + }
252 + $idnOptions = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null);
253 + if ($idnOptions !== null) {
254 + $uri = \YoastSEO_Vendor\GuzzleHttp\Utils::idnUriConvert($uri, $idnOptions);
255 + }
256 + if ($uri->getScheme() === '' && $uri->getHost() !== '') {
257 + $uri = $uri->withScheme('http');
258 + }
259 + return $uri;
260 + }
182 261 /**
183 - * @param string|null $uri
262 + * Whether to preserve an existing Host header when the URI changes.
184 263 *
185 - * @return UriInterface
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.
186 267 */
187 - private function buildUri($uri, array $config)
268 + private static function shouldPreserveHost(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : bool
188 269 {
189 - // for BC we accept null which would otherwise fail in uri_for
190 - $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\uri_for($uri === null ? '' : $uri);
191 - if (isset($config['base_uri'])) {
192 - $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\UriResolver::resolve(\YoastSEO_Vendor\GuzzleHttp\Psr7\uri_for($config['base_uri']), $uri);
270 + if (!$request->hasHeader('Host')) {
271 + return \false;
193 272 }
194 - if (isset($config['idn_conversion']) && $config['idn_conversion'] !== \false) {
195 - $idnOptions = $config['idn_conversion'] === \true ? \IDNA_DEFAULT : $config['idn_conversion'];
196 - $uri = \YoastSEO_Vendor\GuzzleHttp\Utils::idnUriConvert($uri, $idnOptions);
273 + $uri = $request->getUri();
274 + $host = $uri->getHost();
275 + $port = $uri->getPort();
276 + if ($port !== null) {
277 + $host .= ':' . $port;
197 278 }
198 - return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
279 + return $host !== $request->getHeaderLine('Host');
199 280 }
200 281 /**
201 282 * Configures the default options for a client.
202 - *
203 - * @param array $config
204 - * @return void
205 283 */
206 - private function configureDefaults(array $config)
284 + private function configureDefaults(array $config) : void
207 285 {
208 - $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']];
209 287 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
210 288 // We can only trust the HTTP_PROXY environment variable in a CLI
211 289 // process due to the fact that PHP has no reliable mechanism to
212 290 // get environment variables that start with "HTTP_".
213 - if (\php_sapi_name() === 'cli' && \getenv('HTTP_PROXY')) {
214 - $defaults['proxy']['http'] = \getenv('HTTP_PROXY');
291 + if (\PHP_SAPI === 'cli' && ($proxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('HTTP_PROXY'))) {
292 + $defaults['proxy']['http'] = $proxy;
215 293 }
216 - if ($proxy = \getenv('HTTPS_PROXY')) {
294 + if ($proxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('HTTPS_PROXY')) {
217 295 $defaults['proxy']['https'] = $proxy;
218 296 }
219 - if ($noProxy = \getenv('NO_PROXY')) {
297 + if ($noProxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('NO_PROXY')) {
220 298 $cleanedNoProxy = \str_replace(' ', '', $noProxy);
221 299 $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
222 300 }
223 301 $this->config = $config + $defaults;
@@ -225,28 +303,35 @@
225 303 $this->config['cookies'] = new \YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJar();
226 304 }
227 305 // Add the default user-agent header.
228 306 if (!isset($this->config['headers'])) {
229 - $this->config['headers'] = ['User-Agent' => default_user_agent()];
307 + $this->config['headers'] = ['User-Agent' => \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent()];
230 308 } else {
231 309 // Add the User-Agent header if one was not already set.
310 + $hasUserAgent = \false;
232 311 foreach (\array_keys($this->config['headers']) as $name) {
233 - if (\strtolower($name) === 'user-agent') {
234 - return;
312 + if (\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower((string) $name) === 'user-agent') {
313 + $hasUserAgent = \true;
314 + break;
235 315 }
236 316 }
237 - $this->config['headers']['User-Agent'] = default_user_agent();
317 + if (!$hasUserAgent) {
318 + $this->config['headers']['User-Agent'] = \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent();
319 + }
238 320 }
321 + if (\is_array($this->config['headers'])) {
322 + self::warnAboutInvalidHeaderOptionTypes($this->config['headers']);
323 + self::castDeprecatedHeaderOptionValues($this->config['headers']);
324 + }
239 325 }
240 326 /**
241 327 * Merges default options into the array.
242 328 *
243 329 * @param array $options Options to modify by reference
244 - *
245 - * @return array
246 330 */
247 - private function prepareDefaults(array $options)
331 + private function prepareDefaults(array $options) : array
248 332 {
333 + self::warnAboutRequestLevelHandler($options);
249 334 $defaults = $this->config;
250 335 if (!empty($defaults['headers'])) {
251 336 // Default headers are only added if they are not present.
252 337 $defaults['_conditional'] = $defaults['headers'];
@@ -259,9 +344,9 @@
259 344 if ($options['headers'] === null) {
260 345 $defaults['_conditional'] = [];
261 346 unset($options['headers']);
262 347 } elseif (!\is_array($options['headers'])) {
263 - throw new \InvalidArgumentException('headers must be an array');
348 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('headers must be an array');
264 349 }
265 350 }
266 351 // Shallow merge defaults underneath options.
267 352 $result = $options + $defaults;
@@ -270,64 +355,537 @@
270 355 if ($v === null) {
271 356 unset($result[$k]);
272 357 }
273 358 }
274 - return $result;
359 + self::warnAboutInvalidRequestOptionTypes($result);
360 + return self::normalizeDeprecatedRequestOptionValues($result);
275 361 }
276 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 + /**
277 837 * Transfers the given request and applies request options.
278 838 *
279 839 * The URI of the request is not modified and the request options are used
280 840 * as-is without merging in default options.
281 841 *
282 - * @param array $options See \GuzzleHttp\RequestOptions.
283 - *
284 - * @return Promise\PromiseInterface
842 + * @param array $options See {@see RequestOptions}.
285 843 */
286 - private function transfer(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options)
844 + private function transfer(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
287 845 {
288 - // save_to -> sink
289 - if (isset($options['save_to'])) {
290 - $options['sink'] = $options['save_to'];
291 - unset($options['save_to']);
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.');
292 853 }
293 - // exceptions -> http_errors
294 - if (isset($options['exceptions'])) {
295 - $options['http_errors'] = $options['exceptions'];
296 - unset($options['exceptions']);
297 - }
298 - $request = $this->applyOptions($request, $options);
299 854 /** @var HandlerStack $handler */
300 855 $handler = $options['handler'];
301 856 try {
302 - return \YoastSEO_Vendor\GuzzleHttp\Promise\promise_for($handler($request, $options));
857 + return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($handler($request, $options));
303 858 } catch (\Exception $e) {
304 - return \YoastSEO_Vendor\GuzzleHttp\Promise\rejection_for($e);
859 + return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor($e);
305 860 }
306 861 }
307 862 /**
308 863 * Applies the array of request options to a request.
309 - *
310 - * @param RequestInterface $request
311 - * @param array $options
312 - *
313 - * @return RequestInterface
314 864 */
315 - private function applyOptions(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options)
865 + private function applyOptions(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options) : \YoastSEO_Vendor\Psr\Http\Message\RequestInterface
316 866 {
317 867 $modify = ['set_headers' => []];
318 868 if (isset($options['headers'])) {
319 - $modify['set_headers'] = $options['headers'];
869 + if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) {
870 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The headers array must have header name as keys.');
871 + }
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;
320 878 unset($options['headers']);
321 879 }
322 880 if (isset($options['form_params'])) {
323 881 if (isset($options['multipart'])) {
324 - throw new \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.');
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.');
325 883 }
326 - $options['body'] = \http_build_query($options['form_params'], '', '&');
884 + $options['body'] = \http_build_query(self::normalizeNonFiniteFloats($options['form_params'], 'form_params'), '', '&');
327 885 unset($options['form_params']);
328 886 // Ensure that we don't have the header in different case and set the new value.
329 - $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
887 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
330 888 $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
331 889 }
332 890 if (isset($options['multipart'])) {
333 891 $options['body'] = new \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream($options['multipart']);
@@ -333,33 +891,38 @@
333 891 $options['body'] = new \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream($options['multipart']);
334 892 unset($options['multipart']);
335 893 }
336 894 if (isset($options['json'])) {
337 - $options['body'] = \YoastSEO_Vendor\GuzzleHttp\json_encode($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;
338 901 unset($options['json']);
339 902 // Ensure that we don't have the header in different case and set the new value.
340 - $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
903 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
341 904 $options['_conditional']['Content-Type'] = 'application/json';
342 905 }
343 - if (!empty($options['decode_content']) && $options['decode_content'] !== \true) {
906 + if (isset($options['decode_content']) && \is_string($options['decode_content'])) {
344 907 // Ensure that we don't have the header in different case and set the new value.
345 - $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
346 - $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
908 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
909 + $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content'];
347 910 }
348 911 if (isset($options['body'])) {
349 912 if (\is_array($options['body'])) {
350 - $this->invalidBody();
913 + throw $this->invalidBody();
351 914 }
352 - $modify['body'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\stream_for($options['body']);
915 + $modify['body'] = self::createBodyStream($options['body']);
353 916 unset($options['body']);
354 917 }
355 918 if (!empty($options['auth']) && \is_array($options['auth'])) {
356 919 $value = $options['auth'];
357 - $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
920 + $type = isset($value[2]) ? \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($value[2]) : 'basic';
358 921 switch ($type) {
359 922 case 'basic':
360 923 // Ensure that we don't have the header in different case and set the new value.
361 - $modify['set_headers'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
924 + $modify['set_headers'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
362 925 $modify['set_headers']['Authorization'] = 'Basic ' . \base64_encode("{$value[0]}:{$value[1]}");
363 926 break;
364 927 case 'digest':
365 928 // @todo: Do not rely on curl
@@ -366,8 +929,12 @@
366 929 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
367 930 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
368 931 break;
369 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 + }
370 937 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
371 938 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
372 939 break;
373 940 }
@@ -374,12 +941,12 @@
374 941 }
375 942 if (isset($options['query'])) {
376 943 $value = $options['query'];
377 944 if (\is_array($value)) {
378 - $value = \http_build_query($value, null, '&', \PHP_QUERY_RFC3986);
945 + $value = \http_build_query(self::normalizeNonFiniteFloats($value, 'query'), '', '&', \PHP_QUERY_RFC3986);
379 946 }
380 947 if (!\is_string($value)) {
381 - throw new \InvalidArgumentException('query must be a string or array');
948 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('query must be a string or array');
382 949 }
383 950 $modify['query'] = $value;
384 951 unset($options['query']);
385 952 }
@@ -386,16 +953,19 @@
386 953 // Ensure that sink is not an invalid value.
387 954 if (isset($options['sink'])) {
388 955 // TODO: Add more sink validation?
389 956 if (\is_bool($options['sink'])) {
390 - throw new \InvalidArgumentException('sink must not be a boolean');
957 + throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('sink must not be a boolean');
391 958 }
392 959 }
393 - $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\modify_request($request, $modify);
960 + if (isset($options['version'])) {
961 + $modify['version'] = self::normalizeProtocolVersion($options['version']);
962 + }
963 + $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
394 964 if ($request->getBody() instanceof \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream) {
395 965 // Use a multipart/form-data POST if a Content-Type is not set.
396 966 // Ensure that we don't have the header in different case and set the new value.
397 - $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
967 + $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
398 968 $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' . $request->getBody()->getBoundary();
399 969 }
400 970 // Merge in conditional headers if they are not present.
401 971 if (isset($options['_conditional'])) {
@@ -401,13 +971,14 @@
401 971 if (isset($options['_conditional'])) {
402 972 // Build up the changes so it's in a single clone of the message.
403 973 $modify = [];
404 974 foreach ($options['_conditional'] as $k => $v) {
405 - if (!$request->hasHeader($k)) {
406 - $modify['set_headers'][$k] = $v;
975 + $name = (string) $k;
976 + if (!$request->hasHeader($name)) {
977 + $modify['set_headers'][$name] = $v;
407 978 }
408 979 }
409 - $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\modify_request($request, $modify);
980 + $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
410 981 // Don't pass this internal value along to middleware/handlers.
411 982 unset($options['_conditional']);
412 983 }
413 984 return $request;
@@ -412,13 +983,112 @@
412 983 }
413 984 return $request;
414 985 }
415 986 /**
416 - * Throw Exception with pre-set message.
417 - * @return void
418 - * @throws \InvalidArgumentException Invalid body.
987 + * @param array<array-key, mixed> $headers
988 + *
989 + * @return list<string>
419 990 */
420 - private function invalidBody()
991 + private static function castDeprecatedHeaderOptionValues(array &$headers) : array
421 992 {
422 - throw new \InvalidArgumentException('Passing in the "body" request ' . 'option as an array to send a POST request has been deprecated. ' . 'Please use the "form_params" request option to send a ' . 'application/x-www-form-urlencoded request, or the "multipart" ' . 'request option to send a multipart/form-data request.');
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);
1086 + }
1087 + /**
1088 + * Return an InvalidArgumentException with pre-set message.
1089 + */
1090 + private function invalidBody() : \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException
1091 + {
1092 + return new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Passing in the "body" request ' . 'option as an array to send a request is not supported. ' . 'Please use the "form_params" request option to send a ' . 'application/x-www-form-urlencoded request, or the "multipart" ' . 'request option to send a multipart/form-data request.');
423 1093 }
424 1094 }