diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2020-02-06 20:49:21 +0300 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2020-02-06 20:49:21 +0300 |
commit | d583bf2a4b2db903846bfbf1600f69f3b4b60702 (patch) | |
tree | ebb17468edebe72b58a4b123cee7dc4ef3cc9372 /guzzlehttp | |
parent | 052a1b2481dbf65b6ede66f1d35b0da54f92b3c6 (diff) |
Vendor #364 and #396 files
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'guzzlehttp')
37 files changed, 547 insertions, 171 deletions
diff --git a/guzzlehttp/guzzle/src/Client.php b/guzzlehttp/guzzle/src/Client.php index 80417918..f2bf2044 100644 --- a/guzzlehttp/guzzle/src/Client.php +++ b/guzzlehttp/guzzle/src/Client.php @@ -2,11 +2,12 @@ namespace GuzzleHttp; use GuzzleHttp\Cookie\CookieJar; +use GuzzleHttp\Exception\InvalidArgumentException; use GuzzleHttp\Promise; use GuzzleHttp\Psr7; -use Psr\Http\Message\UriInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; /** * @method ResponseInterface get(string|UriInterface $uri, array $options = []) @@ -75,6 +76,12 @@ class Client implements ClientInterface $this->configureDefaults($config); } + /** + * @param string $method + * @param array $args + * + * @return Promise\PromiseInterface + */ public function __call($method, $args) { if (count($args) < 1) { @@ -89,6 +96,14 @@ class Client implements ClientInterface : $this->request($method, $uri, $opts); } + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ public function sendAsync(RequestInterface $request, array $options = []) { // Merge the base URI into the request URI if needed. @@ -100,12 +115,35 @@ class Client implements ClientInterface ); } + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ public function send(RequestInterface $request, array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->sendAsync($request, $options)->wait(); } + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ public function requestAsync($method, $uri = '', array $options = []) { $options = $this->prepareDefaults($options); @@ -125,12 +163,37 @@ class Client implements ClientInterface return $this->transfer($request, $options); } + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ public function request($method, $uri = '', array $options = []) { $options[RequestOptions::SYNCHRONOUS] = true; return $this->requestAsync($method, $uri, $options)->wait(); } + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + */ public function getConfig($option = null) { return $option === null @@ -138,6 +201,11 @@ class Client implements ClientInterface : (isset($this->config[$option]) ? $this->config[$option] : null); } + /** + * @param string|null $uri + * + * @return UriInterface + */ private function buildUri($uri, array $config) { // for BC we accept null which would otherwise fail in uri_for @@ -147,6 +215,11 @@ class Client implements ClientInterface $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); } + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion']; + $uri = _idn_uri_convert($uri, $idnOptions); + } + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; } @@ -154,6 +227,7 @@ class Client implements ClientInterface * Configures the default options for a client. * * @param array $config + * @return void */ private function configureDefaults(array $config) { @@ -165,12 +239,22 @@ class Client implements ClientInterface 'cookies' => false ]; + // idn_to_ascii() is a part of ext-intl and might be not available + $defaults['idn_conversion'] = function_exists('idn_to_ascii') + // Old ICU versions don't have this constant, so we are basically stuck (see https://github.com/guzzle/guzzle/pull/2424 + // and https://github.com/guzzle/guzzle/issues/2448 for details) + && ( + defined('INTL_IDNA_VARIANT_UTS46') + || + PHP_VERSION_ID < 70200 + ); + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. // We can only trust the HTTP_PROXY environment variable in a CLI // process due to the fact that PHP has no reliable mechanism to // get environment variables that start with "HTTP_". - if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { + if (php_sapi_name() === 'cli' && getenv('HTTP_PROXY')) { $defaults['proxy']['http'] = getenv('HTTP_PROXY'); } @@ -210,7 +294,7 @@ class Client implements ClientInterface * * @return array */ - private function prepareDefaults($options) + private function prepareDefaults(array $options) { $defaults = $this->config; @@ -225,7 +309,7 @@ class Client implements ClientInterface if (array_key_exists('headers', $options)) { // Allows default headers to be unset. if ($options['headers'] === null) { - $defaults['_conditional'] = null; + $defaults['_conditional'] = []; unset($options['headers']); } elseif (!is_array($options['headers'])) { throw new \InvalidArgumentException('headers must be an array'); @@ -251,8 +335,7 @@ class Client implements ClientInterface * The URI of the request is not modified and the request options are used * as-is without merging in default options. * - * @param RequestInterface $request - * @param array $options + * @param array $options See \GuzzleHttp\RequestOptions. * * @return Promise\PromiseInterface */ @@ -271,6 +354,7 @@ class Client implements ClientInterface } $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ $handler = $options['handler']; try { @@ -411,6 +495,11 @@ class Client implements ClientInterface return $request; } + /** + * Throw Exception with pre-set message. + * @return void + * @throws InvalidArgumentException Invalid body. + */ private function invalidBody() { throw new \InvalidArgumentException('Passing in the "body" request ' diff --git a/guzzlehttp/guzzle/src/ClientInterface.php b/guzzlehttp/guzzle/src/ClientInterface.php index 2dbcffa4..0c8d42a1 100644 --- a/guzzlehttp/guzzle/src/ClientInterface.php +++ b/guzzlehttp/guzzle/src/ClientInterface.php @@ -1,8 +1,8 @@ <?php namespace GuzzleHttp; -use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; @@ -12,7 +12,10 @@ use Psr\Http\Message\UriInterface; */ interface ClientInterface { - const VERSION = '6.3.3'; + /** + * @deprecated Will be removed in Guzzle 7.0.0 + */ + const VERSION = '6.5.1'; /** * Send an HTTP request. diff --git a/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/guzzlehttp/guzzle/src/Cookie/CookieJar.php index 78f2b79f..38f98ad7 100644 --- a/guzzlehttp/guzzle/src/Cookie/CookieJar.php +++ b/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -94,8 +94,8 @@ class CookieJar implements CookieJarInterface */ public function getCookieByName($name) { - // don't allow a null name - if ($name === null) { + // don't allow a non string name + if ($name === null || !is_scalar($name)) { return null; } foreach ($this->cookies as $cookie) { @@ -103,6 +103,8 @@ class CookieJar implements CookieJarInterface return $cookie; } } + + return null; } public function toArray() @@ -120,7 +122,7 @@ class CookieJar implements CookieJarInterface } elseif (!$path) { $this->cookies = array_filter( $this->cookies, - function (SetCookie $cookie) use ($path, $domain) { + function (SetCookie $cookie) use ($domain) { return !$cookie->matchesDomain($domain); } ); diff --git a/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php index 2cf298a8..6ee11885 100644 --- a/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php +++ b/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -58,9 +58,9 @@ interface CookieJarInterface extends \Countable, \IteratorAggregate * arguments, then the cookie with the specified name, path and domain is * removed. * - * @param string $domain Clears cookies matching a domain - * @param string $path Clears cookies matching a domain and path - * @param string $name Clears cookies matching a domain, path, and name + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name * * @return CookieJarInterface */ diff --git a/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php index 9887c1d5..3fb8600e 100644 --- a/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php +++ b/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -23,6 +23,7 @@ class FileCookieJar extends CookieJar */ public function __construct($cookieFile, $storeSessionCookies = false) { + parent::__construct(); $this->filename = $cookieFile; $this->storeSessionCookies = $storeSessionCookies; @@ -56,7 +57,7 @@ class FileCookieJar extends CookieJar } $jsonStr = \GuzzleHttp\json_encode($json); - if (false === file_put_contents($filename, $jsonStr)) { + if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) { throw new \RuntimeException("Unable to save file {$filename}"); } } diff --git a/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php index 4497bcf0..0224a244 100644 --- a/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php +++ b/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -22,6 +22,7 @@ class SessionCookieJar extends CookieJar */ public function __construct($sessionKey, $storeSessionCookies = false) { + parent::__construct(); $this->sessionKey = $sessionKey; $this->storeSessionCookies = $storeSessionCookies; $this->load(); diff --git a/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/guzzlehttp/guzzle/src/Cookie/SetCookie.php index f6993943..3d776a70 100644 --- a/guzzlehttp/guzzle/src/Cookie/SetCookie.php +++ b/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -227,7 +227,7 @@ class SetCookie /** * Get whether or not this is a secure cookie * - * @return null|bool + * @return bool|null */ public function getSecure() { @@ -247,7 +247,7 @@ class SetCookie /** * Get whether or not this is a session cookie * - * @return null|bool + * @return bool|null */ public function getDiscard() { diff --git a/guzzlehttp/guzzle/src/Exception/ClientException.php b/guzzlehttp/guzzle/src/Exception/ClientException.php index f95c09f2..4cfd393c 100644 --- a/guzzlehttp/guzzle/src/Exception/ClientException.php +++ b/guzzlehttp/guzzle/src/Exception/ClientException.php @@ -4,4 +4,6 @@ namespace GuzzleHttp\Exception; /** * Exception when a client error is encountered (4xx codes) */ -class ClientException extends BadResponseException {} +class ClientException extends BadResponseException +{ +} diff --git a/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/guzzlehttp/guzzle/src/Exception/GuzzleException.php index 510778f6..27b2722b 100644 --- a/guzzlehttp/guzzle/src/Exception/GuzzleException.php +++ b/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -1,13 +1,23 @@ <?php namespace GuzzleHttp\Exception; -/** - * @method string getMessage() - * @method \Throwable|null getPrevious() - * @method mixed getCode() - * @method string getFile() - * @method int getLine() - * @method array getTrace() - * @method string getTraceAsString() - */ -interface GuzzleException {} +use Throwable; + +if (interface_exists(Throwable::class)) { + interface GuzzleException extends Throwable + { + } +} else { + /** + * @method string getMessage() + * @method \Throwable|null getPrevious() + * @method mixed getCode() + * @method string getFile() + * @method int getLine() + * @method array getTrace() + * @method string getTraceAsString() + */ + interface GuzzleException + { + } +} diff --git a/guzzlehttp/guzzle/src/Exception/RequestException.php b/guzzlehttp/guzzle/src/Exception/RequestException.php index 39de327e..12dd081e 100644 --- a/guzzlehttp/guzzle/src/Exception/RequestException.php +++ b/guzzlehttp/guzzle/src/Exception/RequestException.php @@ -1,9 +1,9 @@ <?php namespace GuzzleHttp\Exception; +use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\UriInterface; /** @@ -14,7 +14,7 @@ class RequestException extends TransferException /** @var RequestInterface */ private $request; - /** @var ResponseInterface */ + /** @var ResponseInterface|null */ private $response; /** @var array */ @@ -124,42 +124,17 @@ class RequestException extends TransferException */ public static function getResponseBodySummary(ResponseInterface $response) { - $body = $response->getBody(); - - if (!$body->isSeekable()) { - return null; - } - - $size = $body->getSize(); - - if ($size === 0) { - return null; - } - - $summary = $body->read(120); - $body->rewind(); - - if ($size > 120) { - $summary .= ' (truncated...)'; - } - - // Matches any printable character, including unicode characters: - // letters, marks, numbers, punctuation, spacing, and separators. - if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) { - return null; - } - - return $summary; + return \GuzzleHttp\Psr7\get_message_body_summary($response); } /** - * Obfuscates URI if there is an username and a password present + * Obfuscates URI if there is a username and a password present * * @param UriInterface $uri * * @return UriInterface */ - private static function obfuscateUri($uri) + private static function obfuscateUri(UriInterface $uri) { $userInfo = $uri->getUserInfo(); diff --git a/guzzlehttp/guzzle/src/Exception/ServerException.php b/guzzlehttp/guzzle/src/Exception/ServerException.php index 7cdd3408..127094c1 100644 --- a/guzzlehttp/guzzle/src/Exception/ServerException.php +++ b/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -4,4 +4,6 @@ namespace GuzzleHttp\Exception; /** * Exception when a server error is encountered (5xx codes) */ -class ServerException extends BadResponseException {} +class ServerException extends BadResponseException +{ +} diff --git a/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php b/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php index b60a9678..fff05251 100644 --- a/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php +++ b/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php @@ -1,4 +1,6 @@ <?php namespace GuzzleHttp\Exception; -class TooManyRedirectsException extends RequestException {} +class TooManyRedirectsException extends RequestException +{ +} diff --git a/guzzlehttp/guzzle/src/Exception/TransferException.php b/guzzlehttp/guzzle/src/Exception/TransferException.php index b92071ca..7c11db3a 100644 --- a/guzzlehttp/guzzle/src/Exception/TransferException.php +++ b/guzzlehttp/guzzle/src/Exception/TransferException.php @@ -1,4 +1,6 @@ <?php namespace GuzzleHttp\Exception; -class TransferException extends \RuntimeException implements GuzzleException {} +class TransferException extends \RuntimeException implements GuzzleException +{ +} diff --git a/guzzlehttp/guzzle/src/Handler/CurlFactory.php b/guzzlehttp/guzzle/src/Handler/CurlFactory.php index e0923714..4a28a96e 100644 --- a/guzzlehttp/guzzle/src/Handler/CurlFactory.php +++ b/guzzlehttp/guzzle/src/Handler/CurlFactory.php @@ -1,8 +1,8 @@ <?php namespace GuzzleHttp\Handler; -use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Psr7; use GuzzleHttp\Psr7\LazyOpenStream; @@ -14,6 +14,9 @@ use Psr\Http\Message\RequestInterface; */ class CurlFactory implements CurlFactoryInterface { + const CURL_VERSION_STR = 'curl_version'; + const LOW_CURL_VERSION_NUMBER = '7.21.2'; + /** @var array */ private $handles = []; @@ -117,6 +120,7 @@ class CurlFactory implements CurlFactoryInterface private static function invokeStats(EasyHandle $easy) { $curlStats = curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME); $stats = new TransferStats( $easy->request, $easy->response, @@ -136,7 +140,9 @@ class CurlFactory implements CurlFactoryInterface $ctx = [ 'errno' => $easy->errno, 'error' => curl_error($easy->handle), + 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME), ] + curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = curl_version()['version']; $factory->release($easy); // Retry when nothing is present or when curl failed to rewind. @@ -172,13 +178,22 @@ class CurlFactory implements CurlFactoryInterface ) ); } - - $message = sprintf( - 'cURL error %s: %s (%s)', - $ctx['errno'], - $ctx['error'], - 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' - ); + if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) { + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + } else { + $message = sprintf( + 'cURL error %s: %s (%s) for %s', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', + $easy->request->getUri() + ); + } // Create a connection exception if it was a specific error code. $error = isset($connectionErrors[$easy->errno]) @@ -439,11 +454,16 @@ class CurlFactory implements CurlFactoryInterface } if (isset($options['ssl_key'])) { - $sslKey = $options['ssl_key']; - if (is_array($sslKey)) { - $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; - $sslKey = $sslKey[0]; + if (is_array($options['ssl_key'])) { + if (count($options['ssl_key']) === 2) { + list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key']; + } else { + list($sslKey) = $options['ssl_key']; + } } + + $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key']; + if (!file_exists($sslKey)) { throw new \InvalidArgumentException( "SSL private key not found: {$sslKey}" diff --git a/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php index 2754d8e4..b73e5c72 100644 --- a/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php +++ b/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -1,9 +1,9 @@ <?php namespace GuzzleHttp\Handler; +use GuzzleHttp\Exception\InvalidArgumentException; use GuzzleHttp\Promise as P; use GuzzleHttp\Promise\Promise; -use GuzzleHttp\Psr7; use Psr\Http\Message\RequestInterface; /** @@ -23,6 +23,7 @@ class CurlMultiHandler private $active; private $handles = []; private $delays = []; + private $options = []; /** * This handler accepts the following options: @@ -30,6 +31,8 @@ class CurlMultiHandler * - handle_factory: An optional factory used to create curl handles * - select_timeout: Optional timeout (in seconds) to block before timing * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() * * @param array $options */ @@ -37,14 +40,31 @@ class CurlMultiHandler { $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(50); - $this->selectTimeout = isset($options['select_timeout']) - ? $options['select_timeout'] : 1; + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + $this->selectTimeout = $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = isset($options['options']) ? $options['options'] : []; } public function __get($name) { if ($name === '_mh') { - return $this->_mh = curl_multi_init(); + $this->_mh = curl_multi_init(); + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + // Further calls to _mh will return the value directly, without entering the + // __get() method at all. + return $this->_mh; } throw new \BadMethodCallException(); @@ -82,7 +102,7 @@ class CurlMultiHandler { // Add any delayed handles if needed. if ($this->delays) { - $currentTime = microtime(true); + $currentTime = \GuzzleHttp\_current_time(); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { unset($this->delays[$id]); @@ -134,7 +154,7 @@ class CurlMultiHandler if (empty($easy->options['delay'])) { curl_multi_add_handle($this->_mh, $easy->handle); } else { - $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); + $this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000); } } @@ -186,7 +206,7 @@ class CurlMultiHandler private function timeToNext() { - $currentTime = microtime(true); + $currentTime = \GuzzleHttp\_current_time(); $nextTime = PHP_INT_MAX; foreach ($this->delays as $time) { if ($time < $nextTime) { diff --git a/guzzlehttp/guzzle/src/Handler/MockHandler.php b/guzzlehttp/guzzle/src/Handler/MockHandler.php index d892061c..5b312bc0 100644 --- a/guzzlehttp/guzzle/src/Handler/MockHandler.php +++ b/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -66,7 +66,7 @@ class MockHandler implements \Countable throw new \OutOfBoundsException('Mock queue is empty'); } - if (isset($options['delay'])) { + if (isset($options['delay']) && is_numeric($options['delay'])) { usleep($options['delay'] * 1000); } @@ -175,6 +175,11 @@ class MockHandler implements \Countable return count($this->queue); } + public function reset() + { + $this->queue = []; + } + private function invokeStats( RequestInterface $request, array $options, @@ -182,7 +187,8 @@ class MockHandler implements \Countable $reason = null ) { if (isset($options['on_stats'])) { - $stats = new TransferStats($request, $response, 0, $reason); + $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); call_user_func($options['on_stats'], $stats); } } diff --git a/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/guzzlehttp/guzzle/src/Handler/StreamHandler.php index b686545e..a8eba378 100644 --- a/guzzlehttp/guzzle/src/Handler/StreamHandler.php +++ b/guzzlehttp/guzzle/src/Handler/StreamHandler.php @@ -1,8 +1,8 @@ <?php namespace GuzzleHttp\Handler; -use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7; @@ -33,7 +33,7 @@ class StreamHandler usleep($options['delay'] * 1000); } - $startTime = isset($options['on_stats']) ? microtime(true) : null; + $startTime = isset($options['on_stats']) ? \GuzzleHttp\_current_time() : null; try { // Does not support the expect header. @@ -42,7 +42,7 @@ class StreamHandler // Append a content-length header if body size is zero to match // cURL's behavior. if (0 === $request->getBody()->getSize()) { - $request = $request->withHeader('Content-Length', 0); + $request = $request->withHeader('Content-Length', '0'); } return $this->createResponse( @@ -82,7 +82,7 @@ class StreamHandler $stats = new TransferStats( $request, $response, - microtime(true) - $startTime, + \GuzzleHttp\_current_time() - $startTime, $error, [] ); @@ -343,13 +343,25 @@ class StreamHandler if ('v4' === $options['force_ip_resolve']) { $records = dns_get_record($uri->getHost(), DNS_A); if (!isset($records[0]['ip'])) { - throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + throw new ConnectException( + sprintf( + "Could not resolve IPv4 address for host '%s'", + $uri->getHost() + ), + $request + ); } $uri = $uri->withHost($records[0]['ip']); } elseif ('v6' === $options['force_ip_resolve']) { $records = dns_get_record($uri->getHost(), DNS_AAAA); if (!isset($records[0]['ipv6'])) { - throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + throw new ConnectException( + sprintf( + "Could not resolve IPv6 address for host '%s'", + $uri->getHost() + ), + $request + ); } $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); } diff --git a/guzzlehttp/guzzle/src/HandlerStack.php b/guzzlehttp/guzzle/src/HandlerStack.php index 24c46fd9..6a49cc06 100644 --- a/guzzlehttp/guzzle/src/HandlerStack.php +++ b/guzzlehttp/guzzle/src/HandlerStack.php @@ -1,7 +1,9 @@ <?php namespace GuzzleHttp; +use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Creates a composed Guzzle handler function by stacking middlewares on top of @@ -9,7 +11,7 @@ use Psr\Http\Message\RequestInterface; */ class HandlerStack { - /** @var callable */ + /** @var callable|null */ private $handler; /** @var array */ @@ -59,6 +61,8 @@ class HandlerStack * * @param RequestInterface $request * @param array $options + * + * @return ResponseInterface|PromiseInterface */ public function __invoke(RequestInterface $request, array $options) { @@ -206,7 +210,7 @@ class HandlerStack } /** - * @param $name + * @param string $name * @return int */ private function findByName($name) @@ -223,10 +227,10 @@ class HandlerStack /** * Splices a function into the middleware list at a specific position. * - * @param $findName - * @param $withName + * @param string $findName + * @param string $withName * @param callable $middleware - * @param $before + * @param bool $before */ private function splice($findName, $withName, callable $middleware, $before) { diff --git a/guzzlehttp/guzzle/src/MessageFormatter.php b/guzzlehttp/guzzle/src/MessageFormatter.php index 663ac739..dc36bb52 100644 --- a/guzzlehttp/guzzle/src/MessageFormatter.php +++ b/guzzlehttp/guzzle/src/MessageFormatter.php @@ -168,6 +168,11 @@ class MessageFormatter ); } + /** + * Get headers from message as string + * + * @return string + */ private function headers(MessageInterface $message) { $result = ''; diff --git a/guzzlehttp/guzzle/src/Middleware.php b/guzzlehttp/guzzle/src/Middleware.php index d4ad75c9..bffc1974 100644 --- a/guzzlehttp/guzzle/src/Middleware.php +++ b/guzzlehttp/guzzle/src/Middleware.php @@ -7,7 +7,6 @@ use GuzzleHttp\Promise\RejectedPromise; use GuzzleHttp\Psr7; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; /** * Functions used to create and wrap handlers with handler middleware. @@ -39,7 +38,7 @@ final class Middleware $cookieJar->extractCookies($request, $response); return $response; } - ); + ); }; }; } @@ -58,7 +57,7 @@ final class Middleware return $handler($request, $options); } return $handler($request, $options)->then( - function (ResponseInterface $response) use ($request, $handler) { + function (ResponseInterface $response) use ($request) { $code = $response->getStatusCode(); if ($code < 400) { return $response; @@ -183,7 +182,7 @@ final class Middleware * * @return callable Returns a function that accepts the next handler. */ - public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */) { return function (callable $handler) use ($logger, $formatter, $logLevel) { return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { diff --git a/guzzlehttp/guzzle/src/Pool.php b/guzzlehttp/guzzle/src/Pool.php index 8f1be33c..5838db4f 100644 --- a/guzzlehttp/guzzle/src/Pool.php +++ b/guzzlehttp/guzzle/src/Pool.php @@ -1,12 +1,13 @@ <?php namespace GuzzleHttp; +use GuzzleHttp\Promise\EachPromise; +use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\PromisorInterface; use Psr\Http\Message\RequestInterface; -use GuzzleHttp\Promise\EachPromise; /** - * Sends and iterator of requests concurrently using a capped pool size. + * Sends an iterator of requests concurrently using a capped pool size. * * The pool will read from an iterator until it is cancelled or until the * iterator is consumed. When a request is yielded, the request is sent after @@ -69,6 +70,11 @@ class Pool implements PromisorInterface $this->each = new EachPromise($requests(), $config); } + /** + * Get promise + * + * @return PromiseInterface + */ public function promise() { return $this->each->promise(); @@ -106,6 +112,11 @@ class Pool implements PromisorInterface return $res; } + /** + * Execute callback(s) + * + * @return void + */ private static function cmpCallback(array &$options, $name, array &$results) { if (!isset($options[$name])) { diff --git a/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php index 2eb95f9b..568a1e90 100644 --- a/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php +++ b/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -66,6 +66,11 @@ class PrepareBodyMiddleware return $fn(Psr7\modify_request($request, $modify), $options); } + /** + * Add expect header + * + * @return void + */ private function addExpectHeader( RequestInterface $request, array $options, diff --git a/guzzlehttp/guzzle/src/RedirectMiddleware.php b/guzzlehttp/guzzle/src/RedirectMiddleware.php index 131b7717..2f301261 100644 --- a/guzzlehttp/guzzle/src/RedirectMiddleware.php +++ b/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -13,7 +13,7 @@ use Psr\Http\Message\UriInterface; * Request redirect middleware. * * Apply this middleware like other middleware using - * {@see GuzzleHttp\Middleware::redirect()}. + * {@see \GuzzleHttp\Middleware::redirect()}. */ class RedirectMiddleware { @@ -76,7 +76,7 @@ class RedirectMiddleware /** * @param RequestInterface $request * @param array $options - * @param ResponseInterface|PromiseInterface $response + * @param ResponseInterface $response * * @return ResponseInterface|PromiseInterface */ @@ -118,6 +118,11 @@ class RedirectMiddleware return $promise; } + /** + * Enable tracking on promise. + * + * @return PromiseInterface + */ private function withTracking(PromiseInterface $promise, $uri, $statusCode) { return $promise->then( @@ -135,6 +140,13 @@ class RedirectMiddleware ); } + /** + * Check for too many redirects + * + * @return void + * + * @throws TooManyRedirectsException Too many redirects. + */ private function guardMax(RequestInterface $request, array &$options) { $current = isset($options['__redirect_count']) @@ -172,13 +184,19 @@ class RedirectMiddleware // would do. $statusCode = $response->getStatusCode(); if ($statusCode == 303 || - ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) + ($statusCode <= 302 && !$options['allow_redirects']['strict']) ) { $modify['method'] = 'GET'; $modify['body'] = ''; } - $modify['uri'] = $this->redirectUri($request, $response, $protocols); + $uri = $this->redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? IDNA_DEFAULT : $options['idn_conversion']; + $uri = _idn_uri_convert($uri, $idnOptions); + } + + $modify['uri'] = $uri; Psr7\rewind_body($request); // Add the Referer header if it is told to do so and only @@ -186,7 +204,7 @@ class RedirectMiddleware if ($options['allow_redirects']['referer'] && $modify['uri']->getScheme() === $request->getUri()->getScheme() ) { - $uri = $request->getUri()->withUserInfo('', ''); + $uri = $request->getUri()->withUserInfo(''); $modify['set_headers']['Referer'] = (string) $uri; } else { $modify['remove_headers'][] = 'Referer'; diff --git a/guzzlehttp/guzzle/src/RequestOptions.php b/guzzlehttp/guzzle/src/RequestOptions.php index c6aacfb1..355f658f 100644 --- a/guzzlehttp/guzzle/src/RequestOptions.php +++ b/guzzlehttp/guzzle/src/RequestOptions.php @@ -22,7 +22,7 @@ final class RequestOptions * - strict: (bool, default=false) Set to true to use strict redirects * meaning redirect POST requests with POST requests vs. doing what most * browsers do which is redirect POST requests with GET requests - * - referer: (bool, default=true) Set to false to disable the Referer + * - referer: (bool, default=false) Set to true to enable the Referer * header. * - protocols: (array, default=['http', 'https']) Allowed redirect * protocols. @@ -133,6 +133,14 @@ final class RequestOptions const HTTP_ERRORS = 'http_errors'; /** + * idn: (bool|int, default=true) A combination of IDNA_* constants for + * idn_to_ascii() PHP's function (see "options" parameter). Set to false to + * disable IDN support completely, or to true to use the default + * configuration (IDNA_DEFAULT constant). + */ + const IDN_CONVERSION = 'idn_conversion'; + + /** * json: (mixed) Adds JSON data to a request. The provided value is JSON * encoded and a Content-Type header of application/json will be added to * the request if no Content-Type header is already present. diff --git a/guzzlehttp/guzzle/src/RetryMiddleware.php b/guzzlehttp/guzzle/src/RetryMiddleware.php index f27090fd..5acc8c5c 100644 --- a/guzzlehttp/guzzle/src/RetryMiddleware.php +++ b/guzzlehttp/guzzle/src/RetryMiddleware.php @@ -19,6 +19,9 @@ class RetryMiddleware /** @var callable */ private $decider; + /** @var callable */ + private $delay; + /** * @param callable $decider Function that accepts the number of retries, * a request, [response], and [exception] and @@ -42,13 +45,13 @@ class RetryMiddleware /** * Default exponential backoff delay function. * - * @param $retries + * @param int $retries * - * @return int + * @return int milliseconds. */ public static function exponentialDelay($retries) { - return (int) pow(2, $retries - 1); + return (int) pow(2, $retries - 1) * 1000; } /** @@ -71,6 +74,11 @@ class RetryMiddleware ); } + /** + * Execute fulfilled closure + * + * @return mixed + */ private function onFulfilled(RequestInterface $req, array $options) { return function ($value) use ($req, $options) { @@ -87,6 +95,11 @@ class RetryMiddleware }; } + /** + * Execute rejected closure + * + * @return callable + */ private function onRejected(RequestInterface $req, array $options) { return function ($reason) use ($req, $options) { @@ -103,6 +116,9 @@ class RetryMiddleware }; } + /** + * @return self + */ private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) { $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); diff --git a/guzzlehttp/guzzle/src/TransferStats.php b/guzzlehttp/guzzle/src/TransferStats.php index 15f717e1..87fb3c00 100644 --- a/guzzlehttp/guzzle/src/TransferStats.php +++ b/guzzlehttp/guzzle/src/TransferStats.php @@ -18,11 +18,11 @@ final class TransferStats private $handlerErrorData; /** - * @param RequestInterface $request Request that was sent. - * @param ResponseInterface $response Response received (if any) - * @param null $transferTime Total handler transfer time. - * @param mixed $handlerErrorData Handler error data. - * @param array $handlerStats Handler specific stats. + * @param RequestInterface $request Request that was sent. + * @param ResponseInterface|null $response Response received (if any) + * @param float|null $transferTime Total handler transfer time. + * @param mixed $handlerErrorData Handler error data. + * @param array $handlerStats Handler specific stats. */ public function __construct( RequestInterface $request, @@ -93,7 +93,7 @@ final class TransferStats /** * Get the estimated time the request was being transferred by the handler. * - * @return float Time in seconds. + * @return float|null Time in seconds. */ public function getTransferTime() { diff --git a/guzzlehttp/guzzle/src/functions.php b/guzzlehttp/guzzle/src/functions.php index a3ac450d..5e806f63 100644 --- a/guzzlehttp/guzzle/src/functions.php +++ b/guzzlehttp/guzzle/src/functions.php @@ -1,10 +1,12 @@ <?php namespace GuzzleHttp; +use GuzzleHttp\Exception\InvalidArgumentException; use GuzzleHttp\Handler\CurlHandler; use GuzzleHttp\Handler\CurlMultiHandler; use GuzzleHttp\Handler\Proxy; use GuzzleHttp\Handler\StreamHandler; +use Psr\Http\Message\UriInterface; /** * Expands a URI template @@ -56,7 +58,7 @@ function describe_type($input) /** * Parses an array of header lines into an associative array of headers. * - * @param array $lines Header lines array of strings in the following + * @param iterable $lines Header lines array of strings in the following * format: "Name: Value" * @return array */ @@ -196,7 +198,8 @@ function default_ca_bundle() } } - throw new \RuntimeException(<<< EOT + throw new \RuntimeException( + <<< EOT No system CA bundle could be found in any of the the common system locations. PHP versions earlier than 5.6 are not properly configured to use the system's CA bundle by default. In order to verify peer certificates, you will need to @@ -294,14 +297,14 @@ function is_host_in_noproxy($host, array $noProxyArray) * @param int $options Bitmask of JSON decode options. * * @return mixed - * @throws \InvalidArgumentException if the JSON cannot be decoded. + * @throws Exception\InvalidArgumentException if the JSON cannot be decoded. * @link http://www.php.net/manual/en/function.json-decode.php */ function json_decode($json, $assoc = false, $depth = 512, $options = 0) { $data = \json_decode($json, $assoc, $depth, $options); if (JSON_ERROR_NONE !== json_last_error()) { - throw new \InvalidArgumentException( + throw new Exception\InvalidArgumentException( 'json_decode error: ' . json_last_error_msg() ); } @@ -317,17 +320,75 @@ function json_decode($json, $assoc = false, $depth = 512, $options = 0) * @param int $depth Set the maximum depth. Must be greater than zero. * * @return string - * @throws \InvalidArgumentException if the JSON cannot be encoded. + * @throws Exception\InvalidArgumentException if the JSON cannot be encoded. * @link http://www.php.net/manual/en/function.json-encode.php */ function json_encode($value, $options = 0, $depth = 512) { $json = \json_encode($value, $options, $depth); if (JSON_ERROR_NONE !== json_last_error()) { - throw new \InvalidArgumentException( + throw new Exception\InvalidArgumentException( 'json_encode error: ' . json_last_error_msg() ); } return $json; } + +/** + * Wrapper for the hrtime() or microtime() functions + * (depending on the PHP version, one of the two is used) + * + * @return float|mixed UNIX timestamp + * @internal + */ +function _current_time() +{ + return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); +} + + +/** + * @param int $options + * + * @return UriInterface + * + * @internal + */ +function _idn_uri_convert(UriInterface $uri, $options = 0) +{ + if ($uri->getHost()) { + $idnaVariant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0; + $asciiHost = $idnaVariant === 0 + ? idn_to_ascii($uri->getHost(), $options) + : idn_to_ascii($uri->getHost(), $options, $idnaVariant, $info); + if ($asciiHost === false) { + $errorBitSet = isset($info['errors']) ? $info['errors'] : 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; + } + + throw new InvalidArgumentException($errorMessage); + } else { + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + } + + return $uri; +} diff --git a/guzzlehttp/psr7/CHANGELOG.md b/guzzlehttp/psr7/CHANGELOG.md index 27b65f09..8a3743db 100644 --- a/guzzlehttp/psr7/CHANGELOG.md +++ b/guzzlehttp/psr7/CHANGELOG.md @@ -10,6 +10,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [1.6.0] + +### Added + +- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244) +- Added MIME type for WEBP image format (#246) +- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272) + +### Changed + +- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262) +- Accept port number 0 to be valid (#270) + +### Fixed + +- Fixed subsequent reads from `php://input` in ServerRequest (#247) +- Fixed readable/writable detection for certain stream modes (#248) +- Fixed encoding of special characters in the `userInfo` component of an URI (#253) + + ## [1.5.2] - 2018-12-04 ### Fixed @@ -209,7 +229,8 @@ Currently unsupported: -[Unreleased]: https://github.com/guzzle/psr7/compare/1.5.2...HEAD +[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 [1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 [1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 diff --git a/guzzlehttp/psr7/composer.json b/guzzlehttp/psr7/composer.json index 2add153e..168a055b 100644 --- a/guzzlehttp/psr7/composer.json +++ b/guzzlehttp/psr7/composer.json @@ -18,14 +18,18 @@ "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "require-dev": { - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8", + "ext-zlib": "*" }, "provide": { "psr/http-message-implementation": "1.0" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" @@ -39,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } } } diff --git a/guzzlehttp/psr7/src/LimitStream.php b/guzzlehttp/psr7/src/LimitStream.php index 3c13d4f4..e4f239e3 100644 --- a/guzzlehttp/psr7/src/LimitStream.php +++ b/guzzlehttp/psr7/src/LimitStream.php @@ -72,7 +72,7 @@ class LimitStream implements StreamInterface { if ($whence !== SEEK_SET || $offset < 0) { throw new \RuntimeException(sprintf( - 'Cannot seek to offset % with whence %s', + 'Cannot seek to offset %s with whence %s', $offset, $whence )); diff --git a/guzzlehttp/psr7/src/MessageTrait.php b/guzzlehttp/psr7/src/MessageTrait.php index 1e4da649..a7966d10 100644 --- a/guzzlehttp/psr7/src/MessageTrait.php +++ b/guzzlehttp/psr7/src/MessageTrait.php @@ -66,11 +66,8 @@ trait MessageTrait public function withHeader($header, $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -85,11 +82,8 @@ trait MessageTrait public function withAddedHeader($header, $value) { - if (!is_array($value)) { - $value = [$value]; - } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); $new = clone $this; @@ -144,11 +138,13 @@ trait MessageTrait { $this->headerNames = $this->headers = []; foreach ($headers as $header => $value) { - if (!is_array($value)) { - $value = [$value]; + if (is_int($header)) { + // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec + // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass. + $header = (string) $header; } - - $value = $this->trimHeaderValues($value); + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); $normalized = strtolower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; @@ -160,6 +156,19 @@ trait MessageTrait } } + private function normalizeHeaderValue($value) + { + if (!is_array($value)) { + return $this->trimHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimHeaderValues($value); + } + /** * Trims whitespace from the header values. * @@ -177,7 +186,28 @@ trait MessageTrait private function trimHeaderValues(array $values) { return array_map(function ($value) { - return trim($value, " \t"); + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + return trim((string) $value, " \t"); }, $values); } + + private function assertHeader($header) + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if ($header === '') { + throw new \InvalidArgumentException('Header name can not be empty.'); + } + } } diff --git a/guzzlehttp/psr7/src/Request.php b/guzzlehttp/psr7/src/Request.php index 00066424..59f337db 100644 --- a/guzzlehttp/psr7/src/Request.php +++ b/guzzlehttp/psr7/src/Request.php @@ -36,6 +36,7 @@ class Request implements RequestInterface $body = null, $version = '1.1' ) { + $this->assertMethod($method); if (!($uri instanceof UriInterface)) { $uri = new Uri($uri); } @@ -91,6 +92,7 @@ class Request implements RequestInterface public function withMethod($method) { + $this->assertMethod($method); $new = clone $this; $new->method = strtoupper($method); return $new; @@ -139,4 +141,11 @@ class Request implements RequestInterface // See: http://tools.ietf.org/html/rfc7230#section-5.4 $this->headers = [$header => [$host]] + $this->headers; } + + private function assertMethod($method) + { + if (!is_string($method) || $method === '') { + throw new \InvalidArgumentException('Method must be a non-empty string.'); + } + } } diff --git a/guzzlehttp/psr7/src/Response.php b/guzzlehttp/psr7/src/Response.php index 6e72c06b..e7e04d86 100644 --- a/guzzlehttp/psr7/src/Response.php +++ b/guzzlehttp/psr7/src/Response.php @@ -93,11 +93,11 @@ class Response implements ResponseInterface $version = '1.1', $reason = null ) { - if (filter_var($status, FILTER_VALIDATE_INT) === false) { - throw new \InvalidArgumentException('Status code must be an integer value.'); - } + $this->assertStatusCodeIsInteger($status); + $status = (int) $status; + $this->assertStatusCodeRange($status); - $this->statusCode = (int) $status; + $this->statusCode = $status; if ($body !== '' && $body !== null) { $this->stream = stream_for($body); @@ -125,12 +125,30 @@ class Response implements ResponseInterface public function withStatus($code, $reasonPhrase = '') { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + $new = clone $this; - $new->statusCode = (int) $code; + $new->statusCode = $code; if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { $reasonPhrase = self::$phrases[$new->statusCode]; } $new->reasonPhrase = $reasonPhrase; return $new; } + + private function assertStatusCodeIsInteger($statusCode) + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange($statusCode) + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } } diff --git a/guzzlehttp/psr7/src/ServerRequest.php b/guzzlehttp/psr7/src/ServerRequest.php index 99f453a5..1a09a6c8 100644 --- a/guzzlehttp/psr7/src/ServerRequest.php +++ b/guzzlehttp/psr7/src/ServerRequest.php @@ -168,7 +168,7 @@ class ServerRequest extends Request implements ServerRequestInterface $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; $headers = getallheaders(); $uri = self::getUriFromGlobals(); - $body = new LazyOpenStream('php://input', 'r+'); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); diff --git a/guzzlehttp/psr7/src/Stream.php b/guzzlehttp/psr7/src/Stream.php index 111795eb..d9e7409c 100644 --- a/guzzlehttp/psr7/src/Stream.php +++ b/guzzlehttp/psr7/src/Stream.php @@ -10,6 +10,17 @@ use Psr\Http\Message\StreamInterface; */ class Stream implements StreamInterface { + /** + * Resource modes. + * + * @var string + * + * @see http://php.net/manual/function.fopen.php + * @see http://php.net/manual/en/function.gzopen.php + */ + const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/'; + const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/'; + private $stream; private $size; private $seekable; @@ -18,22 +29,6 @@ class Stream implements StreamInterface private $uri; private $customMetadata; - /** @var array Hash of readable and writable stream types */ - private static $readWriteHash = [ - 'read' => [ - 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, - 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, - 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a+' => true, 'rb+' => true, - ], - 'write' => [ - 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, - 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'rb+' => true, - 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true - ] - ]; - /** * This constructor accepts an associative array of options. * @@ -65,8 +60,8 @@ class Stream implements StreamInterface $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; - $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); - $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); $this->uri = $this->getMetadata('uri'); } @@ -197,6 +192,8 @@ class Stream implements StreamInterface public function seek($offset, $whence = SEEK_SET) { + $whence = (int) $whence; + if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } diff --git a/guzzlehttp/psr7/src/Uri.php b/guzzlehttp/psr7/src/Uri.php index 36219568..825a25ee 100644 --- a/guzzlehttp/psr7/src/Uri.php +++ b/guzzlehttp/psr7/src/Uri.php @@ -437,9 +437,9 @@ class Uri implements UriInterface public function withUserInfo($user, $password = null) { - $info = $user; - if ($password != '') { - $info .= ':' . $password; + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); } if ($this->userInfo === $info) { @@ -537,7 +537,9 @@ class Uri implements UriInterface $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; - $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : ''; @@ -554,7 +556,7 @@ class Uri implements UriInterface ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { - $this->userInfo .= ':' . $parts['pass']; + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); } $this->removeDefaultPort(); @@ -577,6 +579,26 @@ class Uri implements UriInterface } /** + * @param string $component + * + * @return string + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component) + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** * @param string $host * * @return string @@ -606,9 +628,9 @@ class Uri implements UriInterface } $port = (int) $port; - if (1 > $port || 0xffff < $port) { + if (0 > $port || 0xffff < $port) { throw new \InvalidArgumentException( - sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) ); } diff --git a/guzzlehttp/psr7/src/functions.php b/guzzlehttp/psr7/src/functions.php index 957bfb42..8e6dafe6 100644 --- a/guzzlehttp/psr7/src/functions.php +++ b/guzzlehttp/psr7/src/functions.php @@ -724,6 +724,7 @@ function mimetype_from_extension($extension) 'txt' => 'text/plain', 'wav' => 'audio/x-wav', 'webm' => 'video/webm', + 'webp' => 'image/webp', 'wma' => 'audio/x-ms-wma', 'wmv' => 'video/x-ms-wmv', 'woff' => 'application/x-font-woff', |