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/Handler/StreamHandler.php +0 -711 28.0 → trunk View file →
@@ -1,711 +1,0 @@
1 -<?php
2 -
3 -namespace YoastSEO_Vendor\GuzzleHttp\Handler;
4 -
5 -use YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException;
6 -use YoastSEO_Vendor\GuzzleHttp\Exception\RequestException;
7 -use YoastSEO_Vendor\GuzzleHttp\Promise as P;
8 -use YoastSEO_Vendor\GuzzleHttp\Promise\FulfilledPromise;
9 -use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface;
10 -use YoastSEO_Vendor\GuzzleHttp\Psr7;
11 -use YoastSEO_Vendor\GuzzleHttp\TransferStats;
12 -use YoastSEO_Vendor\GuzzleHttp\TransportSharing;
13 -use YoastSEO_Vendor\GuzzleHttp\Utils;
14 -use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
15 -use YoastSEO_Vendor\Psr\Http\Message\ResponseInterface;
16 -use YoastSEO_Vendor\Psr\Http\Message\StreamInterface;
17 -use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
18 -/**
19 - * HTTP handler that uses PHP's HTTP stream wrapper.
20 - *
21 - * @final
22 - */
23 -class StreamHandler
24 -{
25 - private const CONNECTION_ERRORS = [
26 - 'php_network_getaddresses:',
27 - 'getaddrinfo',
28 - 'gethostbyname failed',
29 - 'Connection refused',
30 - 'No connection could be made because the target machine actively refused it',
31 - "couldn't connect to host",
32 - // error on HHVM
33 - 'connection attempt failed',
34 - 'connect() failed',
35 - 'Connection timed out',
36 - 'Operation timed out',
37 - 'Network is unreachable',
38 - 'No route to host',
39 - 'Host is unreachable',
40 - 'Host is down',
41 - 'Cannot connect to HTTPS server through proxy',
42 - ];
43 - /**
44 - * @var array
45 - */
46 - private $lastHeaders = [];
47 - /**
48 - * @var string
49 - */
50 - private $transportSharingMode;
51 - /**
52 - * Accepts an associative array of options:
53 - *
54 - * - transport_sharing: Optional transport sharing mode.
55 - *
56 - * @param array{transport_sharing?: mixed} $options Array of options to use with the handler
57 - */
58 - public function __construct(array $options = [])
59 - {
60 - $this->transportSharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($options['transport_sharing'] ?? null, 'transport_sharing');
61 - }
62 - /**
63 - * Sends an HTTP request.
64 - *
65 - * @param RequestInterface $request Request to send.
66 - * @param array $options Request transfer options.
67 - */
68 - public function __invoke(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
69 - {
70 - // Sleep if there is a delay specified.
71 - if (isset($options['delay'])) {
72 - \usleep($options['delay'] * 1000);
73 - }
74 - $protocolVersion = $request->getProtocolVersion();
75 - if ('' === $protocolVersion) {
76 - \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.');
77 - $protocolVersion = '1.1';
78 - $request = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::modifyRequest($request, ['version' => $protocolVersion]);
79 - }
80 - if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
81 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException(\sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
82 - }
83 - $startTime = isset($options['on_stats']) ? \YoastSEO_Vendor\GuzzleHttp\Utils::currentTime() : null;
84 - self::triggerUnsupportedRequestOptionDeprecations($request, $options);
85 - $this->assertTransportSharingSupported();
86 - try {
87 - // Does not support the expect header.
88 - $request = $request->withoutHeader('Expect');
89 - // Append a content-length header if body size is zero to match
90 - // the behavior of `CurlHandler`
91 - if ((0 === \strcasecmp('PUT', $request->getMethod()) || 0 === \strcasecmp('POST', $request->getMethod())) && 0 === $request->getBody()->getSize()) {
92 - $request = $request->withHeader('Content-Length', '0');
93 - }
94 - return $this->createResponse($request, $options, $this->createStream($request, $options), $startTime);
95 - } catch (\InvalidArgumentException $e) {
96 - throw $e;
97 - } catch (\Exception $e) {
98 - // Determine if the error was a networking error.
99 - if (self::isConnectionError($e->getMessage())) {
100 - $e = new \YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException($e->getMessage(), $request, $e);
101 - } else {
102 - $e = $e instanceof \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException ? $e : new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException($e->getMessage(), $request, null, $e);
103 - }
104 - $this->invokeStats($options, $request, $startTime, null, $e);
105 - return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor($e);
106 - }
107 - }
108 - private static function isConnectionError(string $message) : bool
109 - {
110 - foreach (self::CONNECTION_ERRORS as $connectionError) {
111 - if (\false !== \strpos($message, $connectionError)) {
112 - return \true;
113 - }
114 - }
115 - return \false;
116 - }
117 - private function invokeStats(array $options, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, ?float $startTime, ?\YoastSEO_Vendor\Psr\Http\Message\ResponseInterface $response = null, ?\Throwable $error = null) : void
118 - {
119 - if (isset($options['on_stats'])) {
120 - $stats = new \YoastSEO_Vendor\GuzzleHttp\TransferStats($request, $response, \YoastSEO_Vendor\GuzzleHttp\Utils::currentTime() - $startTime, $error, []);
121 - $options['on_stats']($stats);
122 - }
123 - }
124 - /**
125 - * @param resource $stream
126 - */
127 - private function createResponse(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options, $stream, ?float $startTime) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
128 - {
129 - $hdrs = $this->lastHeaders;
130 - $this->lastHeaders = [];
131 - try {
132 - [$ver, $status, $reason, $headers] = \YoastSEO_Vendor\GuzzleHttp\Handler\HeaderProcessor::parseHeaders($hdrs);
133 - } catch (\Throwable $e) {
134 - return $this->rejectResponseCreation($options, $request, $startTime, $e);
135 - }
136 - [$stream, $headers] = $this->checkDecode($options, $headers, $stream);
137 - $stream = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($stream);
138 - $sink = $stream;
139 - if (\strcasecmp('HEAD', $request->getMethod())) {
140 - $sink = $this->createSink($stream, $options);
141 - }
142 - try {
143 - $response = new \YoastSEO_Vendor\GuzzleHttp\Psr7\Response($status, $headers, $sink, $ver, $reason);
144 - } catch (\Throwable $e) {
145 - return $this->rejectResponseCreation($options, $request, $startTime, $e);
146 - }
147 - if (isset($options['on_headers'])) {
148 - try {
149 - $options['on_headers']($response);
150 - } catch (\Throwable $e) {
151 - return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor(new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException('An error was encountered during the on_headers event', $request, $response, $e));
152 - }
153 - }
154 - // Do not drain when the request is a HEAD request because they have
155 - // no body.
156 - if ($sink !== $stream) {
157 - $this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
158 - }
159 - $this->invokeStats($options, $request, $startTime, $response, null);
160 - return new \YoastSEO_Vendor\GuzzleHttp\Promise\FulfilledPromise($response);
161 - }
162 - private function rejectResponseCreation(array $options, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, ?float $startTime, \Throwable $previous) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
163 - {
164 - $reason = new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException('An error was encountered while creating the response', $request, null, $previous);
165 - $this->invokeStats($options, $request, $startTime, null, $reason);
166 - return \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor($reason);
167 - }
168 - private function createSink(\YoastSEO_Vendor\Psr\Http\Message\StreamInterface $stream, array $options) : \YoastSEO_Vendor\Psr\Http\Message\StreamInterface
169 - {
170 - if (!empty($options['stream'])) {
171 - return $stream;
172 - }
173 - $sink = $options['sink'] ?? \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'r+');
174 - return \is_string($sink) ? new \YoastSEO_Vendor\GuzzleHttp\Psr7\LazyOpenStream($sink, 'w+') : \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($sink);
175 - }
176 - /**
177 - * @param resource $stream
178 - */
179 - private function checkDecode(array $options, array $headers, $stream) : array
180 - {
181 - // Automatically decode responses when instructed.
182 - if (!empty($options['decode_content'])) {
183 - $normalizedKeys = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeHeaderKeys($headers);
184 - if (isset($normalizedKeys['content-encoding'])) {
185 - $encoding = $headers[$normalizedKeys['content-encoding']];
186 - if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
187 - $stream = new \YoastSEO_Vendor\GuzzleHttp\Psr7\InflateStream(\YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::streamFor($stream));
188 - $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
189 - // Remove content-encoding header
190 - unset($headers[$normalizedKeys['content-encoding']]);
191 - // The decoded length cannot be known without inflating the
192 - // stream, so keep the original length for inspection and
193 - // drop the now-unknown Content-Length header.
194 - if (isset($normalizedKeys['content-length'])) {
195 - $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
196 - unset($headers[$normalizedKeys['content-length']]);
197 - }
198 - }
199 - }
200 - }
201 - return [$stream, $headers];
202 - }
203 - /**
204 - * Drains the source stream into the "sink" client option.
205 - *
206 - * @param string $contentLength Header specifying the amount of
207 - * data to read.
208 - *
209 - * @throws \RuntimeException when the sink option is invalid.
210 - */
211 - private function drain(\YoastSEO_Vendor\Psr\Http\Message\StreamInterface $source, \YoastSEO_Vendor\Psr\Http\Message\StreamInterface $sink, string $contentLength) : \YoastSEO_Vendor\Psr\Http\Message\StreamInterface
212 - {
213 - // If a content-length header is provided, then stop reading once
214 - // that number of bytes has been read. This can prevent infinitely
215 - // reading from a stream when dealing with servers that do not honor
216 - // Connection: Close headers.
217 - \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::copyToStream($source, $sink, \strlen($contentLength) > 0 && (int) $contentLength > 0 ? (int) $contentLength : -1);
218 - $sink->seek(0);
219 - $source->close();
220 - return $sink;
221 - }
222 - /**
223 - * Create a resource and check to ensure it was created successfully
224 - *
225 - * @param callable $callback Callable that returns stream resource
226 - *
227 - * @return resource
228 - *
229 - * @throws \RuntimeException on error
230 - */
231 - private function createResource(callable $callback)
232 - {
233 - $errors = [];
234 - \set_error_handler(static function ($_, $msg, $file, $line) use(&$errors) : bool {
235 - $errors[] = ['message' => $msg, 'file' => $file, 'line' => $line];
236 - return \true;
237 - });
238 - try {
239 - $resource = $callback();
240 - } finally {
241 - \restore_error_handler();
242 - }
243 - if (!$resource) {
244 - $message = 'Error creating resource: ';
245 - foreach ($errors as $err) {
246 - foreach ($err as $key => $value) {
247 - $message .= "[{$key}] {$value}" . \PHP_EOL;
248 - }
249 - }
250 - throw new \RuntimeException(\trim($message));
251 - }
252 - return $resource;
253 - }
254 - /**
255 - * @return resource
256 - */
257 - private function createStream(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options)
258 - {
259 - static $methods;
260 - if (!$methods) {
261 - $methods = \array_flip(\get_class_methods(__CLASS__));
262 - }
263 - $scheme = $request->getUri()->getScheme();
264 - if (!\in_array($scheme, ['http', 'https'], \true)) {
265 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf("The scheme '%s' is not supported.", $scheme), $request);
266 - }
267 - $protocols = \YoastSEO_Vendor\GuzzleHttp\Utils::normalizeProtocols($options['protocols'] ?? ['http', 'https']);
268 - if (!\in_array($scheme, $protocols, \true)) {
269 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', $scheme), $request);
270 - }
271 - // HTTP/1.1 streams using the PHP stream wrapper require a
272 - // Connection: close header
273 - if ($request->getProtocolVersion() === '1.1' && !$request->hasHeader('Connection')) {
274 - $request = $request->withHeader('Connection', 'close');
275 - }
276 - // Ensure SSL is verified by default
277 - if (!isset($options['verify'])) {
278 - $options['verify'] = \true;
279 - }
280 - $params = [];
281 - $context = $this->getDefaultContext($request);
282 - if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
283 - throw new \InvalidArgumentException('on_headers must be callable');
284 - }
285 - if (!empty($options)) {
286 - foreach ($options as $key => $value) {
287 - $method = "add_{$key}";
288 - if (isset($methods[$method])) {
289 - $this->{$method}($request, $context, $value, $params);
290 - }
291 - }
292 - }
293 - if (isset($options['stream_context'])) {
294 - if (!\is_array($options['stream_context'])) {
295 - throw new \InvalidArgumentException('stream_context must be an array');
296 - }
297 - self::triggerConflictingStreamContextOptionDeprecations($options['stream_context']);
298 - self::triggerUnsupportedStreamContextOptionDeprecations($options['stream_context']);
299 - $context = \array_replace_recursive($context, $options['stream_context']);
300 - }
301 - // Microsoft NTLM authentication only supported with curl handler
302 - if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
303 - throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
304 - }
305 - $uri = $this->resolveHost($request, $options);
306 - $contextResource = $this->createResource(static function () use($context, $params) {
307 - return \stream_context_create($context, $params);
308 - });
309 - return $this->createResource(function () use($uri, $contextResource, $context, $options, $request) {
310 - $resource = @\fopen((string) $uri, 'r', \false, $contextResource);
311 - // See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
312 - if (\function_exists('YoastSEO_Vendor\\http_get_last_response_headers')) {
313 - $http_response_header = \YoastSEO_Vendor\http_get_last_response_headers();
314 - }
315 - $this->lastHeaders = $http_response_header ?? [];
316 - if (\false === $resource) {
317 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException(\sprintf('Connection refused for URI %s', $uri), $request, null, $context);
318 - }
319 - if (isset($options['read_timeout'])) {
320 - $readTimeout = $options['read_timeout'];
321 - $sec = (int) $readTimeout;
322 - $usec = ($readTimeout - $sec) * 100000;
323 - \stream_set_timeout($resource, $sec, $usec);
324 - }
325 - return $resource;
326 - });
327 - }
328 - private function resolveHost(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
329 - {
330 - $uri = $request->getUri();
331 - if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
332 - if ('v4' === $options['force_ip_resolve']) {
333 - $records = \dns_get_record($uri->getHost(), \DNS_A);
334 - if (\false === $records || !isset($records[0]['ip'])) {
335 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
336 - }
337 - return $uri->withHost($records[0]['ip']);
338 - }
339 - if ('v6' === $options['force_ip_resolve']) {
340 - $records = \dns_get_record($uri->getHost(), \DNS_AAAA);
341 - if (\false === $records || !isset($records[0]['ipv6'])) {
342 - throw new \YoastSEO_Vendor\GuzzleHttp\Exception\ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
343 - }
344 - return $uri->withHost('[' . $records[0]['ipv6'] . ']');
345 - }
346 - }
347 - return $uri;
348 - }
349 - private function getDefaultContext(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : array
350 - {
351 - $headers = '';
352 - foreach ($request->getHeaders() as $name => $value) {
353 - foreach ($value as $val) {
354 - $headers .= "{$name}: {$val}\r\n";
355 - }
356 - }
357 - $context = ['http' => ['method' => $request->getMethod(), 'header' => $headers, 'protocol_version' => $request->getProtocolVersion(), 'ignore_errors' => \true, 'follow_location' => 0], 'ssl' => ['peer_name' => $request->getUri()->getHost()]];
358 - $body = (string) $request->getBody();
359 - if ('' !== $body) {
360 - $context['http']['content'] = $body;
361 - // Prevent the HTTP handler from adding a Content-Type header.
362 - if (!$request->hasHeader('Content-Type')) {
363 - $context['http']['header'] .= "Content-Type:\r\n";
364 - }
365 - }
366 - $context['http']['header'] = \rtrim($context['http']['header']);
367 - return $context;
368 - }
369 - private static function triggerUnsupportedRequestOptionDeprecations(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : void
370 - {
371 - if (\array_key_exists('curl', $options) && $options['curl'] !== null && $options['curl'] !== [] && !self::isCurlOptionGeneratedByAuth($options)) {
372 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "curl" request option to the stream handler is deprecated; guzzlehttp/guzzle 8.0 will reject this option because the stream handler ignores cURL options.');
373 - }
374 - if (\array_key_exists('expect', $options) && $options['expect'] !== \false && $request->hasHeader('Expect')) {
375 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "expect" request option to the stream handler is deprecated when it adds an Expect header; guzzlehttp/guzzle 8.0 will reject this option because the stream handler does not support Expect: 100-Continue.');
376 - }
377 - }
378 - private static function triggerConflictingStreamContextOptionDeprecations(array $streamContext) : void
379 - {
380 - $conflictingOptions = self::conflictingStreamContextOptions();
381 - foreach ($streamContext as $wrapper => $contextOptions) {
382 - if (!\is_string($wrapper) || !isset($conflictingOptions[$wrapper]) || !\is_array($contextOptions)) {
383 - continue;
384 - }
385 - foreach ($contextOptions as $option => $_) {
386 - if (!\is_string($option) || !\array_key_exists($option, $conflictingOptions[$wrapper])) {
387 - continue;
388 - }
389 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', \sprintf('Passing stream_context.%s.%s in the "stream_context" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed request handling. Use %s instead.', $wrapper, $option, $conflictingOptions[$wrapper][$option]));
390 - }
391 - }
392 - }
393 - private static function triggerUnsupportedStreamContextOptionDeprecations(array $streamContext) : void
394 - {
395 - $unsupportedOptions = self::unsupportedStreamContextOptions($streamContext);
396 - if ($unsupportedOptions === []) {
397 - return;
398 - }
399 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.12', \sprintf('Passing PHP stream context options outside the built-in stream handler allow-list to the "stream_context" request option is deprecated; guzzlehttp/guzzle 8.0 will reject stream context options outside the allow-list. Deprecated option%s: %s.', \count($unsupportedOptions) === 1 ? '' : 's', \implode(', ', $unsupportedOptions)));
400 - }
401 - /**
402 - * @return string[]
403 - */
404 - private static function unsupportedStreamContextOptions(array $streamContext) : array
405 - {
406 - $supportedOptions = self::supportedStreamContextOptions();
407 - $conflictingOptions = self::conflictingStreamContextOptions();
408 - $unsupportedOptions = [];
409 - foreach ($streamContext as $wrapper => $contextOptions) {
410 - if (!\is_string($wrapper) || !isset($supportedOptions[$wrapper])) {
411 - if (\is_array($contextOptions)) {
412 - foreach ($contextOptions as $option => $_) {
413 - if (\is_string($wrapper) && \is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) {
414 - continue;
415 - }
416 - $unsupportedOptions[] = \sprintf('stream_context.%s.%s', (string) $wrapper, (string) $option);
417 - }
418 - } else {
419 - $unsupportedOptions[] = \sprintf('stream_context.%s', (string) $wrapper);
420 - }
421 - continue;
422 - }
423 - if (!\is_array($contextOptions)) {
424 - $unsupportedOptions[] = \sprintf('stream_context.%s', $wrapper);
425 - continue;
426 - }
427 - foreach ($contextOptions as $option => $_) {
428 - if (\is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) {
429 - continue;
430 - }
431 - if (!\is_string($option) || !\array_key_exists($option, $supportedOptions[$wrapper])) {
432 - $unsupportedOptions[] = \sprintf('stream_context.%s.%s', $wrapper, (string) $option);
433 - }
434 - }
435 - }
436 - return $unsupportedOptions;
437 - }
438 - /**
439 - * @return array<string, array<string, true>>
440 - */
441 - private static function supportedStreamContextOptions() : array
442 - {
443 - return ['http' => ['request_fulluri' => \true], 'socket' => ['bindto' => \true, 'tcp_nodelay' => \true], 'ssl' => ['SNI_enabled' => \true, 'capture_peer_cert' => \true, 'capture_peer_cert_chain' => \true, 'ciphers' => \true, 'disable_compression' => \true, 'no_ticket' => \true, 'peer_fingerprint' => \true, 'security_level' => \true, 'verify_depth' => \true]];
444 - }
445 - /**
446 - * @return array<string, array<string, string>>
447 - */
448 - private static function conflictingStreamContextOptions() : array
449 - {
450 - return ['http' => ['content' => 'the request body', 'follow_location' => 'the "allow_redirects" request option', 'header' => 'the request headers', 'max_redirects' => 'the "allow_redirects" request option', 'method' => 'the request method', 'protocol_version' => 'the request protocol version', 'proxy' => 'the "proxy" request option', 'timeout' => 'the "timeout" request option'], 'ssl' => ['allow_self_signed' => 'the "verify" request option', 'cafile' => 'the "verify" request option', 'capath' => 'the "verify" request option', 'crypto_method' => 'the "crypto_method" request option', 'local_cert' => 'the "cert" request option', 'local_pk' => 'the "ssl_key" request option', 'min_proto_version' => 'the "crypto_method" request option', 'passphrase' => 'the "cert" or "ssl_key" request option', 'peer_name' => 'the request URI', 'verify_peer' => 'the "verify" request option', 'verify_peer_name' => 'the "verify" request option']];
451 - }
452 - private function assertTransportSharingSupported() : void
453 - {
454 - if ($this->transportSharingMode === \YoastSEO_Vendor\GuzzleHttp\TransportSharing::HANDLER_REQUIRE) {
455 - throw new \InvalidArgumentException('The "transport_sharing" option requires transport sharing, but the stream handler does not support it.');
456 - }
457 - }
458 - private static function isCurlOptionGeneratedByAuth(array $options) : bool
459 - {
460 - if (!isset($options['curl']) || !\is_array($options['curl']) || !isset($options['auth'][2]) || !\is_string($options['auth'][2])) {
461 - return \false;
462 - }
463 - if (!\defined('CURLOPT_HTTPAUTH') || !\defined('CURLOPT_USERPWD')) {
464 - return \false;
465 - }
466 - $type = \strtolower($options['auth'][2]);
467 - if ($type === 'digest') {
468 - $httpAuth = \defined('CURLAUTH_DIGEST') ? \constant('CURLAUTH_DIGEST') : null;
469 - } elseif ($type === 'ntlm') {
470 - $httpAuth = \defined('CURLAUTH_NTLM') ? \constant('CURLAUTH_NTLM') : null;
471 - } else {
472 - return \false;
473 - }
474 - return $httpAuth !== null && \count($options['curl']) === 2 && isset($options['curl'][\CURLOPT_HTTPAUTH], $options['curl'][\CURLOPT_USERPWD]) && $options['curl'][\CURLOPT_HTTPAUTH] === $httpAuth;
475 - }
476 - /**
477 - * @param mixed $value as passed via Request transfer options.
478 - *
479 - * @return array{0: string, 1: string|null}
480 - */
481 - private static function normalizeTlsFileOption(string $option, $value) : array
482 - {
483 - $passphrase = null;
484 - if (\is_array($value)) {
485 - if (!isset($value[0]) || !\is_string($value[0])) {
486 - throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
487 - }
488 - if (isset($value[1])) {
489 - if (!\is_string($value[1])) {
490 - throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
491 - }
492 - $passphrase = $value[1];
493 - }
494 - $value = $value[0];
495 - }
496 - if (!\is_string($value)) {
497 - throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option));
498 - }
499 - return [$value, $passphrase];
500 - }
501 - private static function setTlsPassphrase(array &$options, ?string $passphrase, string $option) : void
502 - {
503 - if ($passphrase === null) {
504 - return;
505 - }
506 - if (isset($options['ssl']['passphrase']) && $options['ssl']['passphrase'] !== $passphrase) {
507 - throw new \InvalidArgumentException(\sprintf('Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.', $option));
508 - }
509 - $options['ssl']['passphrase'] = $passphrase;
510 - }
511 - /**
512 - * @param mixed $value as passed via Request transfer options.
513 - */
514 - private static function assertStreamTlsType(string $option, $value) : void
515 - {
516 - if (!\is_string($value) || $value === '') {
517 - throw new \InvalidArgumentException(\sprintf('%s must be a non-empty string', $option));
518 - }
519 - if (\strtoupper($value) !== 'PEM') {
520 - throw new \InvalidArgumentException(\sprintf('The stream handler only supports "PEM" for the %s request option.', $option));
521 - }
522 - }
523 - /**
524 - * @param mixed $value as passed via Request transfer options.
525 - */
526 - private function add_proxy(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
527 - {
528 - $uri = null;
529 - if (!\is_array($value)) {
530 - $uri = $value;
531 - } else {
532 - $scheme = $request->getUri()->getScheme();
533 - if (isset($value[$scheme])) {
534 - if (!isset($value['no']) || !\YoastSEO_Vendor\GuzzleHttp\Utils::isUriInNoProxy($request->getUri(), $value['no'])) {
535 - $uri = $value[$scheme];
536 - }
537 - }
538 - }
539 - if (!$uri) {
540 - return;
541 - }
542 - $parsed = $this->parse_proxy($uri);
543 - $options['http']['proxy'] = $parsed['proxy'];
544 - if ($parsed['auth']) {
545 - if (!isset($options['http']['header'])) {
546 - $options['http']['header'] = '';
547 - }
548 - $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
549 - }
550 - }
551 - /**
552 - * Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
553 - */
554 - private function parse_proxy(string $url) : array
555 - {
556 - $parsed = \parse_url($url);
557 - // parse_url() misreads scheme-less proxy authorities like
558 - // "user:pass@host"; re-parse only those forms as HTTP.
559 - $schemeLessAuthority = \strpos($url, '://') === \false && \strncmp($url, '//', 2) !== 0;
560 - if ($schemeLessAuthority) {
561 - if (\is_array($parsed) && !isset($parsed['scheme']) && isset($parsed['host'], $parsed['port'])) {
562 - $parsed['scheme'] = 'http';
563 - } elseif ((!\is_array($parsed) || !isset($parsed['host'])) && (\strpos($url, '@') !== \false || \strncmp($url, '[', 1) === 0)) {
564 - $parsed = \parse_url('http://' . $url);
565 - }
566 - }
567 - if (\is_array($parsed) && isset($parsed['scheme']) && \strcasecmp($parsed['scheme'], 'http') === 0) {
568 - if (isset($parsed['host'], $parsed['port'])) {
569 - $user = $parsed['user'] ?? '';
570 - $pass = $parsed['pass'] ?? '';
571 - $auth = $user !== '' || $pass !== '' ? 'Basic ' . \base64_encode("{$user}:{$pass}") : null;
572 - return ['proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", 'auth' => $auth];
573 - }
574 - }
575 - // Return proxy as-is.
576 - return ['proxy' => $url, 'auth' => null];
577 - }
578 - /**
579 - * @param mixed $value as passed via Request transfer options.
580 - */
581 - private function add_timeout(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
582 - {
583 - if ($value > 0) {
584 - $options['http']['timeout'] = $value;
585 - }
586 - }
587 - /**
588 - * @param mixed $value as passed via Request transfer options.
589 - */
590 - private function add_crypto_method(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
591 - {
592 - if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT || $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT || $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT || \defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) {
593 - $options['http']['crypto_method'] = $value;
594 - return;
595 - }
596 - throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
597 - }
598 - /**
599 - * @param mixed $value as passed via Request transfer options.
600 - */
601 - private function add_verify(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
602 - {
603 - if ($value === \false) {
604 - $options['ssl']['verify_peer'] = \false;
605 - $options['ssl']['verify_peer_name'] = \false;
606 - return;
607 - }
608 - if (\is_string($value)) {
609 - $options['ssl']['cafile'] = $value;
610 - if (!\file_exists($value)) {
611 - throw new \RuntimeException("SSL CA bundle not found: {$value}");
612 - }
613 - } elseif ($value !== \true) {
614 - throw new \InvalidArgumentException('Invalid verify request option');
615 - }
616 - $options['ssl']['verify_peer'] = \true;
617 - $options['ssl']['verify_peer_name'] = \true;
618 - $options['ssl']['allow_self_signed'] = \false;
619 - }
620 - /**
621 - * @param mixed $value as passed via Request transfer options.
622 - */
623 - private function add_cert(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
624 - {
625 - [$value, $passphrase] = self::normalizeTlsFileOption('cert', $value);
626 - if (!\file_exists($value)) {
627 - throw new \RuntimeException("SSL certificate not found: {$value}");
628 - }
629 - self::setTlsPassphrase($options, $passphrase, 'cert');
630 - $options['ssl']['local_cert'] = $value;
631 - }
632 - /**
633 - * @param mixed $value as passed via Request transfer options.
634 - */
635 - private function add_cert_type(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
636 - {
637 - self::assertStreamTlsType('cert_type', $value);
638 - }
639 - /**
640 - * @param mixed $value as passed via Request transfer options.
641 - */
642 - private function add_ssl_key(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
643 - {
644 - [$value, $passphrase] = self::normalizeTlsFileOption('ssl_key', $value);
645 - if (!\file_exists($value)) {
646 - throw new \RuntimeException("SSL private key not found: {$value}");
647 - }
648 - self::setTlsPassphrase($options, $passphrase, 'ssl_key');
649 - $options['ssl']['local_pk'] = $value;
650 - }
651 - /**
652 - * @param mixed $value as passed via Request transfer options.
653 - */
654 - private function add_ssl_key_type(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
655 - {
656 - self::assertStreamTlsType('ssl_key_type', $value);
657 - }
658 - /**
659 - * @param mixed $value as passed via Request transfer options.
660 - */
661 - private function add_progress(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
662 - {
663 - if (!\is_callable($value)) {
664 - throw new \InvalidArgumentException('progress client option must be callable');
665 - }
666 - self::addNotification($params, static function ($code, $a, $b, $c, $transferred, $total) use($value) {
667 - if ($code == \STREAM_NOTIFY_PROGRESS) {
668 - // The upload progress cannot be determined. Use 0 for cURL compatibility:
669 - // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html
670 - $value($total, $transferred, 0, 0);
671 - }
672 - });
673 - }
674 - /**
675 - * @param mixed $value as passed via Request transfer options.
676 - */
677 - private function add_debug(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array &$options, $value, array &$params) : void
678 - {
679 - if ($value === \false) {
680 - return;
681 - }
682 - static $map = [\STREAM_NOTIFY_CONNECT => 'CONNECT', \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', \STREAM_NOTIFY_PROGRESS => 'PROGRESS', \STREAM_NOTIFY_FAILURE => 'FAILURE', \STREAM_NOTIFY_COMPLETED => 'COMPLETED', \STREAM_NOTIFY_RESOLVE => 'RESOLVE'];
683 - static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max'];
684 - $value = \YoastSEO_Vendor\GuzzleHttp\Utils::debugResource($value);
685 - $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
686 - self::addNotification($params, static function (int $code, ...$passed) use($ident, $value, $map, $args) : void {
687 - \fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
688 - foreach (\array_filter($passed) as $i => $v) {
689 - \fwrite($value, $args[$i] . ': "' . $v . '" ');
690 - }
691 - \fwrite($value, "\n");
692 - });
693 - }
694 - private static function addNotification(array &$params, callable $notify) : void
695 - {
696 - // Wrap the existing function if needed.
697 - if (!isset($params['notification'])) {
698 - $params['notification'] = $notify;
699 - } else {
700 - $params['notification'] = self::callArray([$params['notification'], $notify]);
701 - }
702 - }
703 - private static function callArray(array $functions) : callable
704 - {
705 - return static function (...$args) use($functions) {
706 - foreach ($functions as $fn) {
707 - $fn(...$args);
708 - }
709 - };
710 - }
711 -}