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

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

881 lines 44.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YoastSEO_Vendor\GuzzleHttp;
4
5 use YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJar;
6 use YoastSEO_Vendor\GuzzleHttp\Exception\GuzzleException;
7 use YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException;
8 use YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState;
9 use YoastSEO_Vendor\GuzzleHttp\Promise as P;
10 use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface;
11 use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
12 use YoastSEO_Vendor\Psr\Http\Message\ResponseInterface;
13 use YoastSEO_Vendor\Psr\Http\Message\StreamInterface;
14 use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
15 /**
16 * @final
17 */
18 class Client implements \YoastSEO_Vendor\GuzzleHttp\ClientInterface, \YoastSEO_Vendor\Psr\Http\Client\ClientInterface
19 {
20 use ClientTrait;
21 /**
22 * @var array Default request options
23 */
24 private $config;
25 /**
26 * Clients accept an array of constructor parameters.
27 *
28 * Here's an example of creating a client using a base_uri and an array of
29 * default request options to apply to each request:
30 *
31 * $client = new Client([
32 * 'base_uri' => 'http://www.foo.com/1.0/',
33 * 'timeout' => 0,
34 * 'allow_redirects' => false,
35 * 'proxy' => '192.168.16.1:10'
36 * ]);
37 *
38 * Client configuration settings include the following options:
39 *
40 * - handler: (callable) Function that transfers HTTP requests over the
41 * wire. The function is called with a Psr7\Http\Message\RequestInterface
42 * and array of transfer options, and must return a
43 * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
44 * Psr7\Http\Message\ResponseInterface on success.
45 * If no handler is provided, a default handler will be created
46 * that enables all of the request options below by attaching all of the
47 * default middleware to the handler.
48 * - base_uri: (string|UriInterface) Base URI of the client that is merged
49 * into relative URIs. Can be a string or instance of UriInterface.
50 * - transport_sharing: (string|null) Transport sharing mode for the
51 * default handler. Accepts TransportSharing::* or null. Defaults to null.
52 * - **: any request option
53 *
54 * @param array $config Client configuration settings.
55 *
56 * @see RequestOptions for a list of available request options.
57 */
58 public function __construct(array $config = [])
59 {
60 $transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null;
61 $transportSharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
62 unset($config['transport_sharing']);
63 if (!isset($config['handler'])) {
64 $config['handler'] = $transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::NONE ? \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create() : \YoastSEO_Vendor\GuzzleHttp\HandlerStack::create(\YoastSEO_Vendor\GuzzleHttp\Utils::chooseHandler(['transport_sharing' => $transportSharingMode]));
65 } elseif (!\is_callable($config['handler'])) {
66 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('handler must be a callable');
67 } elseif ($transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::HANDLER_REQUIRE) {
68 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler or CurlMultiHandler when providing a custom cURL handler.');
69 }
70 // Convert the base_uri to a UriInterface
71 if (isset($config['base_uri'])) {
72 $config['base_uri'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']);
73 }
74 $this->configureDefaults($config);
75 }
76 /**
77 * @param string $method
78 * @param array $args
79 *
80 * @return PromiseInterface|ResponseInterface
81 *
82 * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
83 */
84 public function __call($method, $args)
85 {
86 if (\count($args) < 1) {
87 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('Magic request methods require a URI and optional options array');
88 }
89 $uri = $args[0];
90 $opts = $args[1] ?? [];
91 $isAsync = \substr($method, -5) === 'Async';
92 $method = $isAsync ? \substr($method, 0, -5) : $method;
93 $method = \strtoupper($method);
94 return $isAsync ? $this->requestAsync($method, $uri, $opts) : $this->request($method, $uri, $opts);
95 }
96 /**
97 * Asynchronously send an HTTP request.
98 *
99 * @param array $options Request options to apply to the given
100 * request and to the transfer. See {@see RequestOptions}.
101 */
102 public function sendAsync(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
103 {
104 // Merge the base URI into the request URI if needed.
105 $options = $this->prepareDefaults($options);
106 return $this->transfer($request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), $options);
107 }
108 /**
109 * Send an HTTP request.
110 *
111 * @param array $options Request options to apply to the given
112 * request and to the transfer. See {@see RequestOptions}.
113 *
114 * @throws GuzzleException
115 */
116 public function send(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
117 {
118 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
119 return $this->sendAsync($request, $options)->wait();
120 }
121 /**
122 * The HttpClient PSR (PSR-18) specify this method.
123 *
124 * {@inheritDoc}
125 */
126 public function sendRequest(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
127 {
128 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
129 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::ALLOW_REDIRECTS] = \false;
130 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::HTTP_ERRORS] = \false;
131 return $this->sendAsync($request, $options)->wait();
132 }
133 /**
134 * Create and send an asynchronous HTTP request.
135 *
136 * Use an absolute path to override the base path of the client, or a
137 * relative path to append to the base path of the client. The URL can
138 * contain the query string as well. Use an array to provide a URL
139 * template and additional variables to use in the URL template expansion.
140 *
141 * @param string $method HTTP method
142 * @param string|UriInterface $uri URI object or string.
143 * @param array $options Request options to apply. See {@see RequestOptions}.
144 */
145 public function requestAsync(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
146 {
147 $normalizedMethod = \strtoupper($method);
148 if ($method !== $normalizedMethod) {
149 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::requestAsync() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.');
150 $method = $normalizedMethod;
151 }
152 $options = $this->prepareDefaults($options);
153 // Remove request modifying parameter because it can be done up-front.
154 $headers = $options['headers'] ?? [];
155 $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
156 if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
157 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
158 }
159 $body = $options['body'] ?? null;
160 $version = self::normalizeProtocolVersion($options['version'] ?? '1.1');
161 // Merge the URI into the base URI.
162 $uri = $this->buildUri(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($uri), $options);
163 if (\is_array($body)) {
164 throw $this->invalidBody();
165 }
166 $body = self::createBodyStream($body);
167 $request = new \YoastSEO_Vendor\GuzzleHttp\Psr7\Request($method, $uri, $headers, $body, $version);
168 // Remove the option so that they are not doubly-applied.
169 unset($options['headers'], $options['body'], $options['version']);
170 return $this->transfer($request, $options);
171 }
172 /**
173 * Create and send an HTTP request.
174 *
175 * Use an absolute path to override the base path of the client, or a
176 * relative path to append to the base path of the client. The URL can
177 * contain the query string as well.
178 *
179 * @param string $method HTTP method.
180 * @param string|UriInterface $uri URI object or string.
181 * @param array $options Request options to apply. See {@see RequestOptions}.
182 *
183 * @throws GuzzleException
184 */
185 public function request(string $method, $uri = '', array $options = []) : \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface
186 {
187 $normalizedMethod = \strtoupper($method);
188 if ($method !== $normalizedMethod) {
189 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::request() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.');
190 $method = $normalizedMethod;
191 }
192 $options[\YoastSEO_Vendor\GuzzleHttp\RequestOptions::SYNCHRONOUS] = \true;
193 return $this->requestAsync($method, $uri, $options)->wait();
194 }
195 /**
196 * Get a client configuration option.
197 *
198 * These options include default request options of the client, a "handler"
199 * (if utilized by the concrete client), and a "base_uri" if utilized by
200 * the concrete client.
201 *
202 * @param string|null $option The config option to retrieve.
203 *
204 * @return mixed
205 */
206 public function getConfig(?string $option = null)
207 {
208 return $option === null ? $this->config : $this->config[$option] ?? null;
209 }
210 private function buildUri(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, array $config) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
211 {
212 if (isset($config['base_uri'])) {
213 $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\UriResolver::resolve(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::uriFor($config['base_uri']), $uri);
214 }
215 $idnOptions = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null);
216 if ($idnOptions !== null) {
217 $uri = \YoastSEO_Vendor\GuzzleHttp\Utils::idnUriConvert($uri, $idnOptions);
218 }
219 return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
220 }
221 /**
222 * Configures the default options for a client.
223 */
224 private function configureDefaults(array $config) : void
225 {
226 $defaults = ['allow_redirects' => \YoastSEO_Vendor\GuzzleHttp\RedirectMiddleware::$defaultSettings, 'http_errors' => \true, 'decode_content' => \true, 'verify' => \true, 'cookies' => \false, 'idn_conversion' => \false, 'protocols' => ['http', 'https']];
227 // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
228 // We can only trust the HTTP_PROXY environment variable in a CLI
229 // process due to the fact that PHP has no reliable mechanism to
230 // get environment variables that start with "HTTP_".
231 if (\PHP_SAPI === 'cli' && ($proxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('HTTP_PROXY'))) {
232 $defaults['proxy']['http'] = $proxy;
233 }
234 if ($proxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('HTTPS_PROXY')) {
235 $defaults['proxy']['https'] = $proxy;
236 }
237 if ($noProxy = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('NO_PROXY')) {
238 $cleanedNoProxy = \str_replace(' ', '', $noProxy);
239 $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
240 }
241 $this->config = $config + $defaults;
242 if (!empty($config['cookies']) && $config['cookies'] === \true) {
243 $this->config['cookies'] = new \YoastSEO_Vendor\GuzzleHttp\Cookie\CookieJar();
244 }
245 // Add the default user-agent header.
246 if (!isset($this->config['headers'])) {
247 $this->config['headers'] = ['User-Agent' => \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent()];
248 } else {
249 // Add the User-Agent header if one was not already set.
250 $hasUserAgent = \false;
251 foreach (\array_keys($this->config['headers']) as $name) {
252 if (\strtolower((string) $name) === 'user-agent') {
253 $hasUserAgent = \true;
254 break;
255 }
256 }
257 if (!$hasUserAgent) {
258 $this->config['headers']['User-Agent'] = \YoastSEO_Vendor\GuzzleHttp\Utils::defaultUserAgent();
259 }
260 }
261 if (\is_array($this->config['headers'])) {
262 self::warnAboutInvalidHeaderOptionTypes($this->config['headers']);
263 self::castDeprecatedHeaderOptionValues($this->config['headers']);
264 }
265 }
266 /**
267 * Merges default options into the array.
268 *
269 * @param array $options Options to modify by reference
270 */
271 private function prepareDefaults(array $options) : array
272 {
273 self::warnAboutRequestLevelHandler($options);
274 $defaults = $this->config;
275 if (!empty($defaults['headers'])) {
276 // Default headers are only added if they are not present.
277 $defaults['_conditional'] = $defaults['headers'];
278 unset($defaults['headers']);
279 }
280 // Special handling for headers is required as they are added as
281 // conditional headers and as headers passed to a request ctor.
282 if (\array_key_exists('headers', $options)) {
283 // Allows default headers to be unset.
284 if ($options['headers'] === null) {
285 $defaults['_conditional'] = [];
286 unset($options['headers']);
287 } elseif (!\is_array($options['headers'])) {
288 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('headers must be an array');
289 }
290 }
291 // Shallow merge defaults underneath options.
292 $result = $options + $defaults;
293 // Remove null values.
294 foreach ($result as $k => $v) {
295 if ($v === null) {
296 unset($result[$k]);
297 }
298 }
299 self::warnAboutInvalidRequestOptionTypes($result);
300 return $result;
301 }
302 private static function warnAboutRequestLevelHandler(array $options) : void
303 {
304 if (!\array_key_exists('handler', $options)) {
305 return;
306 }
307 \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.');
308 }
309 private static function warnAboutInvalidRequestOptionTypes(array $options) : void
310 {
311 if (isset($options['handler']) && !\is_callable($options['handler'])) {
312 self::warnInvalidRequestOptionType('handler', 'callable', $options['handler']);
313 }
314 if (isset($options['allow_redirects']) && \is_array($options['allow_redirects'])) {
315 self::warnAboutInvalidAllowRedirectsOptionTypes($options['allow_redirects']);
316 }
317 if (isset($options['auth'])) {
318 self::warnAboutInvalidAuthOptionTypes($options['auth']);
319 }
320 if (isset($options['body']) && \is_array($options['body'])) {
321 self::warnInvalidRequestOptionType('body', 'resource|string|null|int|float|bool|StreamInterface|(callable&object)|\\Iterator|\\Stringable', $options['body']);
322 }
323 self::warnAboutInvalidTlsFileOptionTypes($options, 'cert');
324 self::warnIfPresentAndNotString($options, 'cert_type');
325 self::warnIfPresentAndNotNumber($options, 'connect_timeout');
326 self::warnIfPresentAndNotInt($options, 'crypto_method');
327 self::warnIfPresentAndNotBoolOrResource($options, 'debug');
328 self::warnIfPresentAndNotBoolOrString($options, 'decode_content');
329 self::warnIfPresentAndNotNumber($options, 'delay');
330 self::warnIfPresentAndNotBoolOrInt($options, 'expect');
331 if (isset($options['form_params'])) {
332 self::warnAboutInvalidFormParamTypes($options['form_params']);
333 }
334 if (isset($options['force_ip_resolve']) && !\is_string($options['force_ip_resolve'])) {
335 self::warnInvalidRequestOptionType('force_ip_resolve', 'string', $options['force_ip_resolve']);
336 }
337 if (isset($options['headers'])) {
338 self::warnAboutInvalidHeaderOptionTypes($options['headers']);
339 }
340 self::warnIfPresentAndNotBool($options, 'http_errors');
341 if (isset($options['multipart'])) {
342 self::warnAboutInvalidMultipartOptionTypes($options['multipart']);
343 }
344 self::warnIfPresentAndNotCallable($options, 'on_headers');
345 self::warnIfPresentAndNotCallable($options, 'on_stats');
346 self::warnIfPresentAndNotCallable($options, 'progress');
347 self::warnIfPresentAndNotStringArray($options, 'protocols', \true);
348 self::warnAboutInvalidProxyOptionTypes($options);
349 self::warnIfPresentAndNotNumber($options, 'read_timeout');
350 self::warnIfPresentAndNotInt($options, 'retries');
351 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) {
352 self::warnInvalidRequestOptionType('sink', 'resource|string|StreamInterface', $options['sink']);
353 }
354 self::warnAboutInvalidTlsFileOptionTypes($options, 'ssl_key');
355 self::warnIfPresentAndNotString($options, 'ssl_key_type');
356 self::warnIfPresentAndNotBool($options, 'stream');
357 self::warnIfPresentAndNotArray($options, 'stream_context', 'array<array-key, mixed>');
358 self::warnIfPresentAndNotBool($options, 'synchronous');
359 self::warnIfPresentAndNotNumber($options, 'timeout');
360 self::warnIfPresentAndNotBoolOrString($options, 'verify');
361 self::warnIfPresentAndNotStringOrNumber($options, 'version');
362 self::warnIfPresentAndNotArray($options, 'curl', 'array<int|string, mixed>');
363 if (isset($options['cookies']) && $options['cookies'] === \true) {
364 self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']);
365 }
366 }
367 private static function warnAboutInvalidAllowRedirectsOptionTypes(array $allowRedirects) : void
368 {
369 self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max');
370 self::warnIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict');
371 self::warnIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer');
372 self::warnIfPresentAndNotStringArray($allowRedirects, 'protocols', \true, 'allow_redirects.protocols');
373 self::warnIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect');
374 self::warnIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects');
375 }
376 /**
377 * @param mixed $auth
378 */
379 private static function warnAboutInvalidAuthOptionTypes($auth) : void
380 {
381 if ($auth === \false || \is_string($auth) || $auth === []) {
382 return;
383 }
384 if (!\is_array($auth)) {
385 self::warnInvalidRequestOptionType('auth', 'array{0: string, 1: string, 2?: string|null}|string|false|null', $auth);
386 return;
387 }
388 if (!\array_key_exists(0, $auth) || !\is_string($auth[0])) {
389 self::warnInvalidRequestOptionType('auth.0', 'string', $auth[0] ?? null);
390 }
391 if (!\array_key_exists(1, $auth) || !\is_string($auth[1])) {
392 self::warnInvalidRequestOptionType('auth.1', 'string', $auth[1] ?? null);
393 }
394 if (\array_key_exists(2, $auth) && $auth[2] !== null && !\is_string($auth[2])) {
395 self::warnInvalidRequestOptionType('auth.2', 'string|null', $auth[2]);
396 }
397 }
398 /**
399 * @param mixed $value
400 */
401 private static function warnAboutInvalidFormParamTypes($value) : void
402 {
403 if (!\is_array($value)) {
404 self::warnInvalidRequestOptionType('form_params', 'array<array-key, string|int|float|bool|null|array>', $value);
405 return;
406 }
407 self::warnAboutInvalidFormParamArray($value, 'form_params');
408 }
409 private static function warnAboutInvalidFormParamArray(array $values, string $path) : bool
410 {
411 foreach ($values as $key => $item) {
412 $itemPath = $path . '.' . (string) $key;
413 if (\is_array($item)) {
414 if (!self::warnAboutInvalidFormParamArray($item, $itemPath)) {
415 return \false;
416 }
417 continue;
418 }
419 if ($item !== null && !\is_scalar($item)) {
420 self::warnInvalidRequestOptionType($itemPath, 'string|int|float|bool|null|array', $item);
421 return \false;
422 }
423 }
424 return \true;
425 }
426 /**
427 * @param mixed $headers
428 */
429 private static function warnAboutInvalidHeaderOptionTypes($headers) : void
430 {
431 if (!\is_array($headers)) {
432 self::warnInvalidRequestOptionType('headers', 'array<array-key, string|non-empty-array<array-key, string>>|null', $headers);
433 return;
434 }
435 foreach ($headers as $name => $value) {
436 $path = 'headers.' . (string) $name;
437 if (\is_array($value)) {
438 if ($value === []) {
439 self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
440 break;
441 }
442 foreach ($value as $index => $item) {
443 if (!\is_string($item)) {
444 self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item);
445 break 2;
446 }
447 }
448 } elseif (!\is_string($value)) {
449 self::warnInvalidRequestOptionType($path, 'string|non-empty-array<array-key, string>', $value);
450 break;
451 }
452 }
453 }
454 /**
455 * @param mixed $multipart
456 */
457 private static function warnAboutInvalidMultipartOptionTypes($multipart) : void
458 {
459 if (!\is_array($multipart)) {
460 self::warnInvalidRequestOptionType('multipart', 'array<array-key, array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}>', $multipart);
461 return;
462 }
463 foreach ($multipart as $index => $part) {
464 $path = 'multipart.' . (string) $index;
465 if (!\is_array($part)) {
466 self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
467 return;
468 }
469 if (!\array_key_exists('name', $part) || !\is_string($part['name']) && !\is_int($part['name'])) {
470 self::warnInvalidRequestOptionType($path . '.name', 'string|int', $part['name'] ?? null);
471 }
472 if (!\array_key_exists('contents', $part)) {
473 self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array<array-key, string>, filename?: string}', $part);
474 }
475 if (\array_key_exists('headers', $part)) {
476 if (!\is_array($part['headers'])) {
477 self::warnInvalidRequestOptionType($path . '.headers', 'array<array-key, string>', $part['headers']);
478 } else {
479 foreach ($part['headers'] as $name => $value) {
480 if (!\is_string($value)) {
481 self::warnInvalidRequestOptionType($path . '.headers.' . (string) $name, 'string', $value);
482 break 2;
483 }
484 }
485 }
486 }
487 if (\array_key_exists('filename', $part) && !\is_string($part['filename'])) {
488 self::warnInvalidRequestOptionType($path . '.filename', 'string', $part['filename']);
489 }
490 }
491 }
492 private static function warnAboutInvalidProxyOptionTypes(array $options) : void
493 {
494 if (!isset($options['proxy'])) {
495 return;
496 }
497 if (!\is_string($options['proxy']) && !\is_array($options['proxy'])) {
498 self::warnInvalidRequestOptionType('proxy', 'string|array{http?: string|null, https?: string|null, no?: string|array<array-key, string>|null}', $options['proxy']);
499 return;
500 }
501 if (!\is_array($options['proxy'])) {
502 return;
503 }
504 foreach (['http', 'https'] as $scheme) {
505 if (\array_key_exists($scheme, $options['proxy']) && $options['proxy'][$scheme] !== null && !\is_string($options['proxy'][$scheme])) {
506 self::warnInvalidRequestOptionType('proxy.' . $scheme, 'string|null', $options['proxy'][$scheme]);
507 }
508 }
509 if (!\array_key_exists('no', $options['proxy']) || $options['proxy']['no'] === null) {
510 return;
511 }
512 if (\is_string($options['proxy']['no'])) {
513 return;
514 }
515 if (!\is_array($options['proxy']['no'])) {
516 self::warnInvalidRequestOptionType('proxy.no', 'string|array<array-key, string>|null', $options['proxy']['no']);
517 return;
518 }
519 foreach ($options['proxy']['no'] as $index => $noProxy) {
520 if (!\is_string($noProxy)) {
521 self::warnInvalidRequestOptionType('proxy.no.' . (string) $index, 'string', $noProxy);
522 return;
523 }
524 }
525 }
526 private static function warnAboutInvalidTlsFileOptionTypes(array $options, string $option) : void
527 {
528 if (!isset($options[$option])) {
529 return;
530 }
531 if (\is_string($options[$option])) {
532 return;
533 }
534 if (!\is_array($options[$option])) {
535 self::warnInvalidRequestOptionType($option, 'string|array{0: string, 1?: string}', $options[$option]);
536 return;
537 }
538 if (!\array_key_exists(0, $options[$option]) || !\is_string($options[$option][0])) {
539 self::warnInvalidRequestOptionType($option . '.0', 'string', $options[$option][0] ?? null);
540 }
541 if (\array_key_exists(1, $options[$option]) && $options[$option][1] !== null && !\is_string($options[$option][1])) {
542 self::warnInvalidRequestOptionType($option . '.1', 'string|null', $options[$option][1]);
543 }
544 }
545 private static function warnIfPresentAndNotArray(array $options, string $option, string $expected) : void
546 {
547 if (\array_key_exists($option, $options) && !\is_array($options[$option])) {
548 self::warnInvalidRequestOptionType($option, $expected, $options[$option]);
549 }
550 }
551 private static function warnIfPresentAndNotBool(array $options, string $option, ?string $path = null) : void
552 {
553 if (\array_key_exists($option, $options) && !\is_bool($options[$option])) {
554 self::warnInvalidRequestOptionType($path ?? $option, 'bool', $options[$option]);
555 }
556 }
557 private static function warnIfPresentAndNotBoolOrInt(array $options, string $option) : void
558 {
559 if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_int($options[$option])) {
560 self::warnInvalidRequestOptionType($option, 'bool|int', $options[$option]);
561 }
562 }
563 private static function warnIfPresentAndNotBoolOrResource(array $options, string $option) : void
564 {
565 if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_resource($options[$option])) {
566 self::warnInvalidRequestOptionType($option, 'bool|resource', $options[$option]);
567 }
568 }
569 private static function warnIfPresentAndNotBoolOrString(array $options, string $option) : void
570 {
571 if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) {
572 self::warnInvalidRequestOptionType($option, 'bool|string', $options[$option]);
573 }
574 }
575 private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null) : void
576 {
577 if (\array_key_exists($option, $options) && !\is_callable($options[$option])) {
578 self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option]);
579 }
580 }
581 private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null) : void
582 {
583 if (\array_key_exists($option, $options) && !\is_int($options[$option])) {
584 self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option]);
585 }
586 }
587 private static function warnIfPresentAndNotNumber(array $options, string $option) : void
588 {
589 if (\array_key_exists($option, $options) && !\is_int($options[$option]) && !\is_float($options[$option])) {
590 self::warnInvalidRequestOptionType($option, 'int|float', $options[$option]);
591 }
592 }
593 private static function warnIfPresentAndNotString(array $options, string $option) : void
594 {
595 if (\array_key_exists($option, $options) && !\is_string($options[$option])) {
596 self::warnInvalidRequestOptionType($option, 'string', $options[$option]);
597 }
598 }
599 private static function warnIfPresentAndNotStringArray(array $options, string $option, bool $nonEmpty, ?string $path = null) : void
600 {
601 if (!\array_key_exists($option, $options)) {
602 return;
603 }
604 $path = $path ?? $option;
605 $expected = ($nonEmpty ? 'non-empty-' : '') . 'array<array-key, string>';
606 if (!\is_array($options[$option]) || $nonEmpty && $options[$option] === []) {
607 self::warnInvalidRequestOptionType($path, $expected, $options[$option]);
608 return;
609 }
610 foreach ($options[$option] as $index => $item) {
611 if (!\is_string($item)) {
612 self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item);
613 return;
614 }
615 }
616 }
617 private static function warnIfPresentAndNotStringOrNumber(array $options, string $option) : void
618 {
619 if (\array_key_exists($option, $options) && !\is_string($options[$option]) && !\is_int($options[$option]) && !\is_float($options[$option])) {
620 self::warnInvalidRequestOptionType($option, 'string|int|float', $options[$option]);
621 }
622 }
623 /**
624 * @param mixed $value
625 */
626 private static function warnInvalidRequestOptionType(string $option, string $expected, $value) : void
627 {
628 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing %s to request option "%s" is deprecated; guzzlehttp/guzzle 8.0 requires %s.', \get_debug_type($value), $option, $expected);
629 }
630 /**
631 * Transfers the given request and applies request options.
632 *
633 * The URI of the request is not modified and the request options are used
634 * as-is without merging in default options.
635 *
636 * @param array $options See {@see RequestOptions}.
637 */
638 private function transfer(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
639 {
640 $request = $this->applyOptions($request, $options);
641 $protocolVersion = $request->getProtocolVersion();
642 if ('' === $protocolVersion) {
643 \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.');
644 $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, ['version' => '1.1']);
645 } elseif (!self::isProtocolVersionValid($protocolVersion)) {
646 \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.');
647 }
648 /** @var HandlerStack $handler */
649 $handler = $options['handler'];
650 try {
651 return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($handler($request, $options));
652 } catch (\Exception $e) {
653 return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor($e);
654 }
655 }
656 /**
657 * Applies the array of request options to a request.
658 */
659 private function applyOptions(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options) : \YoastSEO_Vendor\Psr\Http\Message\RequestInterface
660 {
661 $modify = ['set_headers' => []];
662 if (isset($options['headers'])) {
663 if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) {
664 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('The headers array must have header name as keys.');
665 }
666 $headers = $options['headers'];
667 $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers);
668 if ($droppedHeaderNames !== [] && isset($options['_conditional'])) {
669 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']);
670 }
671 $modify['set_headers'] = $headers;
672 unset($options['headers']);
673 }
674 if (isset($options['form_params'])) {
675 if (isset($options['multipart'])) {
676 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.');
677 }
678 $options['body'] = \http_build_query(self::normalizeNonFiniteFloats($options['form_params'], 'form_params'), '', '&');
679 unset($options['form_params']);
680 // Ensure that we don't have the header in different case and set the new value.
681 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
682 $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
683 }
684 if (isset($options['multipart'])) {
685 $options['body'] = new \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream($options['multipart']);
686 unset($options['multipart']);
687 }
688 if (isset($options['json'])) {
689 $options['body'] = \YoastSEO_Vendor\GuzzleHttp\Utils::jsonEncode($options['json']);
690 unset($options['json']);
691 // Ensure that we don't have the header in different case and set the new value.
692 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
693 $options['_conditional']['Content-Type'] = 'application/json';
694 }
695 if (!empty($options['decode_content']) && $options['decode_content'] !== \true) {
696 // Ensure that we don't have the header in different case and set the new value.
697 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
698 $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content'];
699 }
700 if (isset($options['body'])) {
701 if (\is_array($options['body'])) {
702 throw $this->invalidBody();
703 }
704 $modify['body'] = self::createBodyStream($options['body']);
705 unset($options['body']);
706 }
707 if (!empty($options['auth']) && \is_array($options['auth'])) {
708 $value = $options['auth'];
709 $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
710 switch ($type) {
711 case 'basic':
712 // Ensure that we don't have the header in different case and set the new value.
713 $modify['set_headers'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
714 $modify['set_headers']['Authorization'] = 'Basic ' . \base64_encode("{$value[0]}:{$value[1]}");
715 break;
716 case 'digest':
717 // @todo: Do not rely on curl
718 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
719 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
720 break;
721 case 'ntlm':
722 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing "ntlm" as the built-in auth type is deprecated; guzzlehttp/guzzle 8.0 will no longer apply NTLM through the "auth" request option. Configure NTLM with cURL HTTP authentication options instead.');
723 $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
724 $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}";
725 break;
726 }
727 }
728 if (isset($options['query'])) {
729 $value = $options['query'];
730 if (\is_array($value)) {
731 $value = \http_build_query(self::normalizeNonFiniteFloats($value, 'query'), '', '&', \PHP_QUERY_RFC3986);
732 }
733 if (!\is_string($value)) {
734 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('query must be a string or array');
735 }
736 $modify['query'] = $value;
737 unset($options['query']);
738 }
739 // Ensure that sink is not an invalid value.
740 if (isset($options['sink'])) {
741 // TODO: Add more sink validation?
742 if (\is_bool($options['sink'])) {
743 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException('sink must not be a boolean');
744 }
745 }
746 if (isset($options['version'])) {
747 $modify['version'] = self::normalizeProtocolVersion($options['version']);
748 }
749 $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
750 if ($request->getBody() instanceof \YoastSEO_Vendor\GuzzleHttp\Psr7\MultipartStream) {
751 // Use a multipart/form-data POST if a Content-Type is not set.
752 // Ensure that we don't have the header in different case and set the new value.
753 $options['_conditional'] = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
754 $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' . $request->getBody()->getBoundary();
755 }
756 // Merge in conditional headers if they are not present.
757 if (isset($options['_conditional'])) {
758 // Build up the changes so it's in a single clone of the message.
759 $modify = [];
760 foreach ($options['_conditional'] as $k => $v) {
761 $name = (string) $k;
762 if (!$request->hasHeader($name)) {
763 $modify['set_headers'][$name] = $v;
764 }
765 }
766 $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, $modify);
767 // Don't pass this internal value along to middleware/handlers.
768 unset($options['_conditional']);
769 }
770 return $request;
771 }
772 /**
773 * @param array<array-key, mixed> $headers
774 *
775 * @return list<string>
776 */
777 private static function castDeprecatedHeaderOptionValues(array &$headers) : array
778 {
779 $droppedHeaderNames = [];
780 foreach ($headers as $name => $value) {
781 if (\is_array($value)) {
782 if ($value === []) {
783 $droppedHeaderNames[] = (string) $name;
784 unset($headers[$name]);
785 continue;
786 }
787 foreach ($value as $index => $item) {
788 if ($item === null || !\is_string($item) && \is_scalar($item)) {
789 if (\is_float($item) && !\is_finite($item)) {
790 $item = \is_nan($item) ? 'NAN' : ($item > 0 ? 'INF' : '-INF');
791 }
792 $value[$index] = (string) $item;
793 }
794 }
795 $headers[$name] = $value;
796 continue;
797 }
798 if ($value === null || !\is_string($value) && \is_scalar($value)) {
799 if (\is_float($value) && !\is_finite($value)) {
800 $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
801 }
802 $headers[$name] = (string) $value;
803 }
804 }
805 return $droppedHeaderNames;
806 }
807 /**
808 * @param mixed $body
809 */
810 private static function createBodyStream($body) : \YoastSEO_Vendor\Psr\Http\Message\StreamInterface
811 {
812 if ($body instanceof \YoastSEO_Vendor\Psr\Http\Message\StreamInterface) {
813 return $body;
814 }
815 if (\is_resource($body) || $body === null || \is_string($body) || $body instanceof \Iterator) {
816 return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($body);
817 }
818 if (\is_scalar($body)) {
819 \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.');
820 return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor(self::stringifyScalar($body));
821 }
822 if (\is_object($body) && \method_exists($body, '__toString')) {
823 return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor((string) $body);
824 }
825 if (\is_callable($body)) {
826 return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($body);
827 }
828 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)));
829 }
830 /**
831 * @param bool|float|int|string $value
832 */
833 private static function stringifyScalar($value) : string
834 {
835 // Normalize non-finite floats to dodge PHP 8.5's (string) NAN
836 // coercion warning while the value is still accepted.
837 if (\is_float($value) && !\is_finite($value)) {
838 $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
839 }
840 return (string) $value;
841 }
842 /**
843 * Converts non-finite floats in the array to the strings PHP coerces
844 * them to, as implicit coercion of NAN emits a warning on PHP 8.5.
845 */
846 private static function normalizeNonFiniteFloats(array $values, string $option) : array
847 {
848 foreach ($values as $key => $value) {
849 if (\is_array($value)) {
850 $values[$key] = self::normalizeNonFiniteFloats($value, $option);
851 } elseif (\is_float($value) && !\is_finite($value)) {
852 \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);
853 $values[$key] = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
854 }
855 }
856 return $values;
857 }
858 /**
859 * @param string|int|float $version
860 */
861 private static function normalizeProtocolVersion($version) : string
862 {
863 if ('' === $version) {
864 \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.');
865 return '1.1';
866 }
867 return \is_float($version) ? \number_format($version, 1, '.', '') : (string) $version;
868 }
869 private static function isProtocolVersionValid(string $version) : bool
870 {
871 return 1 === \preg_match('/^\\d+(?:\\.\\d+)?$/D', $version);
872 }
873 /**
874 * Return an InvalidArgumentException with pre-set message.
875 */
876 private function invalidBody() : \YoastSEO_Vendor\GuzzleHttp\Exception\InvalidArgumentException
877 {
878 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.');
879 }
880 }
881