Index: vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,736 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Exception\ConnectException;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Promise as P;
+use GuzzleHttp\Promise\FulfilledPromise;
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\Psr7\LazyOpenStream;
+use GuzzleHttp\TransferStats;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Creates curl resources from a request
+ *
+ * @final
+ */
+class CurlFactory implements CurlFactoryInterface
+{
+    public const CURL_VERSION_STR = 'curl_version';
+
+    /**
+     * @deprecated
+     */
+    public const LOW_CURL_VERSION_NUMBER = '7.21.2';
+
+    /**
+     * @var resource[]|\CurlHandle[]
+     */
+    private $handles = [];
+
+    /**
+     * @var int Total number of idle handles to keep in cache
+     */
+    private $maxHandles;
+
+    /**
+     * @param int $maxHandles Maximum number of idle handles.
+     */
+    public function __construct(int $maxHandles)
+    {
+        $this->maxHandles = $maxHandles;
+    }
+
+    public function create(RequestInterface $request, array $options): EasyHandle
+    {
+        $protocolVersion = $request->getProtocolVersion();
+
+        if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
+            if (!self::supportsHttp2()) {
+                throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
+            }
+        } elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
+            throw new ConnectException(sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request);
+        }
+
+        if (isset($options['curl']['body_as_string'])) {
+            $options['_body_as_string'] = $options['curl']['body_as_string'];
+            unset($options['curl']['body_as_string']);
+        }
+
+        $easy = new EasyHandle();
+        $easy->request = $request;
+        $easy->options = $options;
+        $conf = $this->getDefaultConf($easy);
+        $this->applyMethod($easy, $conf);
+        $this->applyHandlerOptions($easy, $conf);
+        $this->applyHeaders($easy, $conf);
+        unset($conf['_headers']);
+
+        // Add handler options from the request configuration options
+        if (isset($options['curl'])) {
+            $conf = \array_replace($conf, $options['curl']);
+        }
+
+        $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
+        $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init();
+        curl_setopt_array($easy->handle, $conf);
+
+        return $easy;
+    }
+
+    private static function supportsHttp2(): bool
+    {
+        static $supportsHttp2 = null;
+
+        if (null === $supportsHttp2) {
+            $supportsHttp2 = self::supportsTls12()
+                && defined('CURL_VERSION_HTTP2')
+                && (\CURL_VERSION_HTTP2 & \curl_version()['features']);
+        }
+
+        return $supportsHttp2;
+    }
+
+    private static function supportsTls12(): bool
+    {
+        static $supportsTls12 = null;
+
+        if (null === $supportsTls12) {
+            $supportsTls12 = \CURL_SSLVERSION_TLSv1_2 & \curl_version()['features'];
+        }
+
+        return $supportsTls12;
+    }
+
+    private static function supportsTls13(): bool
+    {
+        static $supportsTls13 = null;
+
+        if (null === $supportsTls13) {
+            $supportsTls13 = defined('CURL_SSLVERSION_TLSv1_3')
+                && (\CURL_SSLVERSION_TLSv1_3 & \curl_version()['features']);
+        }
+
+        return $supportsTls13;
+    }
+
+    public function release(EasyHandle $easy): void
+    {
+        $resource = $easy->handle;
+        unset($easy->handle);
+
+        if (\count($this->handles) >= $this->maxHandles) {
+            \curl_close($resource);
+        } else {
+            // Remove all callback functions as they can hold onto references
+            // and are not cleaned up by curl_reset. Using curl_setopt_array
+            // does not work for some reason, so removing each one
+            // individually.
+            \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null);
+            \curl_setopt($resource, \CURLOPT_READFUNCTION, null);
+            \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null);
+            \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null);
+            \curl_reset($resource);
+            $this->handles[] = $resource;
+        }
+    }
+
+    /**
+     * Completes a cURL transaction, either returning a response promise or a
+     * rejected promise.
+     *
+     * @param callable(RequestInterface, array): PromiseInterface $handler
+     * @param CurlFactoryInterface                                $factory Dictates how the handle is released
+     */
+    public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
+    {
+        if (isset($easy->options['on_stats'])) {
+            self::invokeStats($easy);
+        }
+
+        if (!$easy->response || $easy->errno) {
+            return self::finishError($handler, $easy, $factory);
+        }
+
+        // Return the response if it is present and there is no error.
+        $factory->release($easy);
+
+        // Rewind the body of the response if possible.
+        $body = $easy->response->getBody();
+        if ($body->isSeekable()) {
+            $body->rewind();
+        }
+
+        return new FulfilledPromise($easy->response);
+    }
+
+    private static function invokeStats(EasyHandle $easy): void
+    {
+        $curlStats = \curl_getinfo($easy->handle);
+        $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME);
+        $stats = new TransferStats(
+            $easy->request,
+            $easy->response,
+            $curlStats['total_time'],
+            $easy->errno,
+            $curlStats
+        );
+        ($easy->options['on_stats'])($stats);
+    }
+
+    /**
+     * @param callable(RequestInterface, array): PromiseInterface $handler
+     */
+    private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
+    {
+        // Get error information and release the handle to the factory.
+        $ctx = [
+            'errno' => $easy->errno,
+            'error' => \curl_error($easy->handle),
+            'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
+        ] + \curl_getinfo($easy->handle);
+        $ctx[self::CURL_VERSION_STR] = self::getCurlVersion();
+        $factory->release($easy);
+
+        // Retry when nothing is present or when curl failed to rewind.
+        if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) {
+            return self::retryFailedRewind($handler, $easy, $ctx);
+        }
+
+        return self::createRejection($easy, $ctx);
+    }
+
+    private static function getCurlVersion(): string
+    {
+        static $curlVersion = null;
+
+        if (null === $curlVersion) {
+            $curlVersion = \curl_version()['version'];
+        }
+
+        return $curlVersion;
+    }
+
+    private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
+    {
+        static $connectionErrors = [
+            \CURLE_OPERATION_TIMEOUTED => true,
+            \CURLE_COULDNT_RESOLVE_HOST => true,
+            \CURLE_COULDNT_CONNECT => true,
+            \CURLE_SSL_CONNECT_ERROR => true,
+            \CURLE_GOT_NOTHING => true,
+        ];
+
+        if ($easy->createResponseException) {
+            return P\Create::rejectionFor(
+                new RequestException(
+                    'An error was encountered while creating the response',
+                    $easy->request,
+                    $easy->response,
+                    $easy->createResponseException,
+                    $ctx
+                )
+            );
+        }
+
+        // If an exception was encountered during the onHeaders event, then
+        // return a rejected promise that wraps that exception.
+        if ($easy->onHeadersException) {
+            return P\Create::rejectionFor(
+                new RequestException(
+                    'An error was encountered during the on_headers event',
+                    $easy->request,
+                    $easy->response,
+                    $easy->onHeadersException,
+                    $ctx
+                )
+            );
+        }
+
+        $uri = $easy->request->getUri();
+
+        $sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri);
+
+        $message = \sprintf(
+            'cURL error %s: %s (%s)',
+            $ctx['errno'],
+            $sanitizedError,
+            'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+        );
+
+        if ('' !== $sanitizedError) {
+            $redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($uri)->__toString();
+            if ($redactedUriString !== '' && false === \strpos($sanitizedError, $redactedUriString)) {
+                $message .= \sprintf(' for %s', $redactedUriString);
+            }
+        }
+
+        // Create a connection exception if it was a specific error code.
+        $error = isset($connectionErrors[$easy->errno])
+            ? new ConnectException($message, $easy->request, null, $ctx)
+            : new RequestException($message, $easy->request, $easy->response, null, $ctx);
+
+        return P\Create::rejectionFor($error);
+    }
+
+    private static function sanitizeCurlError(string $error, UriInterface $uri): string
+    {
+        if ('' === $error) {
+            return $error;
+        }
+
+        $baseUri = $uri->withQuery('')->withFragment('');
+        $baseUriString = $baseUri->__toString();
+
+        if ('' === $baseUriString) {
+            return $error;
+        }
+
+        $redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($baseUri)->__toString();
+
+        return str_replace($baseUriString, $redactedUriString, $error);
+    }
+
+    /**
+     * @return array<int|string, mixed>
+     */
+    private function getDefaultConf(EasyHandle $easy): array
+    {
+        $conf = [
+            '_headers' => $easy->request->getHeaders(),
+            \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
+            \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
+            \CURLOPT_RETURNTRANSFER => false,
+            \CURLOPT_HEADER => false,
+            \CURLOPT_CONNECTTIMEOUT => 300,
+        ];
+
+        if (\defined('CURLOPT_PROTOCOLS')) {
+            $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS;
+        }
+
+        $version = $easy->request->getProtocolVersion();
+
+        if ('2' === $version || '2.0' === $version) {
+            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
+        } elseif ('1.1' === $version) {
+            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
+        } else {
+            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
+        }
+
+        return $conf;
+    }
+
+    private function applyMethod(EasyHandle $easy, array &$conf): void
+    {
+        $body = $easy->request->getBody();
+        $size = $body->getSize();
+
+        if ($size === null || $size > 0) {
+            $this->applyBody($easy->request, $easy->options, $conf);
+
+            return;
+        }
+
+        $method = $easy->request->getMethod();
+        if ($method === 'PUT' || $method === 'POST') {
+            // See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
+            if (!$easy->request->hasHeader('Content-Length')) {
+                $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+            }
+        } elseif ($method === 'HEAD') {
+            $conf[\CURLOPT_NOBODY] = true;
+            unset(
+                $conf[\CURLOPT_WRITEFUNCTION],
+                $conf[\CURLOPT_READFUNCTION],
+                $conf[\CURLOPT_FILE],
+                $conf[\CURLOPT_INFILE]
+            );
+        }
+    }
+
+    private function applyBody(RequestInterface $request, array $options, array &$conf): void
+    {
+        $size = $request->hasHeader('Content-Length')
+            ? (int) $request->getHeaderLine('Content-Length')
+            : null;
+
+        // Send the body as a string if the size is less than 1MB OR if the
+        // [curl][body_as_string] request value is set.
+        if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) {
+            $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody();
+            // Don't duplicate the Content-Length header
+            $this->removeHeader('Content-Length', $conf);
+            $this->removeHeader('Transfer-Encoding', $conf);
+        } else {
+            $conf[\CURLOPT_UPLOAD] = true;
+            if ($size !== null) {
+                $conf[\CURLOPT_INFILESIZE] = $size;
+                $this->removeHeader('Content-Length', $conf);
+            }
+            $body = $request->getBody();
+            if ($body->isSeekable()) {
+                $body->rewind();
+            }
+            $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) {
+                return $body->read($length);
+            };
+        }
+
+        // If the Expect header is not present, prevent curl from adding it
+        if (!$request->hasHeader('Expect')) {
+            $conf[\CURLOPT_HTTPHEADER][] = 'Expect:';
+        }
+
+        // cURL sometimes adds a content-type by default. Prevent this.
+        if (!$request->hasHeader('Content-Type')) {
+            $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:';
+        }
+    }
+
+    private function applyHeaders(EasyHandle $easy, array &$conf): void
+    {
+        foreach ($conf['_headers'] as $name => $values) {
+            foreach ($values as $value) {
+                $value = (string) $value;
+                if ($value === '') {
+                    // cURL requires a special format for empty headers.
+                    // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+                    $conf[\CURLOPT_HTTPHEADER][] = "$name;";
+                } else {
+                    $conf[\CURLOPT_HTTPHEADER][] = "$name: $value";
+                }
+            }
+        }
+
+        // Remove the Accept header if one was not set
+        if (!$easy->request->hasHeader('Accept')) {
+            $conf[\CURLOPT_HTTPHEADER][] = 'Accept:';
+        }
+    }
+
+    /**
+     * Remove a header from the options array.
+     *
+     * @param string $name    Case-insensitive header to remove
+     * @param array  $options Array of options to modify
+     */
+    private function removeHeader(string $name, array &$options): void
+    {
+        foreach (\array_keys($options['_headers']) as $key) {
+            if (!\strcasecmp($key, $name)) {
+                unset($options['_headers'][$key]);
+
+                return;
+            }
+        }
+    }
+
+    private function applyHandlerOptions(EasyHandle $easy, array &$conf): void
+    {
+        $options = $easy->options;
+        if (isset($options['verify'])) {
+            if ($options['verify'] === false) {
+                unset($conf[\CURLOPT_CAINFO]);
+                $conf[\CURLOPT_SSL_VERIFYHOST] = 0;
+                $conf[\CURLOPT_SSL_VERIFYPEER] = false;
+            } else {
+                $conf[\CURLOPT_SSL_VERIFYHOST] = 2;
+                $conf[\CURLOPT_SSL_VERIFYPEER] = true;
+                if (\is_string($options['verify'])) {
+                    // Throw an error if the file/folder/link path is not valid or doesn't exist.
+                    if (!\file_exists($options['verify'])) {
+                        throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}");
+                    }
+                    // If it's a directory or a link to a directory use CURLOPT_CAPATH.
+                    // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
+                    if (
+                        \is_dir($options['verify'])
+                        || (
+                            \is_link($options['verify']) === true
+                            && ($verifyLink = \readlink($options['verify'])) !== false
+                            && \is_dir($verifyLink)
+                        )
+                    ) {
+                        $conf[\CURLOPT_CAPATH] = $options['verify'];
+                    } else {
+                        $conf[\CURLOPT_CAINFO] = $options['verify'];
+                    }
+                }
+            }
+        }
+
+        if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) {
+            $accept = $easy->request->getHeaderLine('Accept-Encoding');
+            if ($accept) {
+                $conf[\CURLOPT_ENCODING] = $accept;
+            } else {
+                // The empty string enables all available decoders and implicitly
+                // sets a matching 'Accept-Encoding' header.
+                $conf[\CURLOPT_ENCODING] = '';
+                // But as the user did not specify any encoding preference,
+                // let's leave it up to server by preventing curl from sending
+                // the header, which will be interpreted as 'Accept-Encoding: *'.
+                // https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding
+                $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+            }
+        }
+
+        if (!isset($options['sink'])) {
+            // Use a default temp stream if no sink was set.
+            $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+');
+        }
+        $sink = $options['sink'];
+        if (!\is_string($sink)) {
+            $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink);
+        } elseif (!\is_dir(\dirname($sink))) {
+            // Ensure that the directory exists before failing in curl.
+            throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink));
+        } else {
+            $sink = new LazyOpenStream($sink, 'w+');
+        }
+        $easy->sink = $sink;
+        $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int {
+            return $sink->write($write);
+        };
+
+        $timeoutRequiresNoSignal = false;
+        if (isset($options['timeout'])) {
+            $timeoutRequiresNoSignal |= $options['timeout'] < 1;
+            $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
+        }
+
+        // CURL default value is CURL_IPRESOLVE_WHATEVER
+        if (isset($options['force_ip_resolve'])) {
+            if ('v4' === $options['force_ip_resolve']) {
+                $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
+            } elseif ('v6' === $options['force_ip_resolve']) {
+                $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6;
+            }
+        }
+
+        if (isset($options['connect_timeout'])) {
+            $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
+            $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
+        }
+
+        if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') {
+            $conf[\CURLOPT_NOSIGNAL] = true;
+        }
+
+        if (isset($options['proxy'])) {
+            if (!\is_array($options['proxy'])) {
+                $conf[\CURLOPT_PROXY] = $options['proxy'];
+            } else {
+                $scheme = $easy->request->getUri()->getScheme();
+                if (isset($options['proxy'][$scheme])) {
+                    $host = $easy->request->getUri()->getHost();
+                    if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) {
+                        unset($conf[\CURLOPT_PROXY]);
+                    } else {
+                        $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme];
+                    }
+                }
+            }
+        }
+
+        if (isset($options['crypto_method'])) {
+            $protocolVersion = $easy->request->getProtocolVersion();
+
+            // If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2
+            if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
+                if (
+                    \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']
+                    || \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']
+                    || \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']
+                ) {
+                    $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
+                } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
+                    if (!self::supportsTls13()) {
+                        throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
+                    }
+                    $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
+                } else {
+                    throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
+                }
+            } elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
+                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
+            } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
+                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
+            } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
+                if (!self::supportsTls12()) {
+                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
+                }
+                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
+            } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
+                if (!self::supportsTls13()) {
+                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
+                }
+                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
+            } else {
+                throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
+            }
+        }
+
+        if (isset($options['cert'])) {
+            $cert = $options['cert'];
+            if (\is_array($cert)) {
+                $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
+                $cert = $cert[0];
+            }
+            if (!\file_exists($cert)) {
+                throw new \InvalidArgumentException("SSL certificate not found: {$cert}");
+            }
+            // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files.
+            // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
+            $ext = pathinfo($cert, \PATHINFO_EXTENSION);
+            if (preg_match('#^(der|p12)$#i', $ext)) {
+                $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext);
+            }
+            $conf[\CURLOPT_SSLCERT] = $cert;
+        }
+
+        if (isset($options['ssl_key'])) {
+            if (\is_array($options['ssl_key'])) {
+                if (\count($options['ssl_key']) === 2) {
+                    [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key'];
+                } else {
+                    [$sslKey] = $options['ssl_key'];
+                }
+            }
+
+            $sslKey = $sslKey ?? $options['ssl_key'];
+
+            if (!\file_exists($sslKey)) {
+                throw new \InvalidArgumentException("SSL private key not found: {$sslKey}");
+            }
+            $conf[\CURLOPT_SSLKEY] = $sslKey;
+        }
+
+        if (isset($options['progress'])) {
+            $progress = $options['progress'];
+            if (!\is_callable($progress)) {
+                throw new \InvalidArgumentException('progress client option must be callable');
+            }
+            $conf[\CURLOPT_NOPROGRESS] = false;
+            $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) {
+                $progress($downloadSize, $downloaded, $uploadSize, $uploaded);
+            };
+        }
+
+        if (!empty($options['debug'])) {
+            $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']);
+            $conf[\CURLOPT_VERBOSE] = true;
+        }
+    }
+
+    /**
+     * This function ensures that a response was set on a transaction. If one
+     * was not set, then the request is retried if possible. This error
+     * typically means you are sending a payload, curl encountered a
+     * "Connection died, retrying a fresh connect" error, tried to rewind the
+     * stream, and then encountered a "necessary data rewind wasn't possible"
+     * error, causing the request to be sent through curl_multi_info_read()
+     * without an error status.
+     *
+     * @param callable(RequestInterface, array): PromiseInterface $handler
+     */
+    private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface
+    {
+        try {
+            // Only rewind if the body has been read from.
+            $body = $easy->request->getBody();
+            if ($body->tell() > 0) {
+                $body->rewind();
+            }
+        } catch (\RuntimeException $e) {
+            $ctx['error'] = 'The connection unexpectedly failed without '
+                .'providing an error. The request would have been retried, '
+                .'but attempting to rewind the request body failed. '
+                .'Exception: '.$e;
+
+            return self::createRejection($easy, $ctx);
+        }
+
+        // Retry no more than 3 times before giving up.
+        if (!isset($easy->options['_curl_retries'])) {
+            $easy->options['_curl_retries'] = 1;
+        } elseif ($easy->options['_curl_retries'] == 2) {
+            $ctx['error'] = 'The cURL request was retried 3 times '
+                .'and did not succeed. The most likely reason for the failure '
+                .'is that cURL was unable to rewind the body of the request '
+                .'and subsequent retries resulted in the same error. Turn on '
+                .'the debug option to see what went wrong. See '
+                .'https://bugs.php.net/bug.php?id=47204 for more information.';
+
+            return self::createRejection($easy, $ctx);
+        } else {
+            ++$easy->options['_curl_retries'];
+        }
+
+        return $handler($easy->request, $easy->options);
+    }
+
+    private function createHeaderFn(EasyHandle $easy): callable
+    {
+        if (isset($easy->options['on_headers'])) {
+            $onHeaders = $easy->options['on_headers'];
+
+            if (!\is_callable($onHeaders)) {
+                throw new \InvalidArgumentException('on_headers must be callable');
+            }
+        } else {
+            $onHeaders = null;
+        }
+
+        return static function ($ch, $h) use (
+            $onHeaders,
+            $easy,
+            &$startingResponse
+        ) {
+            $value = \trim($h);
+            if ($value === '') {
+                $startingResponse = true;
+                try {
+                    $easy->createResponse();
+                } catch (\Exception $e) {
+                    $easy->createResponseException = $e;
+
+                    return -1;
+                }
+                if ($onHeaders !== null) {
+                    try {
+                        $onHeaders($easy->response);
+                    } catch (\Exception $e) {
+                        // Associate the exception with the handle and trigger
+                        // a curl header write error by returning 0.
+                        $easy->onHeadersException = $e;
+
+                        return -1;
+                    }
+                }
+            } elseif ($startingResponse) {
+                $startingResponse = false;
+                $easy->headers = [$value];
+            } else {
+                $easy->headers[] = $value;
+            }
+
+            return \strlen($h);
+        };
+    }
+
+    public function __destruct()
+    {
+        foreach ($this->handles as $id => $handle) {
+            \curl_close($handle);
+            unset($this->handles[$id]);
+        }
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,25 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use Psr\Http\Message\RequestInterface;
+
+interface CurlFactoryInterface
+{
+    /**
+     * Creates a cURL handle resource.
+     *
+     * @param RequestInterface $request Request
+     * @param array            $options Transfer options
+     *
+     * @throws \RuntimeException when an option cannot be applied
+     */
+    public function create(RequestInterface $request, array $options): EasyHandle;
+
+    /**
+     * Release an easy handle, allowing it to be reused or closed.
+     *
+     * This function must call unset on the easy handle's "handle" property.
+     */
+    public function release(EasyHandle $easy): void;
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,49 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Promise\PromiseInterface;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * HTTP handler that uses cURL easy handles as a transport layer.
+ *
+ * When using the CurlHandler, custom curl options can be specified as an
+ * associative array of curl option constants mapping to values in the
+ * **curl** key of the "client" key of the request.
+ *
+ * @final
+ */
+class CurlHandler
+{
+    /**
+     * @var CurlFactoryInterface
+     */
+    private $factory;
+
+    /**
+     * Accepts an associative array of options:
+     *
+     * - handle_factory: Optional curl factory used to create cURL handles.
+     *
+     * @param array{handle_factory?: ?CurlFactoryInterface} $options Array of options to use with the handler
+     */
+    public function __construct(array $options = [])
+    {
+        $this->factory = $options['handle_factory']
+            ?? new CurlFactory(3);
+    }
+
+    public function __invoke(RequestInterface $request, array $options): PromiseInterface
+    {
+        if (isset($options['delay'])) {
+            \usleep($options['delay'] * 1000);
+        }
+
+        $easy = $this->factory->create($request, $options);
+        \curl_exec($easy->handle);
+        $easy->errno = \curl_errno($easy->handle);
+
+        return CurlFactory::finish($this, $easy, $this->factory);
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,284 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use Closure;
+use GuzzleHttp\Promise as P;
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Returns an asynchronous response using curl_multi_* functions.
+ *
+ * When using the CurlMultiHandler, custom curl options can be specified as an
+ * associative array of curl option constants mapping to values in the
+ * **curl** key of the provided request options.
+ *
+ * @final
+ */
+class CurlMultiHandler
+{
+    /**
+     * @var CurlFactoryInterface
+     */
+    private $factory;
+
+    /**
+     * @var int
+     */
+    private $selectTimeout;
+
+    /**
+     * @var int Will be higher than 0 when `curl_multi_exec` is still running.
+     */
+    private $active = 0;
+
+    /**
+     * @var array Request entry handles, indexed by handle id in `addRequest`.
+     *
+     * @see CurlMultiHandler::addRequest
+     */
+    private $handles = [];
+
+    /**
+     * @var array<int, float> An array of delay times, indexed by handle id in `addRequest`.
+     *
+     * @see CurlMultiHandler::addRequest
+     */
+    private $delays = [];
+
+    /**
+     * @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
+     */
+    private $options = [];
+
+    /** @var resource|\CurlMultiHandle */
+    private $_mh;
+
+    /**
+     * This handler accepts the following options:
+     *
+     * - handle_factory: An optional factory  used to create curl handles
+     * - select_timeout: Optional timeout (in seconds) to block before timing
+     *   out while selecting curl handles. Defaults to 1 second.
+     * - options: An associative array of CURLMOPT_* options and
+     *   corresponding values for curl_multi_setopt()
+     */
+    public function __construct(array $options = [])
+    {
+        $this->factory = $options['handle_factory'] ?? new CurlFactory(50);
+
+        if (isset($options['select_timeout'])) {
+            $this->selectTimeout = $options['select_timeout'];
+        } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+            @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED);
+            $this->selectTimeout = (int) $selectTimeout;
+        } else {
+            $this->selectTimeout = 1;
+        }
+
+        $this->options = $options['options'] ?? [];
+
+        // unsetting the property forces the first access to go through
+        // __get().
+        unset($this->_mh);
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return resource|\CurlMultiHandle
+     *
+     * @throws \BadMethodCallException when another field as `_mh` will be gotten
+     * @throws \RuntimeException       when curl can not initialize a multi handle
+     */
+    public function __get($name)
+    {
+        if ($name !== '_mh') {
+            throw new \BadMethodCallException("Can not get other property as '_mh'.");
+        }
+
+        $multiHandle = \curl_multi_init();
+
+        if (false === $multiHandle) {
+            throw new \RuntimeException('Can not initialize curl multi handle.');
+        }
+
+        $this->_mh = $multiHandle;
+
+        foreach ($this->options as $option => $value) {
+            // A warning is raised in case of a wrong option.
+            curl_multi_setopt($this->_mh, $option, $value);
+        }
+
+        return $this->_mh;
+    }
+
+    public function __destruct()
+    {
+        if (isset($this->_mh)) {
+            \curl_multi_close($this->_mh);
+            unset($this->_mh);
+        }
+    }
+
+    public function __invoke(RequestInterface $request, array $options): PromiseInterface
+    {
+        $easy = $this->factory->create($request, $options);
+        $id = (int) $easy->handle;
+
+        $promise = new Promise(
+            [$this, 'execute'],
+            function () use ($id) {
+                return $this->cancel($id);
+            }
+        );
+
+        $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
+
+        return $promise;
+    }
+
+    /**
+     * Ticks the curl event loop.
+     */
+    public function tick(): void
+    {
+        // Add any delayed handles if needed.
+        if ($this->delays) {
+            $currentTime = Utils::currentTime();
+            foreach ($this->delays as $id => $delay) {
+                if ($currentTime >= $delay) {
+                    unset($this->delays[$id]);
+                    \curl_multi_add_handle(
+                        $this->_mh,
+                        $this->handles[$id]['easy']->handle
+                    );
+                }
+            }
+        }
+
+        // Run curl_multi_exec in the queue to enable other async tasks to run
+        P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
+
+        // Step through the task queue which may add additional requests.
+        P\Utils::queue()->run();
+
+        if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
+            // Perform a usleep if a select returns -1.
+            // See: https://bugs.php.net/bug.php?id=61141
+            \usleep(250);
+        }
+
+        while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
+            // Prevent busy looping for slow HTTP requests.
+            \curl_multi_select($this->_mh, $this->selectTimeout);
+        }
+
+        $this->processMessages();
+    }
+
+    /**
+     * Runs \curl_multi_exec() inside the event loop, to prevent busy looping
+     */
+    private function tickInQueue(): void
+    {
+        if (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
+            \curl_multi_select($this->_mh, 0);
+            P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
+        }
+    }
+
+    /**
+     * Runs until all outstanding connections have completed.
+     */
+    public function execute(): void
+    {
+        $queue = P\Utils::queue();
+
+        while ($this->handles || !$queue->isEmpty()) {
+            // If there are no transfers, then sleep for the next delay
+            if (!$this->active && $this->delays) {
+                \usleep($this->timeToNext());
+            }
+            $this->tick();
+        }
+    }
+
+    private function addRequest(array $entry): void
+    {
+        $easy = $entry['easy'];
+        $id = (int) $easy->handle;
+        $this->handles[$id] = $entry;
+        if (empty($easy->options['delay'])) {
+            \curl_multi_add_handle($this->_mh, $easy->handle);
+        } else {
+            $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
+        }
+    }
+
+    /**
+     * Cancels a handle from sending and removes references to it.
+     *
+     * @param int $id Handle ID to cancel and remove.
+     *
+     * @return bool True on success, false on failure.
+     */
+    private function cancel($id): bool
+    {
+        if (!is_int($id)) {
+            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
+        }
+
+        // Cannot cancel if it has been processed.
+        if (!isset($this->handles[$id])) {
+            return false;
+        }
+
+        $handle = $this->handles[$id]['easy']->handle;
+        unset($this->delays[$id], $this->handles[$id]);
+        \curl_multi_remove_handle($this->_mh, $handle);
+        \curl_close($handle);
+
+        return true;
+    }
+
+    private function processMessages(): void
+    {
+        while ($done = \curl_multi_info_read($this->_mh)) {
+            if ($done['msg'] !== \CURLMSG_DONE) {
+                // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216
+                continue;
+            }
+            $id = (int) $done['handle'];
+            \curl_multi_remove_handle($this->_mh, $done['handle']);
+
+            if (!isset($this->handles[$id])) {
+                // Probably was cancelled.
+                continue;
+            }
+
+            $entry = $this->handles[$id];
+            unset($this->handles[$id], $this->delays[$id]);
+            $entry['easy']->errno = $done['result'];
+            $entry['deferred']->resolve(
+                CurlFactory::finish($this, $entry['easy'], $this->factory)
+            );
+        }
+    }
+
+    private function timeToNext(): int
+    {
+        $currentTime = Utils::currentTime();
+        $nextTime = \PHP_INT_MAX;
+        foreach ($this->delays as $time) {
+            if ($time < $nextTime) {
+                $nextTime = $time;
+            }
+        }
+
+        return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,112 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Represents a cURL easy handle and the data it populates.
+ *
+ * @internal
+ */
+final class EasyHandle
+{
+    /**
+     * @var resource|\CurlHandle cURL resource
+     */
+    public $handle;
+
+    /**
+     * @var StreamInterface Where data is being written
+     */
+    public $sink;
+
+    /**
+     * @var array Received HTTP headers so far
+     */
+    public $headers = [];
+
+    /**
+     * @var ResponseInterface|null Received response (if any)
+     */
+    public $response;
+
+    /**
+     * @var RequestInterface Request being sent
+     */
+    public $request;
+
+    /**
+     * @var array Request options
+     */
+    public $options = [];
+
+    /**
+     * @var int cURL error number (if any)
+     */
+    public $errno = 0;
+
+    /**
+     * @var \Throwable|null Exception during on_headers (if any)
+     */
+    public $onHeadersException;
+
+    /**
+     * @var \Exception|null Exception during createResponse (if any)
+     */
+    public $createResponseException;
+
+    /**
+     * Attach a response to the easy handle based on the received headers.
+     *
+     * @throws \RuntimeException if no headers have been received or the first
+     *                           header line is invalid.
+     */
+    public function createResponse(): void
+    {
+        [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers);
+
+        $normalizedKeys = Utils::normalizeHeaderKeys($headers);
+
+        if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) {
+            $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
+            unset($headers[$normalizedKeys['content-encoding']]);
+            if (isset($normalizedKeys['content-length'])) {
+                $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
+
+                $bodyLength = (int) $this->sink->getSize();
+                if ($bodyLength) {
+                    $headers[$normalizedKeys['content-length']] = $bodyLength;
+                } else {
+                    unset($headers[$normalizedKeys['content-length']]);
+                }
+            }
+        }
+
+        // Attach a response to the easy handle with the parsed headers.
+        $this->response = new Response(
+            $status,
+            $headers,
+            $this->sink,
+            $ver,
+            $reason
+        );
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return void
+     *
+     * @throws \BadMethodCallException
+     */
+    public function __get($name)
+    {
+        $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: '.$name;
+        throw new \BadMethodCallException($msg);
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,42 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Utils;
+
+/**
+ * @internal
+ */
+final class HeaderProcessor
+{
+    /**
+     * Returns the HTTP version, status code, reason phrase, and headers.
+     *
+     * @param string[] $headers
+     *
+     * @return array{0:string, 1:int, 2:?string, 3:array}
+     *
+     * @throws \RuntimeException
+     */
+    public static function parseHeaders(array $headers): array
+    {
+        if ($headers === []) {
+            throw new \RuntimeException('Expected a non-empty array of header data');
+        }
+
+        $parts = \explode(' ', \array_shift($headers), 3);
+        $version = \explode('/', $parts[0])[1] ?? null;
+
+        if ($version === null) {
+            throw new \RuntimeException('HTTP version missing from header data');
+        }
+
+        $status = $parts[1] ?? null;
+
+        if ($status === null) {
+            throw new \RuntimeException('HTTP status code missing from header data');
+        }
+
+        return [$version, (int) $status, $parts[2] ?? null, Utils::headersFromLines($headers)];
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,212 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Promise as P;
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\TransferStats;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Handler that returns responses or throw exceptions from a queue.
+ *
+ * @final
+ */
+class MockHandler implements \Countable
+{
+    /**
+     * @var array
+     */
+    private $queue = [];
+
+    /**
+     * @var RequestInterface|null
+     */
+    private $lastRequest;
+
+    /**
+     * @var array
+     */
+    private $lastOptions = [];
+
+    /**
+     * @var callable|null
+     */
+    private $onFulfilled;
+
+    /**
+     * @var callable|null
+     */
+    private $onRejected;
+
+    /**
+     * Creates a new MockHandler that uses the default handler stack list of
+     * middlewares.
+     *
+     * @param array|null    $queue       Array of responses, callables, or exceptions.
+     * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
+     * @param callable|null $onRejected  Callback to invoke when the return value is rejected.
+     */
+    public static function createWithMiddleware(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null): HandlerStack
+    {
+        return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
+    }
+
+    /**
+     * The passed in value must be an array of
+     * {@see ResponseInterface} objects, Exceptions,
+     * callables, or Promises.
+     *
+     * @param array<int, mixed>|null $queue       The parameters to be passed to the append function, as an indexed array.
+     * @param callable|null          $onFulfilled Callback to invoke when the return value is fulfilled.
+     * @param callable|null          $onRejected  Callback to invoke when the return value is rejected.
+     */
+    public function __construct(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null)
+    {
+        $this->onFulfilled = $onFulfilled;
+        $this->onRejected = $onRejected;
+
+        if ($queue) {
+            // array_values included for BC
+            $this->append(...array_values($queue));
+        }
+    }
+
+    public function __invoke(RequestInterface $request, array $options): PromiseInterface
+    {
+        if (!$this->queue) {
+            throw new \OutOfBoundsException('Mock queue is empty');
+        }
+
+        if (isset($options['delay']) && \is_numeric($options['delay'])) {
+            \usleep((int) $options['delay'] * 1000);
+        }
+
+        $this->lastRequest = $request;
+        $this->lastOptions = $options;
+        $response = \array_shift($this->queue);
+
+        if (isset($options['on_headers'])) {
+            if (!\is_callable($options['on_headers'])) {
+                throw new \InvalidArgumentException('on_headers must be callable');
+            }
+            try {
+                $options['on_headers']($response);
+            } catch (\Exception $e) {
+                $msg = 'An error was encountered during the on_headers event';
+                $response = new RequestException($msg, $request, $response, $e);
+            }
+        }
+
+        if (\is_callable($response)) {
+            $response = $response($request, $options);
+        }
+
+        $response = $response instanceof \Throwable
+            ? P\Create::rejectionFor($response)
+            : P\Create::promiseFor($response);
+
+        return $response->then(
+            function (?ResponseInterface $value) use ($request, $options) {
+                $this->invokeStats($request, $options, $value);
+                if ($this->onFulfilled) {
+                    ($this->onFulfilled)($value);
+                }
+
+                if ($value !== null && isset($options['sink'])) {
+                    $contents = (string) $value->getBody();
+                    $sink = $options['sink'];
+
+                    if (\is_resource($sink)) {
+                        \fwrite($sink, $contents);
+                    } elseif (\is_string($sink)) {
+                        \file_put_contents($sink, $contents);
+                    } elseif ($sink instanceof StreamInterface) {
+                        $sink->write($contents);
+                    }
+                }
+
+                return $value;
+            },
+            function ($reason) use ($request, $options) {
+                $this->invokeStats($request, $options, null, $reason);
+                if ($this->onRejected) {
+                    ($this->onRejected)($reason);
+                }
+
+                return P\Create::rejectionFor($reason);
+            }
+        );
+    }
+
+    /**
+     * Adds one or more variadic requests, exceptions, callables, or promises
+     * to the queue.
+     *
+     * @param mixed ...$values
+     */
+    public function append(...$values): void
+    {
+        foreach ($values as $value) {
+            if ($value instanceof ResponseInterface
+                || $value instanceof \Throwable
+                || $value instanceof PromiseInterface
+                || \is_callable($value)
+            ) {
+                $this->queue[] = $value;
+            } else {
+                throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.Utils::describeType($value));
+            }
+        }
+    }
+
+    /**
+     * Get the last received request.
+     */
+    public function getLastRequest(): ?RequestInterface
+    {
+        return $this->lastRequest;
+    }
+
+    /**
+     * Get the last received request options.
+     */
+    public function getLastOptions(): array
+    {
+        return $this->lastOptions;
+    }
+
+    /**
+     * Returns the number of remaining items in the queue.
+     */
+    public function count(): int
+    {
+        return \count($this->queue);
+    }
+
+    public function reset(): void
+    {
+        $this->queue = [];
+    }
+
+    /**
+     * @param mixed $reason Promise or reason.
+     */
+    private function invokeStats(
+        RequestInterface $request,
+        array $options,
+        ?ResponseInterface $response = null,
+        $reason = null
+    ): void {
+        if (isset($options['on_stats'])) {
+            $transferTime = $options['transfer_time'] ?? 0;
+            $stats = new TransferStats($request, $response, $transferTime, $reason);
+            ($options['on_stats'])($stats);
+        }
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/Proxy.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/Proxy.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,51 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\RequestOptions;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Provides basic proxies for handlers.
+ *
+ * @final
+ */
+class Proxy
+{
+    /**
+     * Sends synchronous requests to a specific handler while sending all other
+     * requests to another handler.
+     *
+     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses
+     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync    Handler used for synchronous responses.
+     *
+     * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
+     */
+    public static function wrapSync(callable $default, callable $sync): callable
+    {
+        return static function (RequestInterface $request, array $options) use ($default, $sync): PromiseInterface {
+            return empty($options[RequestOptions::SYNCHRONOUS]) ? $default($request, $options) : $sync($request, $options);
+        };
+    }
+
+    /**
+     * Sends streaming requests to a streaming compatible handler while sending
+     * all other requests to a default handler.
+     *
+     * This, for example, could be useful for taking advantage of the
+     * performance benefits of curl while still supporting true streaming
+     * through the StreamHandler.
+     *
+     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default   Handler used for non-streaming responses
+     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses
+     *
+     * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
+     */
+    public static function wrapStreaming(callable $default, callable $streaming): callable
+    {
+        return static function (RequestInterface $request, array $options) use ($default, $streaming): PromiseInterface {
+            return empty($options['stream']) ? $default($request, $options) : $streaming($request, $options);
+        };
+    }
+}
Index: vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
===================================================================
--- vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,621 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+use GuzzleHttp\Exception\ConnectException;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Promise as P;
+use GuzzleHttp\Promise\FulfilledPromise;
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\Psr7;
+use GuzzleHttp\TransferStats;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * HTTP handler that uses PHP's HTTP stream wrapper.
+ *
+ * @final
+ */
+class StreamHandler
+{
+    /**
+     * @var array
+     */
+    private $lastHeaders = [];
+
+    /**
+     * Sends an HTTP request.
+     *
+     * @param RequestInterface $request Request to send.
+     * @param array            $options Request transfer options.
+     */
+    public function __invoke(RequestInterface $request, array $options): PromiseInterface
+    {
+        // Sleep if there is a delay specified.
+        if (isset($options['delay'])) {
+            \usleep($options['delay'] * 1000);
+        }
+
+        $protocolVersion = $request->getProtocolVersion();
+
+        if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
+            throw new ConnectException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
+        }
+
+        $startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
+
+        try {
+            // Does not support the expect header.
+            $request = $request->withoutHeader('Expect');
+
+            // Append a content-length header if body size is zero to match
+            // cURL's behavior.
+            if (0 === $request->getBody()->getSize()) {
+                $request = $request->withHeader('Content-Length', '0');
+            }
+
+            return $this->createResponse(
+                $request,
+                $options,
+                $this->createStream($request, $options),
+                $startTime
+            );
+        } catch (\InvalidArgumentException $e) {
+            throw $e;
+        } catch (\Exception $e) {
+            // Determine if the error was a networking error.
+            $message = $e->getMessage();
+            // This list can probably get more comprehensive.
+            if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
+                || false !== \strpos($message, 'Connection refused')
+                || false !== \strpos($message, "couldn't connect to host") // error on HHVM
+                || false !== \strpos($message, 'connection attempt failed')
+            ) {
+                $e = new ConnectException($e->getMessage(), $request, $e);
+            } else {
+                $e = RequestException::wrapException($request, $e);
+            }
+            $this->invokeStats($options, $request, $startTime, null, $e);
+
+            return P\Create::rejectionFor($e);
+        }
+    }
+
+    private function invokeStats(
+        array $options,
+        RequestInterface $request,
+        ?float $startTime,
+        ?ResponseInterface $response = null,
+        ?\Throwable $error = null
+    ): void {
+        if (isset($options['on_stats'])) {
+            $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
+            ($options['on_stats'])($stats);
+        }
+    }
+
+    /**
+     * @param resource $stream
+     */
+    private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface
+    {
+        $hdrs = $this->lastHeaders;
+        $this->lastHeaders = [];
+
+        try {
+            [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
+        } catch (\Exception $e) {
+            return P\Create::rejectionFor(
+                new RequestException('An error was encountered while creating the response', $request, null, $e)
+            );
+        }
+
+        [$stream, $headers] = $this->checkDecode($options, $headers, $stream);
+        $stream = Psr7\Utils::streamFor($stream);
+        $sink = $stream;
+
+        if (\strcasecmp('HEAD', $request->getMethod())) {
+            $sink = $this->createSink($stream, $options);
+        }
+
+        try {
+            $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
+        } catch (\Exception $e) {
+            return P\Create::rejectionFor(
+                new RequestException('An error was encountered while creating the response', $request, null, $e)
+            );
+        }
+
+        if (isset($options['on_headers'])) {
+            try {
+                $options['on_headers']($response);
+            } catch (\Exception $e) {
+                return P\Create::rejectionFor(
+                    new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
+                );
+            }
+        }
+
+        // Do not drain when the request is a HEAD request because they have
+        // no body.
+        if ($sink !== $stream) {
+            $this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
+        }
+
+        $this->invokeStats($options, $request, $startTime, $response, null);
+
+        return new FulfilledPromise($response);
+    }
+
+    private function createSink(StreamInterface $stream, array $options): StreamInterface
+    {
+        if (!empty($options['stream'])) {
+            return $stream;
+        }
+
+        $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+');
+
+        return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink);
+    }
+
+    /**
+     * @param resource $stream
+     */
+    private function checkDecode(array $options, array $headers, $stream): array
+    {
+        // Automatically decode responses when instructed.
+        if (!empty($options['decode_content'])) {
+            $normalizedKeys = Utils::normalizeHeaderKeys($headers);
+            if (isset($normalizedKeys['content-encoding'])) {
+                $encoding = $headers[$normalizedKeys['content-encoding']];
+                if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
+                    $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream));
+                    $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
+
+                    // Remove content-encoding header
+                    unset($headers[$normalizedKeys['content-encoding']]);
+
+                    // Fix content-length header
+                    if (isset($normalizedKeys['content-length'])) {
+                        $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
+                        $length = (int) $stream->getSize();
+                        if ($length === 0) {
+                            unset($headers[$normalizedKeys['content-length']]);
+                        } else {
+                            $headers[$normalizedKeys['content-length']] = [$length];
+                        }
+                    }
+                }
+            }
+        }
+
+        return [$stream, $headers];
+    }
+
+    /**
+     * Drains the source stream into the "sink" client option.
+     *
+     * @param string $contentLength Header specifying the amount of
+     *                              data to read.
+     *
+     * @throws \RuntimeException when the sink option is invalid.
+     */
+    private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface
+    {
+        // If a content-length header is provided, then stop reading once
+        // that number of bytes has been read. This can prevent infinitely
+        // reading from a stream when dealing with servers that do not honor
+        // Connection: Close headers.
+        Psr7\Utils::copyToStream(
+            $source,
+            $sink,
+            (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
+        );
+
+        $sink->seek(0);
+        $source->close();
+
+        return $sink;
+    }
+
+    /**
+     * Create a resource and check to ensure it was created successfully
+     *
+     * @param callable $callback Callable that returns stream resource
+     *
+     * @return resource
+     *
+     * @throws \RuntimeException on error
+     */
+    private function createResource(callable $callback)
+    {
+        $errors = [];
+        \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool {
+            $errors[] = [
+                'message' => $msg,
+                'file' => $file,
+                'line' => $line,
+            ];
+
+            return true;
+        });
+
+        try {
+            $resource = $callback();
+        } finally {
+            \restore_error_handler();
+        }
+
+        if (!$resource) {
+            $message = 'Error creating resource: ';
+            foreach ($errors as $err) {
+                foreach ($err as $key => $value) {
+                    $message .= "[$key] $value".\PHP_EOL;
+                }
+            }
+            throw new \RuntimeException(\trim($message));
+        }
+
+        return $resource;
+    }
+
+    /**
+     * @return resource
+     */
+    private function createStream(RequestInterface $request, array $options)
+    {
+        static $methods;
+        if (!$methods) {
+            $methods = \array_flip(\get_class_methods(__CLASS__));
+        }
+
+        if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) {
+            throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request);
+        }
+
+        // HTTP/1.1 streams using the PHP stream wrapper require a
+        // Connection: close header
+        if ($request->getProtocolVersion() === '1.1'
+            && !$request->hasHeader('Connection')
+        ) {
+            $request = $request->withHeader('Connection', 'close');
+        }
+
+        // Ensure SSL is verified by default
+        if (!isset($options['verify'])) {
+            $options['verify'] = true;
+        }
+
+        $params = [];
+        $context = $this->getDefaultContext($request);
+
+        if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
+            throw new \InvalidArgumentException('on_headers must be callable');
+        }
+
+        if (!empty($options)) {
+            foreach ($options as $key => $value) {
+                $method = "add_{$key}";
+                if (isset($methods[$method])) {
+                    $this->{$method}($request, $context, $value, $params);
+                }
+            }
+        }
+
+        if (isset($options['stream_context'])) {
+            if (!\is_array($options['stream_context'])) {
+                throw new \InvalidArgumentException('stream_context must be an array');
+            }
+            $context = \array_replace_recursive($context, $options['stream_context']);
+        }
+
+        // Microsoft NTLM authentication only supported with curl handler
+        if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
+            throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
+        }
+
+        $uri = $this->resolveHost($request, $options);
+
+        $contextResource = $this->createResource(
+            static function () use ($context, $params) {
+                return \stream_context_create($context, $params);
+            }
+        );
+
+        return $this->createResource(
+            function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
+                $resource = @\fopen((string) $uri, 'r', false, $contextResource);
+                $this->lastHeaders = $http_response_header ?? [];
+
+                if (false === $resource) {
+                    throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context);
+                }
+
+                if (isset($options['read_timeout'])) {
+                    $readTimeout = $options['read_timeout'];
+                    $sec = (int) $readTimeout;
+                    $usec = ($readTimeout - $sec) * 100000;
+                    \stream_set_timeout($resource, $sec, $usec);
+                }
+
+                return $resource;
+            }
+        );
+    }
+
+    private function resolveHost(RequestInterface $request, array $options): UriInterface
+    {
+        $uri = $request->getUri();
+
+        if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
+            if ('v4' === $options['force_ip_resolve']) {
+                $records = \dns_get_record($uri->getHost(), \DNS_A);
+                if (false === $records || !isset($records[0]['ip'])) {
+                    throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
+                }
+
+                return $uri->withHost($records[0]['ip']);
+            }
+            if ('v6' === $options['force_ip_resolve']) {
+                $records = \dns_get_record($uri->getHost(), \DNS_AAAA);
+                if (false === $records || !isset($records[0]['ipv6'])) {
+                    throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
+                }
+
+                return $uri->withHost('['.$records[0]['ipv6'].']');
+            }
+        }
+
+        return $uri;
+    }
+
+    private function getDefaultContext(RequestInterface $request): array
+    {
+        $headers = '';
+        foreach ($request->getHeaders() as $name => $value) {
+            foreach ($value as $val) {
+                $headers .= "$name: $val\r\n";
+            }
+        }
+
+        $context = [
+            'http' => [
+                'method' => $request->getMethod(),
+                'header' => $headers,
+                'protocol_version' => $request->getProtocolVersion(),
+                'ignore_errors' => true,
+                'follow_location' => 0,
+            ],
+            'ssl' => [
+                'peer_name' => $request->getUri()->getHost(),
+            ],
+        ];
+
+        $body = (string) $request->getBody();
+
+        if ('' !== $body) {
+            $context['http']['content'] = $body;
+            // Prevent the HTTP handler from adding a Content-Type header.
+            if (!$request->hasHeader('Content-Type')) {
+                $context['http']['header'] .= "Content-Type:\r\n";
+            }
+        }
+
+        $context['http']['header'] = \rtrim($context['http']['header']);
+
+        return $context;
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        $uri = null;
+
+        if (!\is_array($value)) {
+            $uri = $value;
+        } else {
+            $scheme = $request->getUri()->getScheme();
+            if (isset($value[$scheme])) {
+                if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
+                    $uri = $value[$scheme];
+                }
+            }
+        }
+
+        if (!$uri) {
+            return;
+        }
+
+        $parsed = $this->parse_proxy($uri);
+        $options['http']['proxy'] = $parsed['proxy'];
+
+        if ($parsed['auth']) {
+            if (!isset($options['http']['header'])) {
+                $options['http']['header'] = [];
+            }
+            $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
+        }
+    }
+
+    /**
+     * Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
+     */
+    private function parse_proxy(string $url): array
+    {
+        $parsed = \parse_url($url);
+
+        if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
+            if (isset($parsed['host']) && isset($parsed['port'])) {
+                $auth = null;
+                if (isset($parsed['user']) && isset($parsed['pass'])) {
+                    $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}");
+                }
+
+                return [
+                    'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}",
+                    'auth' => $auth ? "Basic {$auth}" : null,
+                ];
+            }
+        }
+
+        // Return proxy as-is.
+        return [
+            'proxy' => $url,
+            'auth' => null,
+        ];
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        if ($value > 0) {
+            $options['http']['timeout'] = $value;
+        }
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        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)
+        ) {
+            $options['http']['crypto_method'] = $value;
+
+            return;
+        }
+
+        throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        if ($value === false) {
+            $options['ssl']['verify_peer'] = false;
+            $options['ssl']['verify_peer_name'] = false;
+
+            return;
+        }
+
+        if (\is_string($value)) {
+            $options['ssl']['cafile'] = $value;
+            if (!\file_exists($value)) {
+                throw new \RuntimeException("SSL CA bundle not found: $value");
+            }
+        } elseif ($value !== true) {
+            throw new \InvalidArgumentException('Invalid verify request option');
+        }
+
+        $options['ssl']['verify_peer'] = true;
+        $options['ssl']['verify_peer_name'] = true;
+        $options['ssl']['allow_self_signed'] = false;
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        if (\is_array($value)) {
+            $options['ssl']['passphrase'] = $value[1];
+            $value = $value[0];
+        }
+
+        if (!\file_exists($value)) {
+            throw new \RuntimeException("SSL certificate not found: {$value}");
+        }
+
+        $options['ssl']['local_cert'] = $value;
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        self::addNotification(
+            $params,
+            static function ($code, $a, $b, $c, $transferred, $total) use ($value) {
+                if ($code == \STREAM_NOTIFY_PROGRESS) {
+                    // The upload progress cannot be determined. Use 0 for cURL compatibility:
+                    // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html
+                    $value($total, $transferred, 0, 0);
+                }
+            }
+        );
+    }
+
+    /**
+     * @param mixed $value as passed via Request transfer options.
+     */
+    private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void
+    {
+        if ($value === false) {
+            return;
+        }
+
+        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',
+        ];
+        static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max'];
+
+        $value = Utils::debugResource($value);
+        $ident = $request->getMethod().' '.$request->getUri()->withFragment('');
+        self::addNotification(
+            $params,
+            static function (int $code, ...$passed) use ($ident, $value, $map, $args): void {
+                \fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+                foreach (\array_filter($passed) as $i => $v) {
+                    \fwrite($value, $args[$i].': "'.$v.'" ');
+                }
+                \fwrite($value, "\n");
+            }
+        );
+    }
+
+    private static function addNotification(array &$params, callable $notify): void
+    {
+        // Wrap the existing function if needed.
+        if (!isset($params['notification'])) {
+            $params['notification'] = $notify;
+        } else {
+            $params['notification'] = self::callArray([
+                $params['notification'],
+                $notify,
+            ]);
+        }
+    }
+
+    private static function callArray(array $functions): callable
+    {
+        return static function (...$args) use ($functions) {
+            foreach ($functions as $fn) {
+                $fn(...$args);
+            }
+        };
+    }
+}
