diff options
| author | Carson Fleming <cflems@cflems.net> | 2017-03-02 22:49:24 -0500 |
|---|---|---|
| committer | Carson Fleming <cflems@cflems.net> | 2017-03-02 22:49:24 -0500 |
| commit | b76e2ff898b23745d4c9aaee49eeb7d88f2896ab (patch) | |
| tree | 9b794be8db310a575d70165d9ebde0a183b61b01 /inc/mailgun/php-http/curl-client | |
| parent | bfcc9f7a7656a2db0c905b3c13114664f00f6c37 (diff) | |
| download | bulletin-b76e2ff898b23745d4c9aaee49eeb7d88f2896ab.tar.gz | |
Updated mailgun plugin
Diffstat (limited to 'inc/mailgun/php-http/curl-client')
| -rw-r--r-- | inc/mailgun/php-http/curl-client/.php_cs | 9 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/.styleci.yml | 4 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/CHANGELOG.md | 163 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/LICENSE | 19 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/README.md | 44 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/composer.json | 50 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/puli.json | 242 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/src/Client.php | 371 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/src/CurlPromise.php | 108 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/src/MultiRunner.php | 127 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/src/PromiseCore.php | 224 | ||||
| -rw-r--r-- | inc/mailgun/php-http/curl-client/src/ResponseBuilder.php | 21 |
12 files changed, 1382 insertions, 0 deletions
diff --git a/inc/mailgun/php-http/curl-client/.php_cs b/inc/mailgun/php-http/curl-client/.php_cs new file mode 100644 index 0000000..febeee5 --- /dev/null +++ b/inc/mailgun/php-http/curl-client/.php_cs @@ -0,0 +1,9 @@ +<?php + +return Symfony\CS\Config\Config::create() + ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers([]) + ->finder( + Symfony\CS\Finder\DefaultFinder::create()->in(__DIR__ . '/src') + ) +; diff --git a/inc/mailgun/php-http/curl-client/.styleci.yml b/inc/mailgun/php-http/curl-client/.styleci.yml new file mode 100644 index 0000000..4d43c93 --- /dev/null +++ b/inc/mailgun/php-http/curl-client/.styleci.yml @@ -0,0 +1,4 @@ +preset: psr2 +finder: + path: + - "src" diff --git a/inc/mailgun/php-http/curl-client/CHANGELOG.md b/inc/mailgun/php-http/curl-client/CHANGELOG.md new file mode 100644 index 0000000..a3f52d1 --- /dev/null +++ b/inc/mailgun/php-http/curl-client/CHANGELOG.md @@ -0,0 +1,163 @@ +# Change Log + +## 1.7 - 2017-02-09 + +### Changed + +- #30: Make sure we rewind streams + +## 1.6.2 - 2017-01-02 + +### Fixed + +- #29: Request not using CURLOPT_POSTFIELDS have content-length set to + +### Changed + +- Use binary mode to create response body stream. + + +## 1.6.1 - 2016-11-11 + +### Fixed + +- #27: ErrorPlugin and sendAsyncRequest() incompatibility + + +## 1.6 - 2016-09-12 + +### Changed + +- `Client::sendRequest` now throws `Http\Client\Exception\NetworkException` on network errors. +- `\UnexpectedValueException` replaced with `Http\Client\Exception\RequestException` in + `Client::sendRequest` and `Client::sendAsyncRequest` + + +## 1.5.1 - 2016-08-29 + +### Fixed + +- #26: Combining CurlClient with StopwatchPlugin causes Promise onRejected handler to never be + invoked. + + +## 1.5 - 2016-08-03 + +### Changed + +- Request body can be send with any method except GET, HEAD and TRACE. +- #25: Make discovery a hard dependency. + + +## 1.4.2 - 2016-06-14 + +### Added + +- #23: "php-http/async-client-implementation" added to "provide" section. + + +## 1.4.1 - 2016-05-30 + +### Fixed + +- #22: Cannot create the client using `HttpClientDiscovery`. + + +## 1.4 - 2016-03-30 + +### Changed + +- #20: Minimize memory usage when reading large response body. + + +## 1.3 - 2016-03-14 + +### Fixed + +- #18: Invalid "Expect" header. + +### Removed + +- #13: Remove HeaderParser. + + +## 1.2 - 2016-03-09 + +### Added + +- #16: Make sure discovery can find the curl client + +### Fixed + +- #15: "Out of memory" sending large files. + + +## 1.1.0 - 2016-01-29 + +### Changed + +- Switch to php-http/message 1.0. + + +## 1.0.0 - 2016-01-28 + +First stable release. + + +## 0.7.0 - 2016-01-26 + +### Changed + +- Migrate from `php-http/discovery` and `php-http/utils` to `php-http/message`. + +## 0.6.0 - 2016-01-12 + +### Changed + +- Root namespace changed from `Http\Curl` to `Http\Client\Curl`. +- Main client class name renamed from `CurlHttpClient` to `Client`. +- Minimum required [php-http/discovery](https://packagist.org/packages/php-http/discovery) + version changed to 0.5. + + +## 0.5.0 - 2015-12-18 + +### Changed + +- Compatibility with php-http/httplug 1.0 beta +- Switch to php-http/discovery 0.4 + + +## 0.4.0 - 2015-12-16 + +### Changed + +- Switch to php-http/message-factory 1.0 + + +## 0.3.1 - 2015-12-14 + +### Changed + +- Requirements fixed. + + +## 0.3.0 - 2015-11-24 + +### Changed + +- Use cURL constants as options keys. + + +## 0.2.0 - 2015-11-17 + +### Added + +- HttpAsyncClient support. + + +## 0.1.0 - 2015-11-11 + +### Added + +- Initial release diff --git a/inc/mailgun/php-http/curl-client/LICENSE b/inc/mailgun/php-http/curl-client/LICENSE new file mode 100644 index 0000000..8e2c4a0 --- /dev/null +++ b/inc/mailgun/php-http/curl-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP HTTP Team <team@php-http.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/inc/mailgun/php-http/curl-client/README.md b/inc/mailgun/php-http/curl-client/README.md new file mode 100644 index 0000000..fc60b7e --- /dev/null +++ b/inc/mailgun/php-http/curl-client/README.md @@ -0,0 +1,44 @@ +# Curl client for PHP HTTP + +[](https://github.com/php-http/curl-client/releases) +[](LICENSE) +[](https://travis-ci.org/php-http/curl-client) +[](https://scrutinizer-ci.com/g/php-http/curl-client) +[](https://scrutinizer-ci.com/g/php-http/curl-client) +[](https://packagist.org/packages/php-http/curl-client) + +The cURL client use the cURL PHP extension which must be activated in your `php.ini`. + + +## Install + +Via Composer + +``` bash +$ composer require php-http/curl-client +``` + +## Documentation + +Please see the [official documentation](http://docs.php-http.org/en/latest/clients/curl-client.html). + +## Testing + +``` bash +$ composer test +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. + + +## Security + +If you discover any security related issues, please contact us at +[security@php-http.org](mailto:security@php-http.org). + + +## License + +The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/inc/mailgun/php-http/curl-client/composer.json b/inc/mailgun/php-http/curl-client/composer.json new file mode 100644 index 0000000..5d16222 --- /dev/null +++ b/inc/mailgun/php-http/curl-client/composer.json @@ -0,0 +1,50 @@ +{ + "name": "php-http/curl-client", + "description": "cURL client for PHP-HTTP", + "license": "MIT", + "keywords": ["http", "curl"], + "homepage": "http://php-http.org", + "authors": [ + { + "name": "Михаил Красильников", + "email": "m.krasilnikov@yandex.ru" + } + ], + "prefer-stable": true, + "minimum-stability": "beta", + "config": { + "bin-dir": "vendor/bin" + }, + "require": { + "php": "^5.5 || ^7.0", + "ext-curl": "*", + "php-http/httplug": "^1.0", + "php-http/message-factory": "^1.0.2", + "php-http/message": "^1.2", + "php-http/discovery": "^1.0" + }, + "require-dev": { + "guzzlehttp/psr7": "^1.0", + "php-http/client-integration-tests": "^0.5.1", + "phpunit/phpunit": "^4.8.27", + "zendframework/zend-diactoros": "^1.0" + }, + "autoload": { + "psr-4": { + "Http\\Client\\Curl\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Http\\Client\\Curl\\Tests\\": "tests/" + } + }, + "provide": { + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0" + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml" + } +} diff --git a/inc/mailgun/php-http/curl-client/puli.json b/inc/mailgun/php-http/curl-client/puli.json new file mode 100644 index 0000000..b35768d --- /dev/null +++ b/inc/mailgun/php-http/curl-client/puli.json @@ -0,0 +1,242 @@ +{ + "version": "1.0", + "name": "php-http/curl-client", + "bindings": { + "98239b8b-103b-4f47-94c7-4cba49a05a1f": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpAsyncClient" + }, + "a6a79968-2aa5-427c-bbe1-a581d9a48321": { + "_class": "Puli\\Discovery\\Binding\\ClassBinding", + "class": "Http\\Client\\Curl\\Client", + "type": "Http\\Client\\HttpClient" + } + }, + "config": { + "bootstrap-file": "vendor/autoload.php" + }, + "packages": { + "clue/stream-filter": { + "install-path": "vendor/clue/stream-filter", + "installer": "composer", + "env": "dev" + }, + "doctrine/instantiator": { + "install-path": "vendor/doctrine/instantiator", + "installer": "composer", + "env": "dev" + }, + "guzzlehttp/psr7": { + "install-path": "vendor/guzzlehttp/psr7", + "installer": "composer", + "env": "dev" + }, + "justinrainbow/json-schema": { + "install-path": "vendor/justinrainbow/json-schema", + "installer": "composer", + "env": "dev" + }, + "paragonie/random_compat": { + "install-path": "vendor/paragonie/random_compat", + "installer": "composer", + "env": "dev" + }, + "php-http/adapter-integration-tests": { + "install-path": "vendor/php-http/adapter-integration-tests", + "installer": "composer", + "env": "dev" + }, + "php-http/discovery": { + "install-path": "vendor/php-http/discovery", + "installer": "composer", + "env": "dev" + }, + "php-http/httplug": { + "install-path": "vendor/php-http/httplug", + "installer": "composer" + }, + "php-http/message": { + "install-path": "vendor/php-http/message", + "installer": "composer", + "env": "dev" + }, + "php-http/message-factory": { + "install-path": "vendor/php-http/message-factory", + "installer": "composer" + }, + "php-http/promise": { + "install-path": "vendor/php-http/promise", + "installer": "composer" + }, + "phpdocumentor/reflection-docblock": { + "install-path": "vendor/phpdocumentor/reflection-docblock", + "installer": "composer", + "env": "dev" + }, + "phpspec/prophecy": { + "install-path": "vendor/phpspec/prophecy", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-code-coverage": { + "install-path": "vendor/phpunit/php-code-coverage", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-file-iterator": { + "install-path": "vendor/phpunit/php-file-iterator", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-text-template": { + "install-path": "vendor/phpunit/php-text-template", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-timer": { + "install-path": "vendor/phpunit/php-timer", + "installer": "composer", + "env": "dev" + }, + "phpunit/php-token-stream": { + "install-path": "vendor/phpunit/php-token-stream", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit": { + "install-path": "vendor/phpunit/phpunit", + "installer": "composer", + "env": "dev" + }, + "phpunit/phpunit-mock-objects": { + "install-path": "vendor/phpunit/phpunit-mock-objects", + "installer": "composer", + "env": "dev" + }, + "psr/http-message": { + "install-path": "vendor/psr/http-message", + "installer": "composer" + }, + "psr/log": { + "install-path": "vendor/psr/log", + "installer": "composer", + "env": "dev" + }, + "puli/composer-plugin": { + "install-path": "vendor/puli/composer-plugin", + "installer": "composer", + "env": "dev" + }, + "puli/discovery": { + "install-path": "vendor/puli/discovery", + "installer": "composer", + "env": "dev" + }, + "puli/repository": { + "install-path": "vendor/puli/repository", + "installer": "composer", + "env": "dev" + }, + "puli/url-generator": { + "install-path": "vendor/puli/url-generator", + "installer": "composer", + "env": "dev" + }, + "ramsey/uuid": { + "install-path": "vendor/ramsey/uuid", + "installer": "composer", + "env": "dev" + }, + "sebastian/comparator": { + "install-path": "vendor/sebastian/comparator", + "installer": "composer", + "env": "dev" + }, + "sebastian/diff": { + "install-path": "vendor/sebastian/diff", + "installer": "composer", + "env": "dev" + }, + "sebastian/environment": { + "install-path": "vendor/sebastian/environment", + "installer": "composer", + "env": "dev" + }, + "sebastian/exporter": { + "install-path": "vendor/sebastian/exporter", + "installer": "composer", + "env": "dev" + }, + "sebastian/global-state": { + "install-path": "vendor/sebastian/global-state", + "installer": "composer", + "env": "dev" + }, + "sebastian/recursion-context": { + "install-path": "vendor/sebastian/recursion-context", + "installer": "composer", + "env": "dev" + }, + "sebastian/version": { + "install-path": "vendor/sebastian/version", + "installer": "composer", + "env": "dev" + }, + "seld/jsonlint": { + "install-path": "vendor/seld/jsonlint", + "installer": "composer", + "env": "dev" + }, + "symfony/filesystem": { + "install-path": "vendor/symfony/filesystem", + "installer": "composer", + "env": "dev" + }, + "symfony/process": { + "install-path": "vendor/symfony/process", + "installer": "composer", + "env": "dev" + }, + "symfony/yaml": { + "install-path": "vendor/symfony/yaml", + "installer": "composer", + "env": "dev" + }, + "th3n3rd/cartesian-product": { + "install-path": "vendor/th3n3rd/cartesian-product", + "installer": "composer", + "env": "dev" + }, + "webmozart/assert": { + "install-path": "vendor/webmozart/assert", + "installer": "composer", + "env": "dev" + }, + "webmozart/expression": { + "install-path": "vendor/webmozart/expression", + "installer": "composer", + "env": "dev" + }, + "webmozart/glob": { + "install-path": "vendor/webmozart/glob", + "installer": "composer", + "env": "dev" + }, + "webmozart/json": { + "install-path": "vendor/webmozart/json", + "installer": "composer", + "env": "dev" + }, + "webmozart/path-util": { + "install-path": "vendor/webmozart/path-util", + "installer": "composer", + "env": "dev" + }, + "zendframework/zend-diactoros": { + "install-path": "vendor/zendframework/zend-diactoros", + "installer": "composer", + "env": "dev" + } + } +} 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; + } +} |
