summaryrefslogtreecommitdiff
path: root/vendor/guzzlehttp/guzzle/src/Handler
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/guzzlehttp/guzzle/src/Handler')
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php638
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php25
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php49
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php267
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php112
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php42
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php212
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/Proxy.php51
-rw-r--r--vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php615
9 files changed, 2011 insertions, 0 deletions
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php
new file mode 100644
index 000000000..be88d9e49
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php
@@ -0,0 +1,638 @@
+<?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;
+
+/**
+ * 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
+ {
+ 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;
+ }
+
+ 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] = \curl_version()['version'];
+ $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 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
+ )
+ );
+ }
+
+ $message = \sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ $uriString = (string) $easy->request->getUri();
+ if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) {
+ $message .= \sprintf(' for %s', $uriString);
+ }
+
+ // 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);
+ }
+
+ /**
+ * @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 ($version == 1.1) {
+ $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
+ } elseif ($version == 2.0) {
+ $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
+ } 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://tools.ietf.org/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 acceptable encodings we need
+ // to overwrite this implicit header with an empty one.
+ $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'])) {
+ if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
+ if (!defined('CURL_SSLVERSION_TLSv1_0')) {
+ throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL');
+ }
+ $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
+ } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
+ if (!defined('CURL_SSLVERSION_TLSv1_1')) {
+ throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
+ }
+ $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
+ } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
+ if (!defined('CURL_SSLVERSION_TLSv1_2')) {
+ 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 (!defined('CURL_SSLVERSION_TLSv1_3')) {
+ 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]);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
new file mode 100644
index 000000000..fe57ed5d5
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
@@ -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;
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php
new file mode 100644
index 000000000..9ad10a9b0
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php
@@ -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);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
new file mode 100644
index 000000000..a64e1821a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace GuzzleHttp\Handler;
+
+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
+ );
+ }
+ }
+ }
+
+ // 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) {
+ }
+
+ $this->processMessages();
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
new file mode 100644
index 000000000..1bc39f4b4
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
@@ -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);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php
new file mode 100644
index 000000000..5554b8fa9
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php
@@ -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)];
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
new file mode 100644
index 000000000..77ffed521
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -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 \Psr\Http\Message\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);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
new file mode 100644
index 000000000..f045b526c
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
@@ -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);
+ };
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
new file mode 100644
index 000000000..61632f564
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
@@ -0,0 +1,615 @@
+<?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);
+ }
+
+ $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);
+ }
+ };
+ }
+}