summaryrefslogtreecommitdiff
path: root/inc/mailgun/php-http/curl-client/src
diff options
context:
space:
mode:
Diffstat (limited to 'inc/mailgun/php-http/curl-client/src')
-rw-r--r--inc/mailgun/php-http/curl-client/src/Client.php371
-rw-r--r--inc/mailgun/php-http/curl-client/src/CurlPromise.php108
-rw-r--r--inc/mailgun/php-http/curl-client/src/MultiRunner.php127
-rw-r--r--inc/mailgun/php-http/curl-client/src/PromiseCore.php224
-rw-r--r--inc/mailgun/php-http/curl-client/src/ResponseBuilder.php21
5 files changed, 851 insertions, 0 deletions
diff --git a/inc/mailgun/php-http/curl-client/src/Client.php b/inc/mailgun/php-http/curl-client/src/Client.php
new file mode 100644
index 0000000..5696ab3
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/Client.php
@@ -0,0 +1,371 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Client\Exception;
+use Http\Client\HttpAsyncClient;
+use Http\Client\HttpClient;
+use Http\Discovery\MessageFactoryDiscovery;
+use Http\Discovery\StreamFactoryDiscovery;
+use Http\Message\MessageFactory;
+use Http\Message\StreamFactory;
+use Http\Promise\Promise;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * PSR-7 compatible cURL based HTTP client
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @author Михаил Красильников <m.krasilnikov@yandex.ru>
+ * @author Blake Williams <github@shabbyrobe.org>
+ *
+ * @api
+ * @since 1.0
+ */
+class Client implements HttpClient, HttpAsyncClient
+{
+ /**
+ * cURL options
+ *
+ * @var array
+ */
+ private $options;
+
+ /**
+ * PSR-7 message factory
+ *
+ * @var MessageFactory
+ */
+ private $messageFactory;
+
+ /**
+ * PSR-7 stream factory
+ *
+ * @var StreamFactory
+ */
+ private $streamFactory;
+
+ /**
+ * cURL synchronous requests handle
+ *
+ * @var resource|null
+ */
+ private $handle = null;
+
+ /**
+ * Simultaneous requests runner
+ *
+ * @var MultiRunner|null
+ */
+ private $multiRunner = null;
+
+ /**
+ * Create new client
+ *
+ * @param MessageFactory|null $messageFactory HTTP Message factory
+ * @param StreamFactory|null $streamFactory HTTP Stream factory
+ * @param array $options cURL options (see http://php.net/curl_setopt)
+ *
+ * @throws \Http\Discovery\Exception\NotFoundException If factory discovery failed.
+ *
+ * @since 1.0
+ */
+ public function __construct(
+ MessageFactory $messageFactory = null,
+ StreamFactory $streamFactory = null,
+ array $options = []
+ ) {
+ $this->messageFactory = $messageFactory ?: MessageFactoryDiscovery::find();
+ $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find();
+ $this->options = $options;
+ }
+
+ /**
+ * Release resources if still active
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->handle)) {
+ curl_close($this->handle);
+ }
+ }
+
+ /**
+ * Sends a PSR-7 request.
+ *
+ * @param RequestInterface $request
+ *
+ * @return ResponseInterface
+ *
+ * @throws \Http\Client\Exception\NetworkException In case of network problems.
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException If creating the body stream fails.
+ *
+ * @since 1.6 \UnexpectedValueException replaced with RequestException.
+ * @since 1.6 Throw NetworkException on network errors.
+ * @since 1.0
+ */
+ public function sendRequest(RequestInterface $request)
+ {
+ $responseBuilder = $this->createResponseBuilder();
+ $options = $this->createCurlOptions($request, $responseBuilder);
+
+ if (is_resource($this->handle)) {
+ curl_reset($this->handle);
+ } else {
+ $this->handle = curl_init();
+ }
+
+ curl_setopt_array($this->handle, $options);
+ curl_exec($this->handle);
+
+ $errno = curl_errno($this->handle);
+ switch ($errno) {
+ case CURLE_OK:
+ // All OK, no actions needed.
+ break;
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_OPERATION_TIMEOUTED:
+ case CURLE_SSL_CONNECT_ERROR:
+ throw new Exception\NetworkException(curl_error($this->handle), $request);
+ default:
+ throw new Exception\RequestException(curl_error($this->handle), $request);
+ }
+
+ $response = $responseBuilder->getResponse();
+ $response->getBody()->seek(0);
+
+ return $response;
+ }
+
+ /**
+ * Sends a PSR-7 request in an asynchronous way.
+ *
+ * @param RequestInterface $request
+ *
+ * @return Promise
+ *
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException If creating the body stream fails.
+ *
+ * @since 1.6 \UnexpectedValueException replaced with RequestException.
+ * @since 1.0
+ */
+ public function sendAsyncRequest(RequestInterface $request)
+ {
+ if (!$this->multiRunner instanceof MultiRunner) {
+ $this->multiRunner = new MultiRunner();
+ }
+
+ $handle = curl_init();
+ $responseBuilder = $this->createResponseBuilder();
+ $options = $this->createCurlOptions($request, $responseBuilder);
+ curl_setopt_array($handle, $options);
+
+ $core = new PromiseCore($request, $handle, $responseBuilder);
+ $promise = new CurlPromise($core, $this->multiRunner);
+ $this->multiRunner->add($core);
+
+ return $promise;
+ }
+
+ /**
+ * Generates cURL options
+ *
+ * @param RequestInterface $request
+ * @param ResponseBuilder $responseBuilder
+ *
+ * @throws \Http\Client\Exception\RequestException On invalid request.
+ * @throws \InvalidArgumentException For invalid header names or values.
+ * @throws \RuntimeException if can not read body
+ *
+ * @return array
+ */
+ private function createCurlOptions(RequestInterface $request, ResponseBuilder $responseBuilder)
+ {
+ $options = $this->options;
+
+ $options[CURLOPT_HEADER] = false;
+ $options[CURLOPT_RETURNTRANSFER] = false;
+ $options[CURLOPT_FOLLOWLOCATION] = false;
+
+ try {
+ $options[CURLOPT_HTTP_VERSION]
+ = $this->getProtocolVersion($request->getProtocolVersion());
+ } catch (\UnexpectedValueException $e) {
+ throw new Exception\RequestException($e->getMessage(), $request);
+ }
+ $options[CURLOPT_URL] = (string) $request->getUri();
+
+ $options = $this->addRequestBodyOptions($request, $options);
+
+ $options[CURLOPT_HTTPHEADER] = $this->createHeaders($request, $options);
+
+ if ($request->getUri()->getUserInfo()) {
+ $options[CURLOPT_USERPWD] = $request->getUri()->getUserInfo();
+ }
+
+ $options[CURLOPT_HEADERFUNCTION] = function ($ch, $data) use ($responseBuilder) {
+ $str = trim($data);
+ if ('' !== $str) {
+ if (strpos(strtolower($str), 'http/') === 0) {
+ $responseBuilder->setStatus($str)->getResponse();
+ } else {
+ $responseBuilder->addHeader($str);
+ }
+ }
+
+ return strlen($data);
+ };
+
+ $options[CURLOPT_WRITEFUNCTION] = function ($ch, $data) use ($responseBuilder) {
+ return $responseBuilder->getResponse()->getBody()->write($data);
+ };
+
+ return $options;
+ }
+
+ /**
+ * Return cURL constant for specified HTTP version
+ *
+ * @param string $requestVersion
+ *
+ * @throws \UnexpectedValueException if unsupported version requested
+ *
+ * @return int
+ */
+ private function getProtocolVersion($requestVersion)
+ {
+ switch ($requestVersion) {
+ case '1.0':
+ return CURL_HTTP_VERSION_1_0;
+ case '1.1':
+ return CURL_HTTP_VERSION_1_1;
+ case '2.0':
+ if (defined('CURL_HTTP_VERSION_2_0')) {
+ return CURL_HTTP_VERSION_2_0;
+ }
+ throw new \UnexpectedValueException('libcurl 7.33 needed for HTTP 2.0 support');
+ }
+
+ return CURL_HTTP_VERSION_NONE;
+ }
+
+ /**
+ * Add request body related cURL options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return array
+ */
+ private function addRequestBodyOptions(RequestInterface $request, array $options)
+ {
+ /*
+ * Some HTTP methods cannot have payload:
+ *
+ * - GET — cURL will automatically change method to PUT or POST if we set CURLOPT_UPLOAD or
+ * CURLOPT_POSTFIELDS.
+ * - HEAD — cURL treats HEAD as GET request with a same restrictions.
+ * - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request.
+ */
+ if (!in_array($request->getMethod(), ['GET', 'HEAD', 'TRACE'], true)) {
+ $body = $request->getBody();
+ $bodySize = $body->getSize();
+ if ($bodySize !== 0) {
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ // Message has non empty body.
+ if (null === $bodySize || $bodySize > 1024 * 1024) {
+ // Avoid full loading large or unknown size body into memory
+ $options[CURLOPT_UPLOAD] = true;
+ if (null !== $bodySize) {
+ $options[CURLOPT_INFILESIZE] = $bodySize;
+ }
+ $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ } else {
+ // Small body can be loaded into memory
+ $options[CURLOPT_POSTFIELDS] = (string) $body;
+ }
+ }
+ }
+
+ if ($request->getMethod() === 'HEAD') {
+ // This will set HTTP method to "HEAD".
+ $options[CURLOPT_NOBODY] = true;
+ } elseif ($request->getMethod() !== 'GET') {
+ // GET is a default method. Other methods should be specified explicitly.
+ $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Create headers array for CURLOPT_HTTPHEADER
+ *
+ * @param RequestInterface $request
+ * @param array $options cURL options
+ *
+ * @return string[]
+ */
+ private function createHeaders(RequestInterface $request, array $options)
+ {
+ $curlHeaders = [];
+ $headers = $request->getHeaders();
+ foreach ($headers as $name => $values) {
+ $header = strtolower($name);
+ if ('expect' === $header) {
+ // curl-client does not support "Expect-Continue", so dropping "expect" headers
+ continue;
+ }
+ if ('content-length' === $header) {
+ if (array_key_exists(CURLOPT_POSTFIELDS, $options)) {
+ // Small body content length can be calculated here.
+ $values = [strlen($options[CURLOPT_POSTFIELDS])];
+ } elseif (!array_key_exists(CURLOPT_READFUNCTION, $options)) {
+ // Else if there is no body, forcing "Content-length" to 0
+ $values = [0];
+ }
+ }
+ foreach ($values as $value) {
+ $curlHeaders[] = $name . ': ' . $value;
+ }
+ }
+ /*
+ * curl-client does not support "Expect-Continue", but cURL adds "Expect" header by default.
+ * We can not suppress it, but we can set it to empty.
+ */
+ $curlHeaders[] = 'Expect:';
+
+ return $curlHeaders;
+ }
+
+ /**
+ * Create new ResponseBuilder instance
+ *
+ * @return ResponseBuilder
+ *
+ * @throws \RuntimeException If creating the stream from $body fails.
+ */
+ private function createResponseBuilder()
+ {
+ try {
+ $body = $this->streamFactory->createStream(fopen('php://temp', 'w+b'));
+ } catch (\InvalidArgumentException $e) {
+ throw new \RuntimeException('Can not create "php://temp" stream.');
+ }
+ $response = $this->messageFactory->createResponse(200, null, [], $body);
+
+ return new ResponseBuilder($response);
+ }
+}
diff --git a/inc/mailgun/php-http/curl-client/src/CurlPromise.php b/inc/mailgun/php-http/curl-client/src/CurlPromise.php
new file mode 100644
index 0000000..68a775c
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/CurlPromise.php
@@ -0,0 +1,108 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Promise\Promise;
+
+/**
+ * Promise represents a response that may not be available yet, but will be resolved at some point
+ * in future. It acts like a proxy to the actual response.
+ *
+ * This interface is an extension of the promises/a+ specification https://promisesaplus.com/
+ * Value is replaced by an object where its class implement a Psr\Http\Message\RequestInterface.
+ * Reason is replaced by an object where its class implement a Http\Client\Exception.
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @author Михаил Красильников <m.krasilnikov@yandex.ru>
+ */
+class CurlPromise implements Promise
+{
+ /**
+ * Shared promise core
+ *
+ * @var PromiseCore
+ */
+ private $core;
+
+ /**
+ * Requests runner
+ *
+ * @var MultiRunner
+ */
+ private $runner;
+
+ /**
+ * Create new promise.
+ *
+ * @param PromiseCore $core Shared promise core
+ * @param MultiRunner $runner Simultaneous requests runner
+ */
+ public function __construct(PromiseCore $core, MultiRunner $runner)
+ {
+ $this->core = $core;
+ $this->runner = $runner;
+ }
+
+ /**
+ * Add behavior for when the promise is resolved or rejected.
+ *
+ * If you do not care about one of the cases, you can set the corresponding callable to null
+ * The callback will be called when the response or exception arrived and never more than once.
+ *
+ * @param callable $onFulfilled Called when a response will be available.
+ * @param callable $onRejected Called when an error happens.
+ *
+ * You must always return the Response in the interface or throw an Exception.
+ *
+ * @return Promise Always returns a new promise which is resolved with value of the executed
+ * callback (onFulfilled / onRejected).
+ */
+ public function then(callable $onFulfilled = null, callable $onRejected = null)
+ {
+ if ($onFulfilled) {
+ $this->core->addOnFulfilled($onFulfilled);
+ }
+ if ($onRejected) {
+ $this->core->addOnRejected($onRejected);
+ }
+
+ return new self($this->core, $this->runner);
+ }
+
+ /**
+ * Get the state of the promise, one of PENDING, FULFILLED or REJECTED.
+ *
+ * @return string
+ */
+ public function getState()
+ {
+ return $this->core->getState();
+ }
+
+ /**
+ * Wait for the promise to be fulfilled or rejected.
+ *
+ * When this method returns, the request has been resolved and the appropriate callable has terminated.
+ *
+ * When called with the unwrap option
+ *
+ * @param bool $unwrap Whether to return resolved value / throw reason or not
+ *
+ * @return \Psr\Http\Message\ResponseInterface|null Resolved value, null if $unwrap is set to false
+ *
+ * @throws \Http\Client\Exception The rejection reason.
+ */
+ public function wait($unwrap = true)
+ {
+ $this->runner->wait($this->core);
+
+ if ($unwrap) {
+ if ($this->core->getState() === self::REJECTED) {
+ throw $this->core->getException();
+ }
+
+ return $this->core->getResponse();
+ }
+ return null;
+ }
+}
diff --git a/inc/mailgun/php-http/curl-client/src/MultiRunner.php b/inc/mailgun/php-http/curl-client/src/MultiRunner.php
new file mode 100644
index 0000000..9094c0f
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/MultiRunner.php
@@ -0,0 +1,127 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Client\Exception\RequestException;
+
+/**
+ * Simultaneous requests runner
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @author Михаил Красильников <m.krasilnikov@yandex.ru>
+ */
+class MultiRunner
+{
+ /**
+ * cURL multi handle
+ *
+ * @var resource|null
+ */
+ private $multiHandle = null;
+
+ /**
+ * Awaiting cores
+ *
+ * @var PromiseCore[]
+ */
+ private $cores = [];
+
+ /**
+ * Release resources if still active
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->multiHandle)) {
+ curl_multi_close($this->multiHandle);
+ }
+ }
+
+ /**
+ * Add promise to runner
+ *
+ * @param PromiseCore $core
+ */
+ public function add(PromiseCore $core)
+ {
+ foreach ($this->cores as $existed) {
+ if ($existed === $core) {
+ return;
+ }
+ }
+
+ $this->cores[] = $core;
+
+ if (null === $this->multiHandle) {
+ $this->multiHandle = curl_multi_init();
+ }
+ curl_multi_add_handle($this->multiHandle, $core->getHandle());
+ }
+
+ /**
+ * Remove promise from runner
+ *
+ * @param PromiseCore $core
+ */
+ public function remove(PromiseCore $core)
+ {
+ foreach ($this->cores as $index => $existed) {
+ if ($existed === $core) {
+ curl_multi_remove_handle($this->multiHandle, $core->getHandle());
+ unset($this->cores[$index]);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Wait for request(s) to be completed.
+ *
+ * @param PromiseCore|null $targetCore
+ */
+ public function wait(PromiseCore $targetCore = null)
+ {
+ do {
+ $status = curl_multi_exec($this->multiHandle, $active);
+ $info = curl_multi_info_read($this->multiHandle);
+ if (false !== $info) {
+ $core = $this->findCoreByHandle($info['handle']);
+
+ if (null === $core) {
+ // We have no promise for this handle. Drop it.
+ curl_multi_remove_handle($this->multiHandle, $info['handle']);
+ continue;
+ }
+
+ if (CURLE_OK === $info['result']) {
+ $core->fulfill();
+ } else {
+ $error = curl_error($core->getHandle());
+ $core->reject(new RequestException($error, $core->getRequest()));
+ }
+ $this->remove($core);
+
+ // This is a promise we are waited for. So exiting wait().
+ if ($core === $targetCore) {
+ return;
+ }
+ }
+ } while ($status === CURLM_CALL_MULTI_PERFORM || $active);
+ }
+
+ /**
+ * Find core by handle.
+ *
+ * @param resource $handle
+ *
+ * @return PromiseCore|null
+ */
+ private function findCoreByHandle($handle)
+ {
+ foreach ($this->cores as $core) {
+ if ($core->getHandle() === $handle) {
+ return $core;
+ }
+ }
+ return null;
+ }
+}
diff --git a/inc/mailgun/php-http/curl-client/src/PromiseCore.php b/inc/mailgun/php-http/curl-client/src/PromiseCore.php
new file mode 100644
index 0000000..f1a3aa5
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/PromiseCore.php
@@ -0,0 +1,224 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Client\Exception;
+use Http\Promise\Promise;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Shared promises core.
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @author Михаил Красильников <m.krasilnikov@yandex.ru>
+ */
+class PromiseCore
+{
+ /**
+ * HTTP request
+ *
+ * @var RequestInterface
+ */
+ private $request;
+
+ /**
+ * cURL handle
+ *
+ * @var resource
+ */
+ private $handle;
+
+ /**
+ * Response builder
+ *
+ * @var ResponseBuilder
+ */
+ private $responseBuilder;
+
+ /**
+ * Promise state
+ *
+ * @var string
+ */
+ private $state;
+
+ /**
+ * Exception
+ *
+ * @var Exception|null
+ */
+ private $exception = null;
+
+ /**
+ * Functions to call when a response will be available.
+ *
+ * @var callable[]
+ */
+ private $onFulfilled = [];
+
+ /**
+ * Functions to call when an error happens.
+ *
+ * @var callable[]
+ */
+ private $onRejected = [];
+
+ /**
+ * Create shared core.
+ *
+ * @param RequestInterface $request HTTP request
+ * @param resource $handle cURL handle
+ * @param ResponseBuilder $responseBuilder
+ */
+ public function __construct(
+ RequestInterface $request,
+ $handle,
+ ResponseBuilder $responseBuilder
+ ) {
+ assert('is_resource($handle)');
+ assert('get_resource_type($handle) === "curl"');
+
+ $this->request = $request;
+ $this->handle = $handle;
+ $this->responseBuilder = $responseBuilder;
+ $this->state = Promise::PENDING;
+ }
+
+ /**
+ * Add on fulfilled callback.
+ *
+ * @param callable $callback
+ */
+ public function addOnFulfilled(callable $callback)
+ {
+ if ($this->getState() === Promise::PENDING) {
+ $this->onFulfilled[] = $callback;
+ } elseif ($this->getState() === Promise::FULFILLED) {
+ $response = call_user_func($callback, $this->responseBuilder->getResponse());
+ if ($response instanceof ResponseInterface) {
+ $this->responseBuilder->setResponse($response);
+ }
+ }
+ }
+
+ /**
+ * Add on rejected callback.
+ *
+ * @param callable $callback
+ */
+ public function addOnRejected(callable $callback)
+ {
+ if ($this->getState() === Promise::PENDING) {
+ $this->onRejected[] = $callback;
+ } elseif ($this->getState() === Promise::REJECTED) {
+ $this->exception = call_user_func($callback, $this->exception);
+ }
+ }
+
+ /**
+ * Return cURL handle
+ *
+ * @return resource
+ */
+ public function getHandle()
+ {
+ return $this->handle;
+ }
+
+ /**
+ * Get the state of the promise, one of PENDING, FULFILLED or REJECTED.
+ *
+ * @return string
+ */
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ /**
+ * Return request
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Return the value of the promise (fulfilled).
+ *
+ * @return ResponseInterface Response Object only when the Promise is fulfilled.
+ */
+ public function getResponse()
+ {
+ return $this->responseBuilder->getResponse();
+ }
+
+ /**
+ * Get the reason why the promise was rejected.
+ *
+ * If the exception is an instance of Http\Client\Exception\HttpException it will contain
+ * the response object with the status code and the http reason.
+ *
+ * @return Exception Exception Object only when the Promise is rejected.
+ *
+ * @throws \LogicException When the promise is not rejected.
+ */
+ public function getException()
+ {
+ if (null === $this->exception) {
+ throw new \LogicException('Promise is not rejected');
+ }
+
+ return $this->exception;
+ }
+
+ /**
+ * Fulfill promise.
+ */
+ public function fulfill()
+ {
+ $this->state = Promise::FULFILLED;
+ $response = $this->responseBuilder->getResponse();
+ try {
+ $response->getBody()->seek(0);
+ } catch (\RuntimeException $e) {
+ $exception = new Exception\TransferException($e->getMessage(), $e->getCode(), $e);
+ $this->reject($exception);
+
+ return;
+ }
+
+ while (count($this->onFulfilled) > 0) {
+ $callback = array_shift($this->onFulfilled);
+ $response = call_user_func($callback, $response);
+ }
+
+ if ($response instanceof ResponseInterface) {
+ $this->responseBuilder->setResponse($response);
+ }
+ }
+
+ /**
+ * Reject promise.
+ *
+ * @param Exception $exception Reject reason.
+ */
+ public function reject(Exception $exception)
+ {
+ $this->exception = $exception;
+ $this->state = Promise::REJECTED;
+
+ while (count($this->onRejected) > 0) {
+ $callback = array_shift($this->onRejected);
+ try {
+ $exception = call_user_func($callback, $this->exception);
+ $this->exception = $exception;
+ } catch (Exception $exception) {
+ $this->exception = $exception;
+ }
+ }
+ }
+}
diff --git a/inc/mailgun/php-http/curl-client/src/ResponseBuilder.php b/inc/mailgun/php-http/curl-client/src/ResponseBuilder.php
new file mode 100644
index 0000000..99e79db
--- /dev/null
+++ b/inc/mailgun/php-http/curl-client/src/ResponseBuilder.php
@@ -0,0 +1,21 @@
+<?php
+namespace Http\Client\Curl;
+
+use Http\Message\Builder\ResponseBuilder as OriginalResponseBuilder;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Extended response builder
+ */
+class ResponseBuilder extends OriginalResponseBuilder
+{
+ /**
+ * Replace response with a new instance
+ *
+ * @param ResponseInterface $response
+ */
+ public function setResponse(ResponseInterface $response)
+ {
+ $this->response = $response;
+ }
+}