From ed89a08f257e12432dd56514a47d053b5a8b47ea Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Tue, 31 Mar 2015 19:04:21 +0100 Subject: [PATCH] Issue #2461985 by stefan.r: Update Guzzle to latest release --- core/composer.lock | 21 +- core/vendor/composer/installed.json | 225 ++++++++--------- core/vendor/guzzlehttp/guzzle/CHANGELOG.md | 66 +++-- core/vendor/guzzlehttp/guzzle/UPGRADING.md | 2 +- .../guzzlehttp/guzzle/build/packager.php | 2 +- .../vendor/guzzlehttp/guzzle/docs/clients.rst | 37 ++- core/vendor/guzzlehttp/guzzle/docs/events.rst | 30 ++- core/vendor/guzzlehttp/guzzle/docs/faq.rst | 4 +- .../guzzlehttp/guzzle/docs/http-messages.rst | 4 +- core/vendor/guzzlehttp/guzzle/src/Client.php | 117 +++++---- .../guzzlehttp/guzzle/src/ClientInterface.php | 2 +- .../guzzle/src/Event/AbstractRequestEvent.php | 10 + .../src/Event/AbstractRetryableEvent.php | 6 +- .../src/Event/AbstractTransferEvent.php | 12 +- .../src/Message/AppliesHeadersInterface.php | 24 ++ .../guzzle/src/Message/MessageFactory.php | 18 +- core/vendor/guzzlehttp/guzzle/src/Pool.php | 95 +++++-- .../guzzle/src/Post/PostBodyInterface.php | 11 +- .../guzzlehttp/guzzle/src/RequestFsm.php | 235 +++++++----------- .../guzzlehttp/guzzle/src/RingBridge.php | 13 +- .../guzzle/src/Subscriber/Prepare.php | 6 +- .../guzzle/src/Subscriber/Redirect.php | 24 +- .../guzzlehttp/guzzle/src/Transaction.php | 26 +- core/vendor/guzzlehttp/guzzle/src/Url.php | 2 +- .../guzzlehttp/guzzle/tests/ClientTest.php | 39 +++ .../Event/AbstractRetryableEventTest.php | 4 +- .../tests/Event/AbstractTransferEventTest.php | 10 + .../guzzle/tests/IntegrationTest.php | 50 ++++ .../tests/Message/MessageFactoryTest.php | 2 +- .../guzzlehttp/guzzle/tests/PoolTest.php | 88 +++++++ .../guzzle/tests/Post/PostBodyTest.php | 1 - .../guzzle/tests/RequestFsmTest.php | 55 ++-- .../guzzle/tests/RingBridgeTest.php | 18 -- .../guzzle/tests/Subscriber/RedirectTest.php | 19 ++ core/vendor/guzzlehttp/ringphp/.travis.yml | 1 + core/vendor/guzzlehttp/ringphp/CHANGELOG.md | 22 ++ core/vendor/guzzlehttp/ringphp/composer.json | 1 + core/vendor/guzzlehttp/ringphp/docs/index.rst | 10 +- .../ringphp/src/Client/CurlFactory.php | 12 +- .../ringphp/src/Client/CurlHandler.php | 1 + .../ringphp/src/Client/CurlMultiHandler.php | 41 +-- .../ringphp/src/Client/StreamHandler.php | 64 +++-- .../ringphp/src/Future/FutureInterface.php | 18 +- .../tests/Client/StreamHandlerTest.php | 5 +- .../guzzlehttp/ringphp/tests/Client/server.js | 82 +++++- 45 files changed, 982 insertions(+), 553 deletions(-) create mode 100644 core/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php diff --git a/core/composer.lock b/core/composer.lock index 26a3e217bac..78822a0a932 100644 --- a/core/composer.lock +++ b/core/composer.lock @@ -783,16 +783,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "5.0.3", + "version": "5.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282" + "reference": "475b29ccd411f2fa8a408e64576418728c032cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/6c72627de1d66832e4270e36e56acdb0d1d8f282", - "reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/475b29ccd411f2fa8a408e64576418728c032cfa", + "reference": "475b29ccd411f2fa8a408e64576418728c032cfa", "shasum": "" }, "require": { @@ -837,20 +837,20 @@ "rest", "web service" ], - "time": "2014-11-04 07:09:15" + "time": "2015-01-28 01:03:29" }, { "name": "guzzlehttp/ringphp", - "version": "1.0.3", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/guzzle/RingPHP.git", - "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964" + "reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/e7c28f96c5ac12ab0e63412cfc15989756fcb964", - "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/52d868f13570a9a56e5fce6614e0ec75d0f13ac2", + "reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2", "shasum": "" }, "require": { @@ -887,7 +887,8 @@ "homepage": "https://github.com/mtdowling" } ], - "time": "2014-11-04 07:01:14" + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "time": "2015-03-30 01:43:20" }, { "name": "guzzlehttp/streams", diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index 625c282f6f7..5b20eb72b6c 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -1218,66 +1218,6 @@ "templating" ] }, - { - "name": "guzzlehttp/guzzle", - "version": "5.0.3", - "version_normalized": "5.0.3.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/6c72627de1d66832e4270e36e56acdb0d1d8f282", - "reference": "6c72627de1d66832e4270e36e56acdb0d1d8f282", - "shasum": "" - }, - "require": { - "guzzlehttp/ringphp": "~1.0", - "php": ">=5.4.0" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0", - "psr/log": "~1.0" - }, - "time": "2014-11-04 07:09:15", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ] - }, { "name": "egulias/email-validator", "version": "1.2.5", @@ -1382,58 +1322,6 @@ "hhvm" ] }, - { - "name": "guzzlehttp/ringphp", - "version": "1.0.3", - "version_normalized": "1.0.3.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/e7c28f96c5ac12ab0e63412cfc15989756fcb964", - "reference": "e7c28f96c5ac12ab0e63412cfc15989756fcb964", - "shasum": "" - }, - "require": { - "guzzlehttp/streams": "~3.0", - "php": ">=5.4.0", - "react/promise": "~2.0" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" - }, - "time": "2014-11-04 07:01:14", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "GuzzleHttp\\Ring\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ] - }, { "name": "easyrdf/easyrdf", "version": "0.9.0", @@ -3156,5 +3044,118 @@ ], "description": "Symfony BrowserKit Component", "homepage": "http://symfony.com" + }, + { + "name": "guzzlehttp/guzzle", + "version": "5.2.0", + "version_normalized": "5.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "475b29ccd411f2fa8a408e64576418728c032cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/475b29ccd411f2fa8a408e64576418728c032cfa", + "reference": "475b29ccd411f2fa8a408e64576418728c032cfa", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "~1.0", + "php": ">=5.4.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0", + "psr/log": "~1.0" + }, + "time": "2015-01-28 01:03:29", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "guzzlehttp/ringphp", + "version": "1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/52d868f13570a9a56e5fce6614e0ec75d0f13ac2", + "reference": "52d868f13570a9a56e5fce6614e0ec75d0f13ac2", + "shasum": "" + }, + "require": { + "guzzlehttp/streams": "~3.0", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "time": "2015-03-30 01:43:20", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function." } ] diff --git a/core/vendor/guzzlehttp/guzzle/CHANGELOG.md b/core/vendor/guzzlehttp/guzzle/CHANGELOG.md index e997e10430d..caafb425fad 100644 --- a/core/vendor/guzzlehttp/guzzle/CHANGELOG.md +++ b/core/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -1,5 +1,35 @@ # CHANGELOG +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + ## 5.0.3 - 2014-11-03 This change updates query strings so that they are treated as un-encoded values @@ -71,10 +101,10 @@ The breaking changes in this release are relatively minor. The biggest thing to look out for is that request and response objects no longer implement fluent interfaces. -* Removed the fluent interfaces (i.e., ``return $this``) from requests, - responses, ``GuzzleHttp\Collection``, ``GuzzleHttp\Url``, - ``GuzzleHttp\Query``, ``GuzzleHttp\Post\PostBody``, and - ``GuzzleHttp\Cookie\SetCookie``. This blog post provides a good outline of +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. This also makes the Guzzle message interfaces compatible with the current PSR-7 message proposal. @@ -84,7 +114,7 @@ interfaces. moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be - `GuzzleHttp\Pool::batch`, which returns a bjectStorage`. Using functions.php + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php caused problems for many users: they aren't PSR-4 compliant, require an explicit include, and needed an if-guard to ensure that the functions are not declared multiple times. @@ -105,10 +135,10 @@ interfaces. written to. * Removed the `asArray` parameter from `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header - value as an array, then use the newly added ``getHeaderAsArray()`` method of - ``MessageInterface``. This change makes the Guzzle interfaces compatible with + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with the PSR-7 interfaces. -* ``GuzzleHttp\Message\MessageFactory`` no longer allows subclasses to add +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add custom request options using double-dispatch (this was an implementation detail). Instead, you should now provide an associative array to the constructor which is a mapping of the request option name mapping to a @@ -121,9 +151,9 @@ interfaces. * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a `maxLen` parameter. This update makes the Guzzle streams project compatible with the current PSR-7 proposal. - * ``GuzzleHttp\Stream\Stream::__construct``, - ``GuzzleHttp\Stream\Stream::factory``, and - ``GuzzleHttp\Stream\Utils::create`` no longer accept a size in the second + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second argument. They now accept an associative array of options, including the "size" key and "metadata" key which can be used to provide custom metadata. @@ -359,7 +389,7 @@ interfaces. * Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. * Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() * Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin -* Bug fix: Visiting XML attributes first before visting XML children when serializing requests +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests * Bug fix: Properly parsing headers that contain commas contained in quotes * Bug fix: mimetype guessing based on a filename is now case-insensitive @@ -502,7 +532,7 @@ interfaces. directly via interfaces * Removed the injecting of a request object onto a response object. The methods to get and set a request still exist but are a no-op until removed. -* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a `Guzzle\Service\Command\ArrayCommandInterface`. * Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response on a request while the request is still being transferred @@ -517,7 +547,7 @@ interfaces. ## 3.5.0 - 2013-05-13 * Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times -* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove itself from the EventDispatcher) * Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values * Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too @@ -833,15 +863,15 @@ interfaces. * Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body * Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new * Added multiple inheritance to service description commands -* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()`` +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` * Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything * Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles ## 2.8.2 - 2012-07-24 * Bug: Query string values set to 0 are no longer dropped from the query string -* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`` -* Bug: ``+`` is now treated as an encoded space when parsing query strings +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings * QueryString and Collection performance improvements * Allowing dot notation for class paths in filters attribute of a service descriptions @@ -858,7 +888,7 @@ interfaces. * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) * Changed the aggregation functions of QueryString to be static methods * Can now use fromString() with querystrings that have a leading ? -* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters * Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body * Cookies are no longer URL decoded by default * Bug: URI template variables set to null are no longer expanded diff --git a/core/vendor/guzzlehttp/guzzle/UPGRADING.md b/core/vendor/guzzlehttp/guzzle/UPGRADING.md index d0f55bc3537..ee8c39dd469 100644 --- a/core/vendor/guzzlehttp/guzzle/UPGRADING.md +++ b/core/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -503,7 +503,7 @@ allow developers to more easily extend and decorate stream behavior. ## Metadata streams `GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams -that contain additonal metadata accessible via `getMetadata()`. +that contain additional metadata accessible via `getMetadata()`. `GuzzleHttp\Stream\StreamInterface::getMetadata` and `GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. diff --git a/core/vendor/guzzlehttp/guzzle/build/packager.php b/core/vendor/guzzlehttp/guzzle/build/packager.php index 96afa93a476..724bf634991 100644 --- a/core/vendor/guzzlehttp/guzzle/build/packager.php +++ b/core/vendor/guzzlehttp/guzzle/build/packager.php @@ -13,7 +13,7 @@ foreach (['README.md', 'LICENSE'] as $file) { // Copy each dependency to the staging directory. Copy *.php and *.pem files. $packager->recursiveCopy('src', 'GuzzleHttp', ['php']); -$packager->recursiveCopy('vendor/react/promise/src', ''); +$packager->recursiveCopy('vendor/react/promise/src', 'React/Promise'); $packager->recursiveCopy('vendor/guzzlehttp/ringphp/src', 'GuzzleHttp/Ring'); $packager->recursiveCopy('vendor/guzzlehttp/streams/src', 'GuzzleHttp/Stream'); $packager->createAutoloader(['React/Promise/functions.php']); diff --git a/core/vendor/guzzlehttp/guzzle/docs/clients.rst b/core/vendor/guzzlehttp/guzzle/docs/clients.rst index c5aed63f020..0614a25edf7 100644 --- a/core/vendor/guzzlehttp/guzzle/docs/clients.rst +++ b/core/vendor/guzzlehttp/guzzle/docs/clients.rst @@ -173,6 +173,12 @@ response has completed. If an exception occurred while transferring the future response, then the exception encountered will be thrown when dereferencing. +.. note:: + + It depends on the RingPHP handler used by a client, but you typically need + to use the same RingPHP handler in order to utilize asynchronous requests + across multiple clients. + Asynchronous Error Handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -524,7 +530,7 @@ json .. code-block:: php - $request = $client->createRequest('/put', ['json' => ['foo' => 'bar']]); + $request = $client->createRequest('PUT', '/put', ['json' => ['foo' => 'bar']]); echo $request->getHeader('Content-Type'); // application/json echo $request->getBody(); @@ -701,7 +707,15 @@ allow_redirects :Types: - bool - array -:Default: ``['max' => 5, 'strict' => false, 'referer' => true]`` +:Default: + :: + + [ + 'max' => 5, + 'strict' => false, + 'referer' => true, + 'protocols' => ['http', 'https'] + ] Set to ``false`` to disable redirects. @@ -721,19 +735,22 @@ number of 5 redirects. // 200 Pass an associative array containing the 'max' key to specify the maximum -number of redirects, optionally provide a 'strict' key value to specify -whether or not to use strict RFC compliant redirects (meaning redirect POST -requests with POST requests vs. doing what most browsers do which is redirect -POST requests with GET requests), and optionally provide a 'referer' key to -specify whether or not the "Referer" header should be added when redirecting. +number of redirects, provide a 'strict' key value to specify whether or not to +use strict RFC compliant redirects (meaning redirect POST requests with POST +requests vs. doing what most browsers do which is redirect POST requests with +GET requests), provide a 'referer' key to specify whether or not the "Referer" +header should be added when redirecting, and provide a 'protocols' array that +specifies which protocols are supported for redirects (defaults to +``['http', 'https']``). .. code-block:: php $res = $client->get('/redirect/3', [ 'allow_redirects' => [ - 'max' => 10, - 'strict' => true, - 'referer' => true + 'max' => 10, // allow at most 10 redirects. + 'strict' => true, // use "strict" RFC compliant redirects. + 'referer' => true, // add a Referer header + 'protocols' => ['https'] // only allow https URLs ] ]); echo $res->getStatusCode(); diff --git a/core/vendor/guzzlehttp/guzzle/docs/events.rst b/core/vendor/guzzlehttp/guzzle/docs/events.rst index 4dae96b96ce..647dcd0f13b 100644 --- a/core/vendor/guzzlehttp/guzzle/docs/events.rst +++ b/core/vendor/guzzlehttp/guzzle/docs/events.rst @@ -11,12 +11,12 @@ Event Emitters ============== Clients, requests, and any other class that implements the -``GuzzleHttp\Common\HasEmitterInterface`` interface have a -``GuzzleHttp\Common\EventEmitter`` object. You can add event *listeners* and +``GuzzleHttp\Event\HasEmitterInterface`` interface have a +``GuzzleHttp\Event\Emitter`` object. You can add event *listeners* and event *subscribers* to an event *emitter*. emitter - An object that implements ``GuzzleHttp\Common\EventEmitterInterface``. This + An object that implements ``GuzzleHttp\Event\EmitterInterface``. This object emits named events to event listeners. You may register event listeners on subscribers on an emitter. @@ -58,7 +58,7 @@ propagation Getting an EventEmitter ----------------------- -You can get the event emitter of ``GuzzleHttp\Common\HasEmitterInterface`` +You can get the event emitter of ``GuzzleHttp\Event\HasEmitterInterface`` object using the the ``getEmitter()`` method. Here's an example of getting a client object's event emitter. @@ -95,7 +95,7 @@ event is triggered, and optionally provide a priority. }); When a listener is triggered, it is passed an event that implements the -``GuzzleHttp\Common\EventInterface`` interface, the name of the event, and the +``GuzzleHttp\Event\EventInterface`` interface, the name of the event, and the event emitter itself. The above example could more verbosely be written as follows: @@ -134,7 +134,7 @@ state. This technique is used in Guzzle extensively when intercepting error events with responses. You can stop the propagation of an event using the ``stopPropagation()`` method -of a ``GuzzleHttp\Common\EventInterface`` object: +of a ``GuzzleHttp\Event\EventInterface`` object: .. code-block:: php @@ -168,7 +168,7 @@ Event Subscribers ----------------- Event subscribers are classes that implement the -``GuzzleHttp\Common\EventSubscriberInterface`` object. They are used to register +``GuzzleHttp\Event\SubscriberInterface`` object. They are used to register one or more event listeners to methods of the class. Event subscribers tell event emitters exactly which events to listen to and what method to invoke on the class when the event is triggered by called the ``getEvents()`` method of @@ -213,6 +213,18 @@ priority of the listener (as shown in the ``before`` listener in the example). } } +To register the listeners the subscriber needs to be attached to the emitter: + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $emitter = $client->getEmitter(); + $subscriber = new SimpleSubscriber(); + $emitter->attach($subscriber); + + //to remove the listeners + $emitter->detach($subscriber); + .. note:: You can specify event priorities using integers or ``"first"`` and @@ -315,7 +327,7 @@ a ``GuzzleHttp\Event\BeforeEvent``. .. code-block:: php use GuzzleHttp\Client; - use GuzzleHttp\Common\EmitterInterface; + use GuzzleHttp\Event\EmitterInterface; use GuzzleHttp\Event\BeforeEvent; $client = new Client(['base_url' => 'http://httpbin.org']); @@ -480,7 +492,7 @@ end --- The ``end`` event is a terminal event, emitted once per request, that provides -access to the repsonse that was received or the exception that was encountered. +access to the response that was received or the exception that was encountered. The event emitted is a ``GuzzleHttp\Event\EndEvent``. This event can be intercepted, but keep in mind that the ``complete`` event diff --git a/core/vendor/guzzlehttp/guzzle/docs/faq.rst b/core/vendor/guzzlehttp/guzzle/docs/faq.rst index 8457c7edaff..a8e9ad060dc 100644 --- a/core/vendor/guzzlehttp/guzzle/docs/faq.rst +++ b/core/vendor/guzzlehttp/guzzle/docs/faq.rst @@ -64,7 +64,7 @@ an HTTP response into a more meaningful model object. - `Guzzle Command `_: Provides the building blocks for service description abstraction. - `Guzzle Services `_: Provides an - implementation of "Guzzle Command" that utlizes Guzzle's service description + implementation of "Guzzle Command" that utilizes Guzzle's service description format. Does Guzzle require cURL? @@ -91,7 +91,7 @@ Can Guzzle send asynchronous requests? Yes. Pass the ``future`` true request option to a request to send it asynchronously. Guzzle will then return a ``GuzzleHttp\Message\FutureResponse`` object that can be used synchronously by accessing the response object like a -normal response, and it can be used asynchronoulsy using a promise that is +normal response, and it can be used asynchronously using a promise that is notified when the response is resolved with a real response or rejected with an exception. diff --git a/core/vendor/guzzlehttp/guzzle/docs/http-messages.rst b/core/vendor/guzzlehttp/guzzle/docs/http-messages.rst index 4144dc4bb1c..0c6527a8d89 100644 --- a/core/vendor/guzzlehttp/guzzle/docs/http-messages.rst +++ b/core/vendor/guzzlehttp/guzzle/docs/http-messages.rst @@ -351,7 +351,7 @@ method of a request. $request = $client->createRequest('GET', '/'); $config = $request->getConfig(); -The config object is a ``GuzzleHttp\Common\Collection`` object that acts like +The config object is a ``GuzzleHttp\Collection`` object that acts like an associative array. You can grab values from the collection using array like access. You can also modify and remove values using array like access. @@ -393,7 +393,7 @@ allow customization through request configuration options. Event Emitter ------------- -Request objects implement ``GuzzleHttp\Common\HasEmitterInterface``, so they +Request objects implement ``GuzzleHttp\Event\HasEmitterInterface``, so they have a method called ``getEmitter()`` that can be used to get an event emitter used by the request. Any listener or subscriber attached to a request will only be triggered for the lifecycle events of a specific request. Conversely, adding diff --git a/core/vendor/guzzlehttp/guzzle/src/Client.php b/core/vendor/guzzlehttp/guzzle/src/Client.php index c8045a14d42..f1779eb4504 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Client.php +++ b/core/vendor/guzzlehttp/guzzle/src/Client.php @@ -177,26 +177,14 @@ class Client implements ClientInterface public function createRequest($method, $url = null, array $options = []) { - $headers = $this->mergeDefaults($options); + $options = $this->mergeDefaults($options); // Use a clone of the client's emitter $options['config']['emitter'] = clone $this->getEmitter(); + $url = $url || (is_string($url) && strlen($url)) + ? $this->buildUrl($url) + : (string) $this->baseUrl; - $request = $this->messageFactory->createRequest( - $method, - $url ? (string) $this->buildUrl($url) : (string) $this->baseUrl, - $options - ); - - // Merge in default headers - if ($headers) { - foreach ($headers as $key => $value) { - if (!$request->hasHeader($key)) { - $request->setHeader($key, $value); - } - } - } - - return $request; + return $this->messageFactory->createRequest($method, $url, $options); } public function get($url = null, $options = []) @@ -236,30 +224,31 @@ class Client implements ClientInterface public function send(RequestInterface $request) { - $trans = new Transaction($this, $request); + $isFuture = $request->getConfig()->get('future'); + $trans = new Transaction($this, $request, $isFuture); $fn = $this->fsm; - // Ensure a future response is returned if one was requested. - if ($request->getConfig()->get('future')) { - try { - $fn($trans); + try { + $fn($trans); + if ($isFuture) { // Turn the normal response into a future if needed. return $trans->response instanceof FutureInterface ? $trans->response : new FutureResponse(new FulfilledPromise($trans->response)); - } catch (RequestException $e) { - // Wrap the exception in a promise if the user asked for a future. + } + // Resolve deep futures if this is not a future + // transaction. This accounts for things like retries + // that do not have an immediate side-effect. + while ($trans->response instanceof FutureInterface) { + $trans->response = $trans->response->wait(); + } + return $trans->response; + } catch (\Exception $e) { + if ($isFuture) { + // Wrap the exception in a promise return new FutureResponse(new RejectedPromise($e)); } - } else { - try { - $fn($trans); - return $trans->response instanceof FutureInterface - ? $trans->response->wait() - : $trans->response; - } catch (\Exception $e) { - throw RequestException::wrapException($trans->request, $e); - } + throw RequestException::wrapException($trans->request, $e); } } @@ -292,9 +281,10 @@ class Client implements ClientInterface /** * Expand a URI template and inherit from the base URL if it's relative * - * @param string|array $url URL or URI template to expand - * + * @param string|array $url URL or an array of the URI template to expand + * followed by a hash of template varnames. * @return string + * @throws \InvalidArgumentException */ private function buildUrl($url) { @@ -305,6 +295,11 @@ class Client implements ClientInterface : (string) $this->baseUrl->combine($url); } + if (!isset($url[1])) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a URL array.'); + } + // Absolute URL if (strpos($url[0], '://')) { return Utils::uriTemplate($url[0], $url[1]); @@ -320,7 +315,12 @@ class Client implements ClientInterface { if (!isset($config['base_url'])) { $this->baseUrl = new Url('', ''); - } elseif (is_array($config['base_url'])) { + } elseif (!is_array($config['base_url'])) { + $this->baseUrl = Url::fromString($config['base_url']); + } elseif (count($config['base_url']) < 2) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a base_url array.'); + } else { $this->baseUrl = Url::fromString( Utils::uriTemplate( $config['base_url'][0], @@ -328,8 +328,6 @@ class Client implements ClientInterface ) ); $config['base_url'] = (string) $this->baseUrl; - } else { - $this->baseUrl = Url::fromString($config['base_url']); } } @@ -356,27 +354,42 @@ class Client implements ClientInterface } /** - * Merges default options into the array passed by reference and returns - * an array of headers that need to be merged in after the request is - * created. + * Merges default options into the array passed by reference. * * @param array $options Options to modify by reference * - * @return array|null + * @return array */ - private function mergeDefaults(&$options) + private function mergeDefaults($options) { - // Merging optimization for when no headers are present - if (!isset($options['headers']) || !isset($this->defaults['headers'])) { - $options = array_replace_recursive($this->defaults, $options); - return null; + $defaults = $this->defaults; + + // Case-insensitively merge in default headers if both defaults and + // options have headers specified. + if (!empty($defaults['headers']) && !empty($options['headers'])) { + // Create a set of lowercased keys that are present. + $lkeys = []; + foreach (array_keys($options['headers']) as $k) { + $lkeys[strtolower($k)] = true; + } + // Merge in lowercase default keys when not present in above set. + foreach ($defaults['headers'] as $key => $value) { + if (!isset($lkeys[strtolower($key)])) { + $options['headers'][$key] = $value; + } + } + // No longer need to merge in headers. + unset($defaults['headers']); } - $defaults = $this->defaults; - unset($defaults['headers']); - $options = array_replace_recursive($defaults, $options); + $result = array_replace_recursive($defaults, $options); + foreach ($options as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } - return $this->defaults['headers']; + return $result; } /** @@ -385,6 +398,6 @@ class Client implements ClientInterface */ public function sendAll($requests, array $options = []) { - (new Pool($this, $requests, $options))->wait(); + Pool::send($this, $requests, $options); } } diff --git a/core/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/core/vendor/guzzlehttp/guzzle/src/ClientInterface.php index 4f6ad26c7c0..fac88645d65 100644 --- a/core/vendor/guzzlehttp/guzzle/src/ClientInterface.php +++ b/core/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -11,7 +11,7 @@ use GuzzleHttp\Message\ResponseInterface; */ interface ClientInterface extends HasEmitterInterface { - const VERSION = '5.0.3'; + const VERSION = '5.2.0'; /** * Create and return a new {@see RequestInterface} object. diff --git a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php index 53383f4b3be..8c8fbc94a4b 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php +++ b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php @@ -41,6 +41,16 @@ abstract class AbstractRequestEvent extends AbstractEvent return $this->transaction->request; } + /** + * Get the number of transaction retries. + * + * @return int + */ + public function getRetryCount() + { + return $this->transaction->retries; + } + /** * @return Transaction */ diff --git a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php index 828d315847e..bbbdfaf8313 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php +++ b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php @@ -27,9 +27,9 @@ class AbstractRetryableEvent extends AbstractTransferEvent */ public function retry($afterDelay = 0) { - $this->transaction->response = null; - $this->transaction->exception = null; - $this->transaction->state = 'before'; + // Setting the transition state to 'retry' will cause the next state + // transition of the transaction to retry the request. + $this->transaction->state = 'retry'; if ($afterDelay) { $this->transaction->request->getConfig()->set('delay', $afterDelay); diff --git a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php index 9e4a90f59b9..3b106df0078 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php +++ b/core/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php @@ -20,11 +20,13 @@ abstract class AbstractTransferEvent extends AbstractRequestEvent */ public function getTransferInfo($name = null) { - return !$name - ? $this->transaction->transferInfo - : (isset($this->transaction->transferInfo[$name]) - ? $this->transaction->transferInfo[$name] - : null); + if (!$name) { + return $this->transaction->transferInfo; + } + + return isset($this->transaction->transferInfo[$name]) + ? $this->transaction->transferInfo[$name] + : null; } /** diff --git a/core/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php b/core/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php new file mode 100644 index 00000000000..ca42f20f309 --- /dev/null +++ b/core/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php @@ -0,0 +1,24 @@ + 5, - 'strict' => false, - 'referer' => false + 'max' => 5, + 'strict' => false, + 'referer' => false, + 'protocols' => ['http', 'https'] ]; /** @@ -198,9 +199,8 @@ class MessageFactory implements MessageFactoryInterface if ($value === true) { $value = self::$defaultRedirect; - } elseif (!isset($value['max'])) { - throw new Iae('allow_redirects must be true, false, or an ' - . 'array that contains the \'max\' key'); + } elseif (!is_array($value)) { + throw new Iae('allow_redirects must be true, false, or array'); } else { // Merge the default settings with the provided settings $value += self::$defaultRedirect; @@ -227,12 +227,8 @@ class MessageFactory implements MessageFactoryInterface if (!is_array($value)) { throw new Iae('header value must be an array'); } - - // Do not overwrite existing headers foreach ($value as $k => $v) { - if (!$request->hasHeader($k)) { - $request->setHeader($k, $v); - } + $request->setHeader($k, $v); } break; diff --git a/core/vendor/guzzlehttp/guzzle/src/Pool.php b/core/vendor/guzzlehttp/guzzle/src/Pool.php index e95ab096b8b..49d9940fe20 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Pool.php +++ b/core/vendor/guzzlehttp/guzzle/src/Pool.php @@ -1,6 +1,7 @@ wait(); + $pool = new self($client, $requests, $options); + $pool->wait(); + } + + private function getPoolSize() + { + return is_callable($this->poolSize) + ? call_user_func($this->poolSize, count($this->waitQueue)) + : $this->poolSize; + } + + /** + * Add as many requests as possible up to the current pool limit. + */ + private function addNextRequests() + { + $limit = max($this->getPoolSize() - count($this->waitQueue), 0); + while ($limit--) { + if (!$this->addNextRequest()) { + break; + } + } } public function wait() @@ -150,11 +178,7 @@ class Pool implements FutureInterface } // Seed the pool with N number of requests. - for ($i = 0; $i < $this->poolSize; $i++) { - if (!$this->addNextRequest()) { - break; - } - } + $this->addNextRequests(); // Stop if the pool was cancelled while transferring requests. if ($this->isRealized) { @@ -168,6 +192,7 @@ class Pool implements FutureInterface } catch (\Exception $e) { // Eat exceptions because they should be handled asynchronously } + $this->addNextRequests(); } // Clean up no longer needed state. @@ -241,6 +266,8 @@ class Pool implements FutureInterface */ private function addNextRequest() { + add_next: + if ($this->isRealized || !$this->iter || !$this->iter->valid()) { return false; } @@ -258,23 +285,49 @@ class Pool implements FutureInterface // Be sure to use "lazy" futures, meaning they do not send right away. $request->getConfig()->set('future', 'lazy'); - $this->attachListeners($request, $this->eventListeners); - $response = $this->client->send($request); $hash = spl_object_hash($request); + $this->attachListeners($request, $this->eventListeners); + $request->getEmitter()->on('before', [$this, '_trackRetries'], RequestEvents::EARLY); + $response = $this->client->send($request); $this->waitQueue[$hash] = $response; + $promise = $response->promise(); + + // Don't recursively call itself for completed or rejected responses. + if ($promise instanceof FulfilledPromise + || $promise instanceof RejectedPromise + ) { + try { + $this->finishResponse($request, $response->wait(), $hash); + } catch (\Exception $e) { + $this->finishResponse($request, $e, $hash); + } + goto add_next; + } // Use this function for both resolution and rejection. - $fn = function ($value) use ($request, $hash) { - unset($this->waitQueue[$hash]); - $result = $value instanceof ResponseInterface - ? ['request' => $request, 'response' => $value, 'error' => null] - : ['request' => $request, 'response' => null, 'error' => $value]; - $this->deferred->progress($result); - $this->addNextRequest(); + $thenFn = function ($value) use ($request, $hash) { + $this->finishResponse($request, $value, $hash); + if (!$request->getConfig()->get('_pool_retries')) { + $this->addNextRequests(); + } }; - $response->then($fn, $fn); + $promise->then($thenFn, $thenFn); return true; } + + public function _trackRetries(BeforeEvent $e) + { + $e->getRequest()->getConfig()->set('_pool_retries', $e->getRetryCount()); + } + + private function finishResponse($request, $value, $hash) + { + unset($this->waitQueue[$hash]); + $result = $value instanceof ResponseInterface + ? ['request' => $request, 'response' => $value, 'error' => null] + : ['request' => $request, 'response' => null, 'error' => $value]; + $this->deferred->progress($result); + } } diff --git a/core/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php b/core/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php index 81e45953577..c2ec9a62c5b 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php +++ b/core/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php @@ -1,22 +1,15 @@ [ - 'success' => 'send', - 'intercept' => 'complete', - 'error' => 'error' - ], - // The complete and error events are handled using the "then" of - // the RingPHP request, so we exit the FSM. - 'send' => ['error' => 'error'], - 'complete' => [ - 'success' => 'end', - 'intercept' => 'before', - 'error' => 'error' - ], - 'error' => [ - 'success' => 'complete', - 'intercept' => 'before', - 'error' => 'end' - ], - 'end' => [] - ]; - public function __construct( callable $handler, MessageFactoryInterface $messageFactory, @@ -59,156 +35,119 @@ class RequestFsm * optionally supplied $finalState is entered. * * @param Transaction $trans Transaction being transitioned. - * @param string $finalState The state to stop on. If unspecified, - * runs until a terminal state is found. * * @throws \Exception if a terminal state throws an exception. */ - public function __invoke(Transaction $trans, $finalState = null) + public function __invoke(Transaction $trans) { - $trans->_transitionCount = 1; + $trans->_transitionCount = 0; if (!$trans->state) { $trans->state = 'before'; } - while ($trans->state !== $finalState) { + transition: - if (!isset($this->states[$trans->state])) { - throw new StateException("Invalid state: {$trans->state}"); - } elseif (++$trans->_transitionCount > $this->maxTransitions) { - throw new StateException('Too many state transitions were ' - . 'encountered ({$trans->_transitionCount}). This likely ' - . 'means that a combination of event listeners are in an ' - . 'infinite loop.'); - } + if (++$trans->_transitionCount > $this->maxTransitions) { + throw new StateException("Too many state transitions were " + . "encountered ({$trans->_transitionCount}). This likely " + . "means that a combination of event listeners are in an " + . "infinite loop."); + } - $state = $this->states[$trans->state]; + switch ($trans->state) { + case 'before': goto before; + case 'complete': goto complete; + case 'error': goto error; + case 'retry': goto retry; + case 'send': goto send; + case 'end': goto end; + default: throw new StateException("Invalid state: {$trans->state}"); + } + before: { try { - /** @var callable $fn */ - $fn = [$this, $trans->state]; - if ($fn($trans)) { - // Handles transitioning to the "intercept" state. - if (isset($state['intercept'])) { - $trans->state = $state['intercept']; - continue; - } - throw new StateException('Invalid intercept state ' - . 'transition from ' . $trans->state); + $trans->request->getEmitter()->emit('before', new BeforeEvent($trans)); + $trans->state = 'send'; + if ((bool) $trans->response) { + $trans->state = 'complete'; } - - if (isset($state['success'])) { - // Transition to the success state - $trans->state = $state['success']; - } else { - // Break: this is a terminal state with no transition. - break; - } - - } catch (StateException $e) { - // State exceptions are thrown no matter what. - throw $e; } catch (\Exception $e) { + $trans->state = 'error'; $trans->exception = $e; - // Terminal error states throw the exception. - if (!isset($state['error'])) { - throw $e; + } + goto transition; + } + + complete: { + try { + if ($trans->response instanceof FutureInterface) { + // Futures will have their own end events emitted when + // dereferenced. + return; } - // Transition to the error state. - $trans->state = $state['error']; + $trans->state = 'end'; + $trans->response->setEffectiveUrl($trans->request->getUrl()); + $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans)); + } catch (\Exception $e) { + $trans->state = 'error'; + $trans->exception = $e; } + goto transition; } - } - private function before(Transaction $trans) - { - $trans->request->getEmitter()->emit('before', new BeforeEvent($trans)); - - // When a response is set during the before event (i.e., a mock), then - // we don't need to send anything. Skip ahead to the complete event - // by returning to to go to the intercept state. - return (bool) $trans->response; - } - - private function send(Transaction $trans) - { - $fn = $this->handler; - $trans->response = FutureResponse::proxy( - $fn(RingBridge::prepareRingRequest($trans)), - function ($value) use ($trans) { - RingBridge::completeRingResponse($trans, $value, $this->mf, $this); - return $trans->response; + error: { + try { + // Convert non-request exception to a wrapped exception + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + $trans->state = 'end'; + $trans->request->getEmitter()->emit('error', new ErrorEvent($trans)); + // An intercepted request (not retried) transitions to complete + if (!$trans->exception && $trans->state !== 'retry') { + $trans->state = 'complete'; + } + } catch (\Exception $e) { + $trans->state = 'end'; + $trans->exception = $e; } - ); - } + goto transition; + } - /** - * Emits the error event and ensures that the exception is set and is an - * instance of RequestException. If the error event is not intercepted, - * then the exception is thrown and we transition to the "end" event. This - * event also allows requests to be retried, and when retried, transitions - * to the "before" event. Otherwise, when no retries, and the exception is - * intercepted, transition to the "complete" event. - */ - private function error(Transaction $trans) - { - // Convert non-request exception to a wrapped exception - if (!($trans->exception instanceof RequestException)) { - $trans->exception = RequestException::wrapException( - $trans->request, $trans->exception + retry: { + $trans->retries++; + $trans->response = null; + $trans->exception = null; + $trans->state = 'before'; + goto transition; + } + + send: { + $fn = $this->handler; + $trans->response = FutureResponse::proxy( + $fn(RingBridge::prepareRingRequest($trans)), + function ($value) use ($trans) { + RingBridge::completeRingResponse($trans, $value, $this->mf, $this); + $this($trans); + return $trans->response; + } ); - } - - // Dispatch an event and allow interception - $event = new ErrorEvent($trans); - $trans->request->getEmitter()->emit('error', $event); - - if ($trans->exception) { - throw $trans->exception; - } - - $trans->exception = null; - - // Return true to transition to the 'before' state. False otherwise. - return $trans->state === 'before'; - } - - /** - * Emits a complete event, and if a request is marked for a retry during - * the complete event, then the "before" state is transitioned to. - */ - private function complete(Transaction $trans) - { - // Futures will have their own end events emitted when dereferenced. - if ($trans->response instanceof FutureInterface) { - return false; - } - - $trans->response->setEffectiveUrl($trans->request->getUrl()); - $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans)); - - // Return true to transition to the 'before' state. False otherwise. - return $trans->state === 'before'; - } - - /** - * Emits the "end" event and throws an exception if one is present. - */ - private function end(Transaction $trans) - { - // Futures will have their own end events emitted when dereferenced, - // but still emit, even for futures, when an exception is present. - if (!$trans->exception && $trans->response instanceof FutureInterface) { return; } - $trans->request->getEmitter()->emit('end', new EndEvent($trans)); - - // Throw exceptions in the terminal event if the exception was not - // handled by an "end" event listener. - if ($trans->exception) { - throw $trans->exception; + end: { + $trans->request->getEmitter()->emit('end', new EndEvent($trans)); + // Throw exceptions in the terminal event if the exception + // was not handled by an "end" event listener. + if ($trans->exception) { + if (!($trans->exception instanceof RequestException)) { + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + } + throw $trans->exception; + } } } } diff --git a/core/vendor/guzzlehttp/guzzle/src/RingBridge.php b/core/vendor/guzzlehttp/guzzle/src/RingBridge.php index 12e3c70132c..bc6841d42bc 100644 --- a/core/vendor/guzzlehttp/guzzle/src/RingBridge.php +++ b/core/vendor/guzzlehttp/guzzle/src/RingBridge.php @@ -72,19 +72,17 @@ class RingBridge /** * Handles the process of processing a response received from a ring - * handler. The created response is added to the transaction, and any - * necessary events are emitted based on the ring response. + * handler. The created response is added to the transaction, and the + * transaction stat is set appropriately. * * @param Transaction $trans Owns request and response. * @param array $response Ring response array * @param MessageFactoryInterface $messageFactory Creates response objects. - * @param callable $fsm Request FSM function. */ public static function completeRingResponse( Transaction $trans, array $response, - MessageFactoryInterface $messageFactory, - callable $fsm + MessageFactoryInterface $messageFactory ) { $trans->state = 'complete'; $trans->transferInfo = isset($response['transfer_stats']) @@ -116,9 +114,6 @@ class RingBridge $trans->state = 'error'; $trans->exception = $response['error']; } - - // Complete the lifecycle of the request. - $fsm($trans); } /** @@ -163,7 +158,7 @@ class RingBridge Sending the request did not return a response, exception, or populate the transaction with a response. This is most likely due to an incorrectly implemented RingPHP handler. If you are simply trying to mock responses, -then it is recommneded to use the GuzzleHttp\Ring\Client\MockHandler. +then it is recommended to use the GuzzleHttp\Ring\Client\MockHandler. EOT; return new RequestException($message, $request); } diff --git a/core/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php b/core/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php index 472f6039262..b5ed4e26092 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php +++ b/core/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php @@ -4,9 +4,9 @@ namespace GuzzleHttp\Subscriber; use GuzzleHttp\Event\BeforeEvent; use GuzzleHttp\Event\RequestEvents; use GuzzleHttp\Event\SubscriberInterface; +use GuzzleHttp\Message\AppliesHeadersInterface; use GuzzleHttp\Message\RequestInterface; use GuzzleHttp\Mimetypes; -use GuzzleHttp\Post\PostBodyInterface; use GuzzleHttp\Stream\StreamInterface; /** @@ -40,8 +40,8 @@ class Prepare implements SubscriberInterface $this->addContentLength($request, $body); - if ($body instanceof PostBodyInterface) { - // Synchronize the POST body with the request's headers + if ($body instanceof AppliesHeadersInterface) { + // Synchronize the body with the request headers $body->applyRequestHeaders($request); } elseif (!$request->hasHeader('Content-Type')) { $this->addContentType($request, $body); diff --git a/core/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php b/core/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php index 84f89d396be..ff992268bb0 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php +++ b/core/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php @@ -4,6 +4,7 @@ namespace GuzzleHttp\Subscriber; use GuzzleHttp\Event\CompleteEvent; use GuzzleHttp\Event\RequestEvents; use GuzzleHttp\Event\SubscriberInterface; +use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Exception\CouldNotRewindStreamException; use GuzzleHttp\Exception\TooManyRedirectsException; use GuzzleHttp\Message\RequestInterface; @@ -25,6 +26,9 @@ use GuzzleHttp\Url; * POST request with a GET request). * - referer: Set to true to automatically add the "Referer" header when a * redirect request is sent. + * - protocols: Array of allowed protocols. Defaults to 'http' and 'https'. + * When a redirect attempts to utilize a protocol that is not white listed, + * an exception is thrown. */ class Redirect implements SubscriberInterface { @@ -99,6 +103,7 @@ class Redirect implements SubscriberInterface ResponseInterface $response ) { $config = $request->getConfig(); + $protocols = $config->getPath('redirect/protocols') ?: ['http', 'https']; // Use a GET request if this is an entity enclosing request and we are // not forcing RFC compliance, but rather emulating what all browsers @@ -112,7 +117,7 @@ class Redirect implements SubscriberInterface } $previousUrl = $request->getUrl(); - $this->setRedirectUrl($request, $response); + $this->setRedirectUrl($request, $response, $protocols); $this->rewindEntityBody($request); // Add the Referer header if it is told to do so and only @@ -134,10 +139,12 @@ class Redirect implements SubscriberInterface * * @param RequestInterface $request * @param ResponseInterface $response + * @param array $protocols */ private function setRedirectUrl( RequestInterface $request, - ResponseInterface $response + ResponseInterface $response, + array $protocols ) { $location = $response->getHeader('Location'); $location = Url::fromString($location); @@ -151,6 +158,19 @@ class Redirect implements SubscriberInterface $location = $originalUrl->combine($location); } + // Ensure that the redirect URL is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URL, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + $request->setUrl($location); } } diff --git a/core/vendor/guzzlehttp/guzzle/src/Transaction.php b/core/vendor/guzzlehttp/guzzle/src/Transaction.php index 6a982f4c213..30a3f946916 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Transaction.php +++ b/core/vendor/guzzlehttp/guzzle/src/Transaction.php @@ -56,30 +56,48 @@ class Transaction public $transferInfo = []; /** - * The transaction's state. + * The number of transaction retries. + * + * @var int + */ + public $retries = 0; + + /** + * The transaction's current state. * * @var string */ public $state; /** - * The number of state transitions that this transactions has been through. + * Whether or not this is a future transaction. This value should not be + * changed after the future is constructed. + * + * @var bool + */ + public $future; + + /** + * The number of state transitions that this transaction has been through. * * @var int * @internal This is for internal use only. If you modify this, then you * are asking for trouble. */ - public $_transitionCount; + public $_transitionCount = 0; /** * @param ClientInterface $client Client that is used to send the requests * @param RequestInterface $request Request to send + * @param bool $future Whether or not this is a future request. */ public function __construct( ClientInterface $client, - RequestInterface $request + RequestInterface $request, + $future = false ) { $this->client = $client; $this->request = $request; + $this->_future = $future; } } diff --git a/core/vendor/guzzlehttp/guzzle/src/Url.php b/core/vendor/guzzlehttp/guzzle/src/Url.php index 95ec4333511..a81bad2f027 100644 --- a/core/vendor/guzzlehttp/guzzle/src/Url.php +++ b/core/vendor/guzzlehttp/guzzle/src/Url.php @@ -135,7 +135,7 @@ class Url $password = null, $port = null, $path = null, - Query $query = null, + $query = null, $fragment = null ) { $this->scheme = $scheme; diff --git a/core/vendor/guzzlehttp/guzzle/tests/ClientTest.php b/core/vendor/guzzlehttp/guzzle/tests/ClientTest.php index 48f17026dbe..913e535b85d 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/ClientTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/ClientTest.php @@ -7,10 +7,12 @@ use GuzzleHttp\Event\ErrorEvent; use GuzzleHttp\Message\MessageFactory; use GuzzleHttp\Message\Response; use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Query; use GuzzleHttp\Ring\Client\MockHandler; use GuzzleHttp\Ring\Future\FutureArray; use GuzzleHttp\Subscriber\History; use GuzzleHttp\Subscriber\Mock; +use GuzzleHttp\Url; use React\Promise\Deferred; /** @@ -70,6 +72,14 @@ class ClientTest extends \PHPUnit_Framework_TestCase $this->assertEquals('http://foo.com/baz/', $client->getBaseUrl()); } + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesUriTemplateValue() + { + new Client(['base_url' => ['http://foo.com/']]); + } + /** * @expectedException \Exception * @expectedExceptionMessage Foo @@ -243,6 +253,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase $this->assertEquals('custom', $request->getHeader('Foo')); } + public function testCanOverrideDefaultOptionWithNull() + { + $client = new Client(['defaults' => ['proxy' => 'invalid!']]); + $request = $client->createRequest('GET', 'http://foo.com?a=b', [ + 'proxy' => null + ]); + $this->assertFalse($request->getConfig()->hasKey('proxy')); + } + public function testDoesNotOverwriteExistingUA() { $client = new Client(['defaults' => [ @@ -272,6 +291,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase ); } + public function testFalsyPathsAreCombinedWithBaseUrl() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/0', + $client->createRequest('GET', '0')->getUrl() + ); + } + public function testUsesBaseUrlCombinedWithProvidedUrlViaUriTemplate() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); @@ -582,4 +610,15 @@ class ClientTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res); $this->assertEquals(200, $res->getStatusCode()); } + + public function testCanUseUrlWithCustomQuery() + { + $client = new Client(); + $url = Url::fromString('http://foo.com/bar'); + $query = new Query(['baz' => '123%20']); + $query->setEncodingType(false); + $url->setQuery($query); + $r = $client->createRequest('GET', $url); + $this->assertEquals('http://foo.com/bar?baz=123%20', $r->getUrl()); + } } diff --git a/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php b/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php index dcc874c66d4..6a39d8bb096 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php @@ -19,7 +19,7 @@ class AbstractRetryableEventTest extends \PHPUnit_Framework_TestCase ->getMockForAbstractClass(); $e->retry(); $this->assertTrue($e->isPropagationStopped()); - $this->assertEquals('before', $t->state); + $this->assertEquals('retry', $t->state); } public function testCanRetryAfterDelay() @@ -31,7 +31,7 @@ class AbstractRetryableEventTest extends \PHPUnit_Framework_TestCase ->getMockForAbstractClass(); $e->retry(10); $this->assertTrue($e->isPropagationStopped()); - $this->assertEquals('before', $t->state); + $this->assertEquals('retry', $t->state); $this->assertEquals(10, $t->request->getConfig()->get('delay')); } } diff --git a/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php b/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php index ca3b5ba9a08..5313c8e7f36 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php @@ -46,4 +46,14 @@ class AbstractTransferEventTest extends \PHPUnit_Framework_TestCase $this->assertSame($t->response, $e->getResponse()); $this->assertTrue($e->isPropagationStopped()); } + + public function testReturnsNumberOfRetries() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $t->retries = 2; + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $this->assertEquals(2, $e->getRetryCount()); + } } diff --git a/core/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php b/core/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php index 0e91ff9a774..e26c64d9f8f 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php @@ -3,7 +3,10 @@ namespace GuzzleHttp\Tests; use GuzzleHttp\Client; use GuzzleHttp\Event\AbstractTransferEvent; +use GuzzleHttp\Event\CompleteEvent; use GuzzleHttp\Event\EndEvent; +use GuzzleHttp\Event\ErrorEvent; +use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Message\Response; use GuzzleHttp\Pool; @@ -70,4 +73,51 @@ class IntegrationTest extends \PHPUnit_Framework_TestCase $this->assertNotEmpty($transfer); $this->assertArrayHasKey('url', $transfer); } + + public function testNestedFutureResponsesAreResolvedWhenSending() + { + $c = new Client(); + $total = 3; + Server::enqueue([ + new Response(200), + new Response(201), + new Response(202) + ]); + $c->getEmitter()->on( + 'complete', + function (CompleteEvent $e) use (&$total) { + if (--$total) { + $e->retry(); + } + } + ); + $response = $c->get(Server::$url); + $this->assertEquals(202, $response->getStatusCode()); + $this->assertEquals('GuzzleHttp\Message\Response', get_class($response)); + } + + public function testNestedFutureErrorsAreResolvedWhenSending() + { + $c = new Client(); + $total = 3; + Server::enqueue([ + new Response(500), + new Response(501), + new Response(502) + ]); + $c->getEmitter()->on( + 'error', + function (ErrorEvent $e) use (&$total) { + if (--$total) { + $e->retry(); + } + } + ); + try { + $c->get(Server::$url); + $this->fail('Did not throw!'); + } catch (RequestException $e) { + $this->assertEquals(502, $e->getResponse()->getStatusCode()); + } + } } diff --git a/core/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php b/core/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php index 3e1fd3dc9ca..aa2e45e0217 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php @@ -144,7 +144,7 @@ class MessageFactoryTest extends \PHPUnit_Framework_TestCase */ public function testValidatesRedirects() { - (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => []]); + (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => 'foo']); } public function testCanEnableStrictRedirectsAndSpecifyMax() diff --git a/core/vendor/guzzlehttp/guzzle/tests/PoolTest.php b/core/vendor/guzzlehttp/guzzle/tests/PoolTest.php index 788a0f87557..b5f02add8e1 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/PoolTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/PoolTest.php @@ -154,6 +154,64 @@ class PoolTest extends \PHPUnit_Framework_TestCase $this->assertSame($responses[3], $result[2]->getResponse()); } + public function testBatchesRequestsWithDynamicPoolSize() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $responses = [ + new Response(301, ['Location' => 'http://foo.com/bar']), + new Response(200), + new Response(200), + new Response(404) + ]; + + $client->getEmitter()->attach(new Mock($responses)); + $requests = [ + $client->createRequest('GET', 'http://foo.com/baz'), + $client->createRequest('HEAD', 'http://httpbin.org/get'), + $client->createRequest('PUT', 'http://httpbin.org/put'), + ]; + + $a = $b = $c = $d = 0; + $result = Pool::batch($client, $requests, [ + 'before' => function (BeforeEvent $e) use (&$a) { $a++; }, + 'complete' => function (CompleteEvent $e) use (&$b) { $b++; }, + 'error' => function (ErrorEvent $e) use (&$c) { $c++; }, + 'end' => function (EndEvent $e) use (&$d) { $d++; }, + 'pool_size' => function ($queueSize) { + static $options = [1, 2, 1]; + static $queued = 0; + + $this->assertEquals( + $queued, + $queueSize, + 'The number of queued requests should be equal to the sum of pool sizes so far.' + ); + + $next = array_shift($options); + $queued += $next; + + return $next; + } + ]); + + $this->assertEquals(4, $a); + $this->assertEquals(2, $b); + $this->assertEquals(1, $c); + $this->assertEquals(3, $d); + $this->assertCount(3, $result); + $this->assertInstanceOf('GuzzleHttp\BatchResults', $result); + + // The first result is actually the second (redirect) response. + $this->assertSame($responses[1], $result[0]); + // The second result is a 1:1 request:response map + $this->assertSame($responses[2], $result[1]); + // The third entry is the 404 RequestException + $this->assertSame($responses[3], $result[2]->getResponse()); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Each event listener must be a callable or @@ -228,4 +286,34 @@ class PoolTest extends \PHPUnit_Framework_TestCase Pool::send($client, $requests); $this->assertCount(1, $history); } + + public function testDoesNotInfinitelyRecurse() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $last = null; + $client->getEmitter()->on( + 'before', + function (BeforeEvent $e) use (&$last) { + $e->intercept(new Response(200)); + if (function_exists('xdebug_get_stack_depth')) { + if ($last) { + $this->assertEquals($last, xdebug_get_stack_depth()); + } else { + $last = xdebug_get_stack_depth(); + } + } + } + ); + + $requests = []; + for ($i = 0; $i < 100; $i++) { + $requests[] = $client->createRequest('GET', 'http://foo.com'); + } + + $pool = new Pool($client, $requests); + $pool->wait(); + } } diff --git a/core/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php b/core/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php index 308f5e056db..0283a5ebf20 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php @@ -130,7 +130,6 @@ class PostBodyTest extends \PHPUnit_Framework_TestCase $b->seek(0); $this->assertEquals('foo=bar&baz=123', $b->read(1000)); $this->assertEquals(15, $b->tell()); - $this->assertTrue($b->eof()); } public function testCanSpecifyQueryAggregator() diff --git a/core/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php b/core/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php index 86dc270318a..dd676840516 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php @@ -5,6 +5,7 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Message\MessageFactory; use GuzzleHttp\Message\Response; use GuzzleHttp\RequestFsm; +use GuzzleHttp\Ring\Future\CompletedFutureArray; use GuzzleHttp\Subscriber\Mock; use GuzzleHttp\Transaction; use GuzzleHttp\Client; @@ -29,19 +30,23 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase public function testEmitsBeforeEventInTransition() { - $fsm = new RequestFsm(function () {}, $this->mf); + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $c = false; $t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) { $c = true; }); - $fsm($t, 'send'); + $fsm($t); $this->assertTrue($c); } public function testEmitsCompleteEventInTransition() { - $fsm = new RequestFsm(function () {}, $this->mf); + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $t->response = new Response(200); $t->state = 'complete'; @@ -49,13 +54,15 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { $c = true; }); - $fsm($t, 'end'); + $fsm($t); $this->assertTrue($c); } public function testDoesNotEmitCompleteForFuture() { - $fsm = new RequestFsm(function () {}, $this->mf); + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $deferred = new Deferred(); $t->response = new FutureResponse($deferred->promise()); @@ -64,21 +71,6 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { $c = true; }); - $fsm($t, 'end'); - $this->assertFalse($c); - } - - public function testDoesNotEmitEndForFuture() - { - $fsm = new RequestFsm(function () {}, $this->mf); - $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); - $deferred = new Deferred(); - $t->response = new FutureResponse($deferred->promise()); - $t->state = 'end'; - $c = false; - $t->request->getEmitter()->on('end', function (EndEvent $e) use (&$c) { - $c = true; - }); $fsm($t); $this->assertFalse($c); } @@ -95,7 +87,9 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase public function testTransitionsThroughErrorsInBefore() { - $fsm = new RequestFsm(function () {}, $this->mf); + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); $client = new Client(); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $t = new Transaction($client, $request); @@ -105,7 +99,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase throw new \Exception('foo'); }); try { - $fsm($t, 'send'); + $fsm($t); $this->fail('did not throw'); } catch (RequestException $e) { $this->assertContains('foo', $t->exception->getMessage()); @@ -133,7 +127,9 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase public function testTransitionsThroughErrorInterception() { - $fsm = new RequestFsm(function () {}, $this->mf); + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 404]); + }, $this->mf); $client = new Client(); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $t = new Transaction($client, $request); @@ -142,9 +138,6 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase $t->request->getEmitter()->on('error', function (ErrorEvent $e) { $e->intercept(new Response(200)); }); - $fsm($t, 'send'); - $t->response = new Response(404); - $t->state = 'complete'; $fsm($t); $this->assertEquals(200, $t->response->getStatusCode()); $this->assertNull($t->exception); @@ -175,10 +168,12 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase { $client = new Client([ 'fsm' => $fsm = new RequestFsm( - function () {}, - new MessageFactory(), - 3 - ) + function () { + return new CompletedFutureArray(['status' => 200]); + }, + new MessageFactory(), + 3 + ) ]); $request = $client->createRequest('GET', 'http://foo.com:123'); $request->getEmitter()->on('before', function () { diff --git a/core/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php b/core/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php index 19af0777465..dc26a42aa66 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php @@ -107,24 +107,6 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foo', (string) $response->getBody()); } - public function testEmitsCompleteEventOnSuccess() - { - $c = false; - $trans = new Transaction(new Client(), new Request('GET', 'http://f.co')); - $trans->request->getEmitter()->on('complete', function () use (&$c) { - $c = true; - }); - $f = new MessageFactory(); - $res = ['status' => 200, 'headers' => []]; - $fsm = new RequestFsm(function () {}, new MessageFactory()); - RingBridge::completeRingResponse($trans, $res, $f, $fsm); - $this->assertInstanceOf( - 'GuzzleHttp\Message\ResponseInterface', - $trans->response - ); - $this->assertTrue($c); - } - public function testEmitsErrorEventOnError() { $client = new Client(['base_url' => 'http://127.0.0.1:123']); diff --git a/core/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php b/core/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php index 168b605b26d..293cfc217aa 100644 --- a/core/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php +++ b/core/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php @@ -266,4 +266,23 @@ class RedirectTest extends \PHPUnit_Framework_TestCase $response->getEffectiveUrl() ); } + + /** + * @expectedException \GuzzleHttp\Exception\BadResponseException + * @expectedExceptionMessage Redirect URL, https://foo.com/redirect2, does not use one of the allowed redirect protocols: http + */ + public function testThrowsWhenRedirectingToInvalidUrlProtocol() + { + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: https://foo.com/redirect2\r\nContent-Length: 0\r\n\r\n" + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->get('http://www.example.com/foo', [ + 'allow_redirects' => [ + 'protocols' => ['http'] + ] + ]); + } } diff --git a/core/vendor/guzzlehttp/ringphp/.travis.yml b/core/vendor/guzzlehttp/ringphp/.travis.yml index 0befb178ecc..e43fbdd9cda 100644 --- a/core/vendor/guzzlehttp/ringphp/.travis.yml +++ b/core/vendor/guzzlehttp/ringphp/.travis.yml @@ -4,6 +4,7 @@ php: - 5.4 - 5.5 - 5.6 + - 7.0 - hhvm before_script: diff --git a/core/vendor/guzzlehttp/ringphp/CHANGELOG.md b/core/vendor/guzzlehttp/ringphp/CHANGELOG.md index ee3ce796e3a..97458da57d2 100644 --- a/core/vendor/guzzlehttp/ringphp/CHANGELOG.md +++ b/core/vendor/guzzlehttp/ringphp/CHANGELOG.md @@ -1,5 +1,27 @@ # CHANGELOG +## 1.0.7 - 2015-03-29 + +* PHP 7 fixes. + +## 1.0.6 - 2015-02-26 + +* Bug fix: futures now extend from React's PromiseInterface to ensure that they + are properly forwarded down the promise chain. +* The multi handle of the CurlMultiHandler is now created lazily. + +## 1.0.5 - 2014-12-10 + +* Adding more error information to PHP stream wrapper exceptions. +* Added digest auth integration test support to test server. + +## 1.0.4 - 2014-12-01 + +* Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS. +* Setting debug to `false` does not enable debug output. +* Added a fix to the StreamHandler to return a `FutureArrayInterface` when an + error occurs. + ## 1.0.3 - 2014-11-03 * Setting the `header` stream option as a string to be compatible with GAE. diff --git a/core/vendor/guzzlehttp/ringphp/composer.json b/core/vendor/guzzlehttp/ringphp/composer.json index 2d8849f5aa8..fcca6298e1f 100644 --- a/core/vendor/guzzlehttp/ringphp/composer.json +++ b/core/vendor/guzzlehttp/ringphp/composer.json @@ -1,5 +1,6 @@ { "name": "guzzlehttp/ringphp", + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", "license": "MIT", "authors": [ { diff --git a/core/vendor/guzzlehttp/ringphp/docs/index.rst b/core/vendor/guzzlehttp/ringphp/docs/index.rst index 3ac54479636..4bbce631c70 100644 --- a/core/vendor/guzzlehttp/ringphp/docs/index.rst +++ b/core/vendor/guzzlehttp/ringphp/docs/index.rst @@ -15,11 +15,11 @@ without tying your application to a specific implementation. .. toctree:: :maxdepth: 2 - spec - futures - client_middleware - client_handlers - testing + spec + futures + client_middleware + client_handlers + testing .. code-block:: php diff --git a/core/vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php b/core/vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php index f535a3db131..0377ec258e6 100644 --- a/core/vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php +++ b/core/vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php @@ -406,12 +406,20 @@ class CurlFactory case 'timeout': - $options[CURLOPT_TIMEOUT_MS] = $value * 1000; + if (defined('CURLOPT_TIMEOUT_MS')) { + $options[CURLOPT_TIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_TIMEOUT] = $value; + } break; case 'connect_timeout': - $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; + if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_CONNECTTIMEOUT] = $value; + } break; case 'proxy': diff --git a/core/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php b/core/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php index d0323d88a9c..d8215c5cf4c 100644 --- a/core/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php +++ b/core/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php @@ -71,6 +71,7 @@ class CurlHandler $response = ['transfer_stats' => curl_getinfo($h)]; $response['curl']['error'] = curl_error($h); $response['curl']['errno'] = curl_errno($h); + $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']); $this->releaseEasyHandle($h); return new CompletedFutureArray( diff --git a/core/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php b/core/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php index 8335aac1d21..b45f6c3979a 100644 --- a/core/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php +++ b/core/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php @@ -13,13 +13,14 @@ use React\Promise\Deferred; * 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 "client" key of the request. + * + * @property resource $_mh Internal use only. Lazy loaded multi-handle. */ class CurlMultiHandler { /** @var callable */ private $factory; private $selectTimeout; - private $mh; private $active; private $handles = []; private $delays = []; @@ -42,8 +43,9 @@ class CurlMultiHandler */ public function __construct(array $options = []) { - $this->mh = isset($options['mh']) - ? $options['mh'] : curl_multi_init(); + if (isset($options['mh'])) { + $this->_mh = $options['mh']; + } $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(); $this->selectTimeout = isset($options['select_timeout']) @@ -52,6 +54,15 @@ class CurlMultiHandler ? $options['max_handles'] : 100; } + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + public function __destruct() { // Finish any open connections before terminating the script. @@ -59,9 +70,9 @@ class CurlMultiHandler $this->execute(); } - if ($this->mh) { - curl_multi_close($this->mh); - $this->mh = null; + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); } } @@ -106,7 +117,7 @@ class CurlMultiHandler do { if ($this->active && - curl_multi_select($this->mh, $this->selectTimeout) === -1 + 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 @@ -119,7 +130,7 @@ class CurlMultiHandler } do { - $mrc = curl_multi_exec($this->mh, $this->active); + $mrc = curl_multi_exec($this->_mh, $this->active); } while ($mrc === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); @@ -142,13 +153,13 @@ class CurlMultiHandler if (isset($entry['request']['client']['delay'])) { $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000); } elseif (empty($entry['request']['future'])) { - curl_multi_add_handle($this->mh, $entry['handle']); + curl_multi_add_handle($this->_mh, $entry['handle']); } else { - curl_multi_add_handle($this->mh, $entry['handle']); + curl_multi_add_handle($this->_mh, $entry['handle']); // "lazy" futures are only sent once the pool has many requests. if ($entry['request']['future'] !== 'lazy') { do { - $mrc = curl_multi_exec($this->mh, $this->active); + $mrc = curl_multi_exec($this->_mh, $this->active); } while ($mrc === CURLM_CALL_MULTI_PERFORM); $this->processMessages(); } @@ -159,7 +170,7 @@ class CurlMultiHandler { if (isset($this->handles[$id])) { curl_multi_remove_handle( - $this->mh, + $this->_mh, $this->handles[$id]['handle'] ); curl_close($this->handles[$id]['handle']); @@ -183,7 +194,7 @@ class CurlMultiHandler $handle = $this->handles[$id]['handle']; unset($this->delays[$id], $this->handles[$id]); - curl_multi_remove_handle($this->mh, $handle); + curl_multi_remove_handle($this->_mh, $handle); curl_close($handle); return true; @@ -197,7 +208,7 @@ class CurlMultiHandler if ($currentTime >= $delay) { unset($this->delays[$id]); curl_multi_add_handle( - $this->mh, + $this->_mh, $this->handles[$id]['handle'] ); } @@ -206,7 +217,7 @@ class CurlMultiHandler private function processMessages() { - while ($done = curl_multi_info_read($this->mh)) { + while ($done = curl_multi_info_read($this->_mh)) { $id = (int) $done['handle']; if (!isset($this->handles[$id])) { diff --git a/core/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php b/core/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php index 3a18ec6b89d..855aad7f647 100644 --- a/core/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php +++ b/core/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php @@ -16,6 +16,7 @@ use GuzzleHttp\Stream\Utils; class StreamHandler { private $options; + private $lastHeaders; public function __construct(array $options = []) { @@ -30,15 +31,17 @@ class StreamHandler try { // Does not support the expect header. $request = Core::removeHeader($request, 'Expect'); - $stream = $this->createStream($url, $request, $headers); - return $this->createResponse($request, $url, $headers, $stream); + $stream = $this->createStream($url, $request); + return $this->createResponse($request, $url, $stream); } catch (RingException $e) { return $this->createErrorResponse($url, $e); } } - private function createResponse(array $request, $url, array $hdrs, $stream) + private function createResponse(array $request, $url, $stream) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = null; $parts = explode(' ', array_shift($hdrs), 3); $response = [ 'status' => $parts[1], @@ -131,13 +134,13 @@ class StreamHandler $e = new ConnectException($e->getMessage(), 0, $e); } - return [ + return new CompletedFutureArray([ 'status' => null, 'body' => null, 'headers' => [], 'effective_url' => $url, 'error' => $e - ]; + ]); } /** @@ -150,17 +153,25 @@ class StreamHandler */ private function createResource(callable $callback) { - // Turn off error reporting while we try to initiate the request - $level = error_reporting(0); - $resource = call_user_func($callback); - error_reporting($level); + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); - // If the resource could not be created, then grab the last error and - // throw an exception. - if (!is_resource($resource)) { + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { $message = 'Error creating resource: '; - foreach ((array) error_get_last() as $key => $value) { - $message .= "[{$key}] {$value} "; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } } throw new RingException(trim($message)); } @@ -168,11 +179,8 @@ class StreamHandler return $resource; } - private function createStream( - $url, - array $request, - &$http_response_header - ) { + private function createStream($url, array $request) + { static $methods; if (!$methods) { $methods = array_flip(get_class_methods(__CLASS__)); @@ -207,8 +215,7 @@ class StreamHandler $url, $request, $options, - $this->createContext($request, $options, $params), - $http_response_header + $this->createContext($request, $options, $params) ); } @@ -302,7 +309,7 @@ class StreamHandler private function add_progress(array $request, &$options, $value, &$params) { - $fn = function ($code, $_, $_, $_, $transferred, $total) use ($value) { + $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) { if ($code == STREAM_NOTIFY_PROGRESS) { $value($total, $transferred, null, null); } @@ -316,6 +323,10 @@ class StreamHandler private function add_debug(array $request, &$options, $value, &$params) { + if ($value === false) { + return; + } + static $map = [ STREAM_NOTIFY_CONNECT => 'CONNECT', STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', @@ -382,16 +393,17 @@ class StreamHandler $url, array $request, array $options, - $context, - &$http_response_header + $context ) { return $this->createResource( - function () use ($url, &$http_response_header, $context) { + function () use ($url, $context) { if (false === strpos($url, 'http')) { trigger_error("URL is invalid: {$url}", E_USER_WARNING); return null; } - return fopen($url, 'r', null, $context); + $resource = fopen($url, 'r', null, $context); + $this->lastHeaders = $http_response_header; + return $resource; }, $request, $options diff --git a/core/vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php b/core/vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php index 902e7768e08..43d18117b16 100644 --- a/core/vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php +++ b/core/vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php @@ -16,7 +16,7 @@ use React\Promise\PromisorInterface; * computation has not yet completed when wait() is called, the call to wait() * will block until the future has completed. */ -interface FutureInterface extends PromisorInterface +interface FutureInterface extends PromiseInterface, PromisorInterface { /** * Returns the result of the future either from cache or by blocking until @@ -37,20 +37,4 @@ interface FutureInterface extends PromisorInterface * Cancels the future, if possible. */ public function cancel(); - - /** - * Create and return a promise that invokes the given methods when the - * future has a value, exception, or progress events. - * - * @param callable $onFulfilled Called when the promise is resolved. - * @param callable $onRejected Called when the promise is rejected. - * @param callable $onProgress Called on progress events. - * - * @return PromiseInterface - */ - public function then( - callable $onFulfilled = null, - callable $onRejected = null, - callable $onProgress = null - ); } diff --git a/core/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php b/core/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php index 8f63e29558d..7549a83f641 100644 --- a/core/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php +++ b/core/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php @@ -41,7 +41,10 @@ class StreamHandlerTest extends \PHPUnit_Framework_TestCase 'headers' => ['host' => ['localhost:123']], 'client' => ['timeout' => 0.01], ]); - + $this->assertInstanceOf( + 'GuzzleHttp\Ring\Future\CompletedFutureArray', + $result + ); $this->assertNull($result['status']); $this->assertNull($result['body']); $this->assertEquals([], $result['headers']); diff --git a/core/vendor/guzzlehttp/ringphp/tests/Client/server.js b/core/vendor/guzzlehttp/ringphp/tests/Client/server.js index 5642c0fca40..6a03e33ab22 100644 --- a/core/vendor/guzzlehttp/ringphp/tests/Client/server.js +++ b/core/vendor/guzzlehttp/ringphp/tests/Client/server.js @@ -21,6 +21,15 @@ * < * < [{'http_method': 'GET', 'uri': '/', 'headers': {}, 'body': 'string'}] * + * - Attempt access to the secure area + * > GET /secure/by-digest/qop-auth/guzzle-server/requests + * > Host: 127.0.0.1:8125 + * + * < HTTP/1.1 401 Unauthorized + * < WWW-Authenticate: Digest realm="Digest Test", qop="auth", nonce="0796e98e1aeef43141fab2a66bf4521a", algorithm="MD5", stale="false" + * < + * < 401 Unauthorized + * * - Shutdown the server * > DELETE /guzzle-server * > Host: 127.0.0.1:8125 @@ -44,6 +53,77 @@ var GuzzleServer = function(port, log) { this.requests = []; var that = this; + var md5 = function(input) { + var crypto = require('crypto'); + var hasher = crypto.createHash('md5'); + hasher.update(input); + return hasher.digest('hex'); + } + + /** + * Node.js HTTP server authentication module. + * + * It is only initialized on demand (by loadAuthentifier). This avoids + * requiring the dependency to http-auth on standard operations, and the + * performance hit at startup. + */ + var auth; + + /** + * Provides authentication handlers (Basic, Digest). + */ + var loadAuthentifier = function(type, options) { + var typeId = type; + if (type == 'digest') { + typeId += '.'+(options && options.qop ? options.qop : 'none'); + } + if (!loadAuthentifier[typeId]) { + if (!auth) { + try { + auth = require('http-auth'); + } catch (e) { + if (e.code == 'MODULE_NOT_FOUND') { + return; + } + } + } + switch (type) { + case 'digest': + var digestParams = { + realm: 'Digest Test', + login: 'me', + password: 'test' + }; + if (options && options.qop) { + digestParams.qop = options.qop; + } + loadAuthentifier[typeId] = auth.digest(digestParams, function(username, callback) { + callback(md5(digestParams.login + ':' + digestParams.realm + ':' + digestParams.password)); + }); + break + } + } + return loadAuthentifier[typeId]; + }; + + var firewallRequest = function(request, req, res, requestHandlerCallback) { + var securedAreaUriParts = request.uri.match(/^\/secure\/by-(digest)(\/qop-([^\/]*))?(\/.*)$/); + if (securedAreaUriParts) { + var authentifier = loadAuthentifier(securedAreaUriParts[1], { qop: securedAreaUriParts[2] }); + if (!authentifier) { + res.writeHead(501, 'HTTP authentication not implemented', { 'Content-Length': 0 }); + res.end(); + return; + } + authentifier.check(req, res, function(req, res) { + req.url = securedAreaUriParts[4]; + requestHandlerCallback(request, req, res); + }); + } else { + requestHandlerCallback(request, req, res); + } + }; + var controlRequest = function(request, req, res) { if (req.url == '/guzzle-server/perf') { res.writeHead(200, 'OK', {'Content-Length': 16}); @@ -140,7 +220,7 @@ var GuzzleServer = function(port, log) { // Called when the request completes req.addListener('end', function() { - receivedRequest(request, req, res); + firewallRequest(request, req, res, receivedRequest); }); });