Update to Drupal 8.1.7. For more information, see https://www.drupal.org/project/drupal/releases/8.1.7

This commit is contained in:
Pantheon Automation 2016-07-18 09:07:48 -07:00 committed by Greg Anderson
parent 38ba7c357d
commit e9f047ccf8
61 changed files with 1613 additions and 561 deletions

View file

@ -180,8 +180,10 @@ AddEncoding gzip svgz
</IfModule>
</IfModule>
# Add headers to all responses.
# Various header fixes.
<IfModule mod_headers.c>
# Disable content sniffing, since it's an attack vector.
Header always set X-Content-Type-Options nosniff
# Disable Proxy header, since it's an attack vector.
RequestHeader unset Proxy
</IfModule>

52
composer.lock generated
View file

@ -678,32 +678,32 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.1.0",
"version": "6.2.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81"
"reference": "3f808fba627f2c5b69e2501217bf31af349c1427"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427",
"reference": "3f808fba627f2c5b69e2501217bf31af349c1427",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "~1.0",
"guzzlehttp/psr7": "~1.1",
"php": ">=5.5.0"
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.3.1",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~4.0",
"psr/log": "~1.0"
"phpunit/phpunit": "^4.0",
"psr/log": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "6.2-dev"
}
},
"autoload": {
@ -736,20 +736,20 @@
"rest",
"web service"
],
"time": "2015-09-08 17:36:26"
"time": "2016-07-15 17:22:37"
},
{
"name": "guzzlehttp/promises",
"version": "1.0.2",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "97fe7210def29451ec74923b27e552238defd75a"
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a",
"reference": "97fe7210def29451ec74923b27e552238defd75a",
"url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
"shasum": ""
},
"require": {
@ -787,20 +787,20 @@
"keywords": [
"promise"
],
"time": "2015-08-15 19:37:21"
"time": "2016-05-18 16:56:05"
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.0",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"shasum": ""
},
"require": {
@ -816,7 +816,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
},
"autoload": {
@ -845,7 +845,7 @@
"stream",
"uri"
],
"time": "2015-08-15 19:32:36"
"time": "2016-06-24 23:00:38"
},
{
"name": "ircmaxell/password-compat",
@ -1057,12 +1057,12 @@
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
"reference": "1.0.0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
"url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0",
"reference": "1.0.0",
"shasum": ""
},
"type": "library",
@ -1183,7 +1183,7 @@
],
"authors": [
{
"name": "Symfony CMF Community",
"name": "Symfony CMF community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors"
}
],

View file

@ -1,3 +1,7 @@
Drupal 8.1.7, 2016-07-18
------------------------
- Fixed security issue. SA-CORE-2016-003.
Drupal 8.1.3, 2016-06-15
------------------------
- Fixed security issue. SA-CORE-2016-002.

View file

@ -21,7 +21,7 @@
"twig/twig": "^1.23.1",
"doctrine/common": "2.5.*",
"doctrine/annotations": "1.2.*",
"guzzlehttp/guzzle": "~6.1",
"guzzlehttp/guzzle": "~6.2",
"symfony-cmf/routing": "1.3.*",
"easyrdf/easyrdf": "0.9.*",
"zendframework/zend-feed": "~2.4",

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.1.5';
const VERSION = '8.1.7';
/**
* Core API compatibility.

View file

@ -206,7 +206,7 @@ class ConfigDependencyManager {
* @return int
* The comparison result for uasort().
*/
protected function sortGraphByWeight(array $a, array $b) {
protected static function sortGraphByWeight(array $a, array $b) {
$weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
if ($weight_cmp === 0) {
@ -227,7 +227,7 @@ class ConfigDependencyManager {
* @return int
* The comparison result for uasort().
*/
public function sortGraph(array $a, array $b) {
public static function sortGraph(array $a, array $b) {
$weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
if ($weight_cmp === 0) {

View file

@ -52,6 +52,13 @@ class ClientFactory {
'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
],
'handler' => $this->stack,
// Security consideration: prevent Guzzle from using environment variables
// to configure the outbound proxy.
'proxy' => [
'http' => NULL,
'https' => NULL,
'no' => [],
]
];
$config = NestedArray::mergeDeep($default_config, Settings::get('http_client_config', []), $config);

View file

@ -68,13 +68,14 @@ function ckeditor_ckeditor_css_alter(array &$css, Editor $editor) {
}
/**
* Retrieves the default theme's CKEditor stylesheets defined in the .info file.
* Retrieves the default theme's CKEditor stylesheets.
*
* Themes may specify iframe-specific CSS files for use with CKEditor by
* including a "ckeditor_stylesheets" key in the theme .info file.
* including a "ckeditor_stylesheets" key in their .info.yml file.
*
* @code
* ckeditor_stylesheets[] = css/ckeditor-iframe.css
* ckeditor_stylesheets:
* - css/ckeditor-iframe.css
* @endcode
*/
function _ckeditor_theme_css($theme = NULL) {

View file

@ -797,22 +797,6 @@ function system_requirements($phase) {
}
}
// Check if the Twig C extension is available.
if ($phase == 'runtime') {
$url = 'http://twig.sensiolabs.org/doc/installation.html#installing-the-c-extension';
$requirements['twig_c_extension'] = [
'title' => t('Twig C extension'),
'severity' => REQUIREMENT_INFO,
];
if (!function_exists('twig_template_get_attributes')) {
$requirements['twig_c_extension']['value'] = t('Not available');
$requirements['twig_c_extension']['description'] = t('Enabling the Twig C extension can greatly increase rendering performance. See <a href=":url">the installation instructions</a> for more detail.', [':url' => $url]);
}
else {
$requirements['twig_c_extension']['description'] = t('The <a href=":url">Twig C extension</a> is available', [':url' => $url]);
}
}
// Check xdebug.max_nesting_level, as some pages will not work if it is too
// low.
if (extension_loaded('xdebug')) {

View file

@ -0,0 +1,5 @@
<?php
// @codingStandardsIgnoreFile
// This file is intentionally empty so that it overwrites when sites are
// updated from a zip/tarball without deleting the /core folder first.
// @todo: remove in 8.3.x

View file

@ -0,0 +1,5 @@
<?php
// @codingStandardsIgnoreFile
// This file is intentionally empty so that it overwrites when sites are
// updated from a zip/tarball without deleting the /core folder first.
// @todo: remove in 8.3.x

View file

@ -28,7 +28,6 @@
*
* Regions:
* - page.header: Items for the header region.
* - page.highlighted: Items for the highlighted region.
* - page.primary_menu: Items for the primary menu region.
* - page.secondary_menu: Items for the secondary menu region.
* - page.highlighted: Items for the highlighted content region.

View file

@ -325,9 +325,6 @@ $settings['update_free_access'] = FALSE;
*
* You can also define an array of host names that can be accessed directly,
* bypassing the proxy, in $settings['http_client_config']['proxy']['no'].
*
* If these settings are not configured, the system environment variables
* HTTP_PROXY, HTTPS_PROXY, and NO_PROXY on the web server will be used instead.
*/
# $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080';
# $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080';

View file

@ -741,17 +741,17 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.0.2",
"version_normalized": "1.0.2.0",
"version": "1.2.0",
"version_normalized": "1.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "97fe7210def29451ec74923b27e552238defd75a"
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a",
"reference": "97fe7210def29451ec74923b27e552238defd75a",
"url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
"shasum": ""
},
"require": {
@ -760,7 +760,7 @@
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"time": "2015-08-15 19:37:21",
"time": "2016-05-18 16:56:05",
"type": "library",
"extra": {
"branch-alias": {
@ -845,17 +845,17 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.0",
"version_normalized": "1.2.0.0",
"version": "1.3.1",
"version_normalized": "1.3.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"shasum": ""
},
"require": {
@ -868,11 +868,11 @@
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"time": "2015-08-15 19:32:36",
"time": "2016-06-24 23:00:38",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
},
"installation-source": "dist",
@ -1369,12 +1369,12 @@
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
"reference": "1.0.0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
"url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0",
"reference": "1.0.0",
"shasum": ""
},
"time": "2012-12-21 11:40:51",
@ -1718,7 +1718,7 @@
],
"authors": [
{
"name": "Symfony CMF Community",
"name": "Symfony CMF community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors"
}
],
@ -2783,34 +2783,34 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.1.0",
"version_normalized": "6.1.0.0",
"version": "6.2.1",
"version_normalized": "6.2.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81"
"reference": "3f808fba627f2c5b69e2501217bf31af349c1427"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427",
"reference": "3f808fba627f2c5b69e2501217bf31af349c1427",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "~1.0",
"guzzlehttp/psr7": "~1.1",
"php": ">=5.5.0"
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.3.1",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~4.0",
"psr/log": "~1.0"
"phpunit/phpunit": "^4.0",
"psr/log": "^1.0"
},
"time": "2015-09-08 17:36:26",
"time": "2016-07-15 17:22:37",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "6.2-dev"
}
},
"installation-source": "dist",

View file

@ -1,5 +1,7 @@
language: php
sudo: false
php:
- 5.5
- 5.6
@ -12,16 +14,17 @@ before_script:
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
- ~/.nvm/nvm.sh run v0.6.14
- '[ "$TRAVIS_PHP_VERSION" != "7.0" ] || echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'
script: make test
matrix:
allow_failures:
- php: hhvm
- php: 7.0
fast_finish: true
before_deploy:
- rvm 1.9.3 do gem install mime-types -v 2.6.2
- make package
deploy:

View file

@ -1,5 +1,49 @@
# CHANGELOG
## 6.2.1 - 2016-07-18
* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
https://httpoxy.org/
* Fixing timeout bug with StreamHandler:
https://github.com/guzzle/guzzle/pull/1488
* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
a server does not honor `Connection: close`.
* Ignore URI fragment when sending requests.
## 6.2.0 - 2016-03-21
* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
https://github.com/guzzle/guzzle/pull/1389
* Bug fix: Fix sleep calculation when waiting for delayed requests.
https://github.com/guzzle/guzzle/pull/1324
* Feature: More flexible history containers.
https://github.com/guzzle/guzzle/pull/1373
* Bug fix: defer sink stream opening in StreamHandler.
https://github.com/guzzle/guzzle/pull/1377
* Bug fix: do not attempt to escape cookie values.
https://github.com/guzzle/guzzle/pull/1406
* Feature: report original content encoding and length on decoded responses.
https://github.com/guzzle/guzzle/pull/1409
* Bug fix: rewind seekable request bodies before dispatching to cURL.
https://github.com/guzzle/guzzle/pull/1422
* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
https://github.com/guzzle/guzzle/pull/1367
## 6.1.1 - 2015-11-22
* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
* Feature: HandlerStack is now more generic.
https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
* Bug fix: setting verify to false in the StreamHandler now disables peer
verification. https://github.com/guzzle/guzzle/issues/1256
* Feature: Middleware now uses an exception factory, including more error
context. https://github.com/guzzle/guzzle/pull/1282
* Feature: better support for disabled functions.
https://github.com/guzzle/guzzle/pull/1287
* Bug fix: fixed regression where MockHandler was not using `sink`.
https://github.com/guzzle/guzzle/pull/1292
## 6.1.0 - 2015-09-08
* Feature: Added the `on_stats` request option to provide access to transfer

View file

@ -1,4 +1,4 @@
Copyright (c) 2011-2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,7 +1,7 @@
Guzzle, PHP HTTP client
=======================
[![Build Status](https://secure.travis-ci.org/guzzle/guzzle.svg?branch=master)](http://travis-ci.org/guzzle/guzzle)
[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle)
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
trivial to integrate with web services.
@ -18,13 +18,13 @@ trivial to integrate with web services.
- Middleware system allows you to augment and compose client behavior.
```php
$client = new GuzzleHttp\Client();
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
echo $res->getStatusCode();
// "200"
echo $res->getHeader('content-type');
// 200
echo $res->getHeaderLine('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
@ -57,7 +57,7 @@ curl -sS https://getcomposer.org/installer | php
Next, run the Composer command to install the latest stable version of Guzzle:
```bash
composer.phar require guzzlehttp/guzzle
php composer.phar require guzzlehttp/guzzle
```
After installing, you need to require Composer's autoloader:

View file

@ -13,14 +13,14 @@
}
],
"require": {
"php": ">=5.5.0",
"guzzlehttp/psr7": "~1.1",
"guzzlehttp/promises": "~1.0"
"php": ">=5.5",
"guzzlehttp/psr7": "^1.3.1",
"guzzlehttp/promises": "^1.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~4.0",
"psr/log": "~1.0"
"phpunit/phpunit": "^4.0",
"psr/log": "^1.0"
},
"autoload": {
"files": ["src/functions_include.php"],
@ -35,7 +35,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "6.2-dev"
}
}
}

View file

@ -93,7 +93,7 @@ class Client implements ClientInterface
$options = $this->prepareDefaults($options);
return $this->transfer(
$request->withUri($this->buildUri($request->getUri(), $options)),
$request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
$options
);
}
@ -104,7 +104,7 @@ class Client implements ClientInterface
return $this->sendAsync($request, $options)->wait();
}
public function requestAsync($method, $uri = null, array $options = [])
public function requestAsync($method, $uri = '', array $options = [])
{
$options = $this->prepareDefaults($options);
// Remove request modifying parameter because it can be done up-front.
@ -123,7 +123,7 @@ class Client implements ClientInterface
return $this->transfer($request, $options);
}
public function request($method, $uri = null, array $options = [])
public function request($method, $uri = '', array $options = [])
{
$options[RequestOptions::SYNCHRONOUS] = true;
return $this->requestAsync($method, $uri, $options)->wait();
@ -138,11 +138,14 @@ class Client implements ClientInterface
private function buildUri($uri, array $config)
{
if (!isset($config['base_uri'])) {
return $uri instanceof UriInterface ? $uri : new Psr7\Uri($uri);
// for BC we accept null which would otherwise fail in uri_for
$uri = Psr7\uri_for($uri === null ? '' : $uri);
if (isset($config['base_uri'])) {
$uri = Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
}
return Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
return $uri->getScheme() === '' ? $uri->withScheme('http') : $uri;
}
/**
@ -160,9 +163,13 @@ class Client implements ClientInterface
'cookies' => false
];
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set
if ($proxy = getenv('HTTP_PROXY')) {
$defaults['proxy']['http'] = $proxy;
// 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')) {
$defaults['proxy']['http'] = getenv('HTTP_PROXY');
}
if ($proxy = getenv('HTTPS_PROXY')) {
@ -173,7 +180,7 @@ class Client implements ClientInterface
$cleanedNoProxy = str_replace(' ', '', $noProxy);
$defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
}
$this->config = $config + $defaults;
if (!empty($config['cookies']) && $config['cookies'] === true) {
@ -255,7 +262,7 @@ class Client implements ClientInterface
unset($options['save_to']);
}
// exceptions -> http_error
// exceptions -> http_errors
if (isset($options['exceptions'])) {
$options['http_errors'] = $options['exceptions'];
unset($options['exceptions']);
@ -291,15 +298,20 @@ class Client implements ClientInterface
. 'x-www-form-urlencoded requests, and the multipart '
. 'option to send multipart/form-data requests.');
}
$options['body'] = http_build_query($options['form_params'], null, '&');
$options['body'] = http_build_query($options['form_params'], '', '&');
unset($options['form_params']);
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
}
if (isset($options['multipart'])) {
$elements = $options['multipart'];
$options['body'] = new Psr7\MultipartStream($options['multipart']);
unset($options['multipart']);
$options['body'] = new Psr7\MultipartStream($elements);
}
if (isset($options['json'])) {
$options['body'] = \GuzzleHttp\json_encode($options['json']);
unset($options['json']);
$options['_conditional']['Content-Type'] = 'application/json';
}
if (!empty($options['decode_content'])
@ -325,13 +337,10 @@ class Client implements ClientInterface
unset($options['body']);
}
if (!empty($options['auth'])) {
if (!empty($options['auth']) && is_array($options['auth'])) {
$value = $options['auth'];
$type = is_array($value)
? (isset($value[2]) ? strtolower($value[2]) : 'basic')
: $value;
$config['auth'] = $value;
switch (strtolower($type)) {
$type = isset($value[2]) ? strtolower($value[2]) : 'basic';
switch ($type) {
case 'basic':
$modify['set_headers']['Authorization'] = 'Basic '
. base64_encode("$value[0]:$value[1]");
@ -356,10 +365,12 @@ class Client implements ClientInterface
unset($options['query']);
}
if (isset($options['json'])) {
$modify['body'] = Psr7\stream_for(json_encode($options['json']));
$options['_conditional']['Content-Type'] = 'application/json';
unset($options['json']);
// Ensure that sink is not an invalid value.
if (isset($options['sink'])) {
// TODO: Add more sink validation?
if (is_bool($options['sink'])) {
throw new \InvalidArgumentException('sink must not be a boolean');
}
}
$request = Psr7\modify_request($request, $modify);

View file

@ -12,7 +12,7 @@ use Psr\Http\Message\UriInterface;
*/
interface ClientInterface
{
const VERSION = '6.1.0';
const VERSION = '6.2.1';
/**
* Send an HTTP request.
@ -44,7 +44,7 @@ interface ClientInterface
* 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 $method HTTP method.
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*

View file

@ -5,7 +5,7 @@ use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Cookie jar that stores cookies an an array
* Cookie jar that stores cookies as an array
*/
class CookieJar implements CookieJarInterface
{
@ -58,22 +58,10 @@ class CookieJar implements CookieJarInterface
}
/**
* Quote the cookie value if it is not already quoted and it contains
* problematic characters.
*
* @param string $value Value that may or may not need to be quoted
*
* @return string
* @deprecated
*/
public static function getCookieValue($value)
{
if (substr($value, 0, 1) !== '"' &&
substr($value, -1, 1) !== '"' &&
strpbrk($value, ';,=')
) {
$value = '"' . $value . '"';
}
return $value;
}
@ -82,7 +70,7 @@ class CookieJar implements CookieJarInterface
* that survives between requests.
*
* @param SetCookie $cookie Being evaluated.
* @param bool $allowSessionCookies If we should presist session cookies
* @param bool $allowSessionCookies If we should persist session cookies
* @return bool
*/
public static function shouldPersist(
@ -245,10 +233,10 @@ class CookieJar implements CookieJarInterface
if ($cookie->matchesPath($path) &&
$cookie->matchesDomain($host) &&
!$cookie->isExpired() &&
(!$cookie->getSecure() || $scheme == 'https')
(!$cookie->getSecure() || $scheme === 'https')
) {
$values[] = $cookie->getName() . '='
. self::getCookieValue($cookie->getValue());
. $cookie->getValue();
}
}

View file

@ -9,9 +9,9 @@ class FileCookieJar extends CookieJar
/** @var string filename */
private $filename;
/** @var bool Control whether to presist session cookies or not. */
/** @var bool Control whether to persist session cookies or not. */
private $storeSessionCookies;
/**
* Create a new FileCookieJar object
*
@ -55,7 +55,8 @@ class FileCookieJar extends CookieJar
}
}
if (false === file_put_contents($filename, json_encode($json))) {
$jsonStr = \GuzzleHttp\json_encode($json);
if (false === file_put_contents($filename, $jsonStr)) {
throw new \RuntimeException("Unable to save file {$filename}");
}
}
@ -73,9 +74,11 @@ class FileCookieJar extends CookieJar
$json = file_get_contents($filename);
if (false === $json) {
throw new \RuntimeException("Unable to load file {$filename}");
} elseif ($json === '') {
return;
}
$data = json_decode($json, true);
$data = \GuzzleHttp\json_decode($json, true);
if (is_array($data)) {
foreach (json_decode($json, true) as $cookie) {
$this->setCookie(new SetCookie($cookie));

View file

@ -9,7 +9,7 @@ class SessionCookieJar extends CookieJar
/** @var string session key */
private $sessionKey;
/** @var bool Control whether to presist session cookies or not. */
/** @var bool Control whether to persist session cookies or not. */
private $storeSessionCookies;
/**
@ -56,11 +56,10 @@ class SessionCookieJar extends CookieJar
*/
protected function load()
{
$cookieJar = isset($_SESSION[$this->sessionKey])
? $_SESSION[$this->sessionKey]
: null;
$data = json_decode($cookieJar, true);
if (!isset($_SESSION[$this->sessionKey])) {
return;
}
$data = json_decode($_SESSION[$this->sessionKey], true);
if (is_array($data)) {
foreach ($data as $cookie) {
$this->setCookie(new SetCookie($cookie));

View file

@ -86,8 +86,8 @@ class SetCookie
{
$str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
foreach ($this->data as $k => $v) {
if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) {
if ($k == 'Expires') {
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
if ($k === 'Expires') {
$str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
} else {
$str .= ($v === true ? $k : "{$k}={$v}") . '; ';
@ -307,7 +307,7 @@ class SetCookie
$cookiePath = $this->getPath();
// Match on exact matches or when path is the default empty "/"
if ($cookiePath == '/' || $cookiePath == $requestPath) {
if ($cookiePath === '/' || $cookiePath == $requestPath) {
return true;
}
@ -317,12 +317,12 @@ class SetCookie
}
// Match if the last character of the cookie-path is "/"
if (substr($cookiePath, -1, 1) == '/') {
if (substr($cookiePath, -1, 1) === '/') {
return true;
}
// Match if the first character not included in cookie path is "/"
return substr($requestPath, strlen($cookiePath), 1) == '/';
return substr($requestPath, strlen($cookiePath), 1) === '/';
}
/**

View file

@ -77,26 +77,70 @@ class RequestException extends TransferException
);
}
$level = floor($response->getStatusCode() / 100);
if ($level == '4') {
$label = 'Client error response';
$level = (int) floor($response->getStatusCode() / 100);
if ($level === 4) {
$label = 'Client error';
$className = __NAMESPACE__ . '\\ClientException';
} elseif ($level == '5') {
$label = 'Server error response';
} elseif ($level === 5) {
$label = 'Server error';
$className = __NAMESPACE__ . '\\ServerException';
} else {
$label = 'Unsuccessful response';
$label = 'Unsuccessful request';
$className = __CLASS__;
}
$message = $label . ' [url] ' . $request->getUri()
. ' [http method] ' . $request->getMethod()
. ' [status code] ' . $response->getStatusCode()
. ' [reason phrase] ' . $response->getReasonPhrase();
// Server Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated)
$message = sprintf(
'%s: `%s` resulted in a `%s` response',
$label,
$request->getMethod() . ' ' . $request->getUri(),
$response->getStatusCode() . ' ' . $response->getReasonPhrase()
);
$summary = static::getResponseBodySummary($response);
if ($summary !== null) {
$message .= ":\n{$summary}\n";
}
return new $className($message, $request, $response, $previous, $ctx);
}
/**
* Get a short summary of the response
*
* Will return `null` if the response is not printable.
*
* @param ResponseInterface $response
*
* @return string|null
*/
public static function getResponseBodySummary(ResponseInterface $response)
{
$body = $response->getBody();
if (!$body->isSeekable()) {
return null;
}
$size = $body->getSize();
$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;
}
/**
* Get the request that caused the exception
*

View file

@ -194,7 +194,7 @@ class CurlFactory implements CurlFactoryInterface
$conf = [
'_headers' => $easy->request->getHeaders(),
CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
CURLOPT_URL => (string) $easy->request->getUri(),
CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 150,
@ -265,6 +265,9 @@ class CurlFactory implements CurlFactoryInterface
$this->removeHeader('Content-Length', $conf);
}
$body = $request->getBody();
if ($body->isSeekable()) {
$body->rewind();
}
$conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
return $body->read($length);
};
@ -492,12 +495,14 @@ class CurlFactory implements CurlFactoryInterface
private function createHeaderFn(EasyHandle $easy)
{
if (!isset($easy->options['on_headers'])) {
$onHeaders = null;
} elseif (!is_callable($easy->options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
} else {
if (isset($easy->options['on_headers'])) {
$onHeaders = $easy->options['on_headers'];
if (!is_callable($onHeaders)) {
throw new \InvalidArgumentException('on_headers must be callable');
}
} else {
$onHeaders = null;
}
return function ($ch, $h) use (
@ -509,7 +514,7 @@ class CurlFactory implements CurlFactoryInterface
if ($value === '') {
$startingResponse = true;
$easy->createResponse();
if ($onHeaders) {
if ($onHeaders !== null) {
try {
$onHeaders($easy->response);
} catch (\Exception $e) {

View file

@ -192,6 +192,6 @@ class CurlMultiHandler
}
}
return max(0, $currentTime - $nextTime);
return max(0, $nextTime - $currentTime) * 1000000;
}
}

View file

@ -56,8 +56,13 @@ final class EasyHandle
if (!empty($this->options['decode_content'])
&& isset($normalizedKeys['content-encoding'])
) {
$headers['x-encoded-content-encoding']
= $headers[$normalizedKeys['content-encoding']];
unset($headers[$normalizedKeys['content-encoding']]);
if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length']
= $headers[$normalizedKeys['content-length']];
$bodyLength = (int) $this->sink->getSize();
if ($bodyLength) {
$headers[$normalizedKeys['content-length']] = $bodyLength;

View file

@ -27,7 +27,7 @@ class MockHandler implements \Countable
* @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable $onRejected Callback to invoke when the return value is rejected.
*
* @return MockHandler
* @return HandlerStack
*/
public static function createWithMiddleware(
array $queue = null,
@ -74,7 +74,7 @@ class MockHandler implements \Countable
$response = array_shift($this->queue);
if (is_callable($response)) {
$response = $response($request, $options);
$response = call_user_func($response, $request, $options);
}
$response = $response instanceof \Exception
@ -87,6 +87,19 @@ class MockHandler implements \Countable
if ($this->onFulfilled) {
call_user_func($this->onFulfilled, $value);
}
if (isset($options['sink'])) {
$contents = (string) $value->getBody();
$sink = $options['sink'];
if (is_resource($sink)) {
fwrite($sink, $contents);
} elseif (is_string($sink)) {
file_put_contents($sink, $contents);
} elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
$sink->write($contents);
}
}
return $value;
},
function ($reason) use ($request, $options) {

View file

@ -1,6 +1,7 @@
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\RequestInterface;
/**
@ -22,7 +23,7 @@ class Proxy
callable $sync
) {
return function (RequestInterface $request, array $options) use ($default, $sync) {
return empty($options['sync'])
return empty($options[RequestOptions::SYNCHRONOUS])
? $default($request, $options)
: $sync($request, $options);
};

View file

@ -105,7 +105,12 @@ class StreamHandler
$headers = \GuzzleHttp\headers_from_lines($hdrs);
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream);
$sink = $this->createSink($stream, $options);
$sink = $stream;
if (strcasecmp('HEAD', $request->getMethod())) {
$sink = $this->createSink($stream, $options);
}
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
if (isset($options['on_headers'])) {
@ -118,8 +123,14 @@ class StreamHandler
}
}
// Do not drain when the request is a HEAD request because they have
// no body.
if ($sink !== $stream) {
$this->drain($stream, $sink);
$this->drain(
$stream,
$sink,
$response->getHeaderLine('Content-Length')
);
}
$this->invokeStats($options, $request, $startTime, $response, null);
@ -138,7 +149,7 @@ class StreamHandler
: fopen('php://temp', 'r+');
return is_string($sink)
? new Psr7\Stream(Psr7\try_fopen($sink, 'r+'))
? new Psr7\LazyOpenStream($sink, 'w+')
: Psr7\stream_for($sink);
}
@ -149,16 +160,21 @@ class StreamHandler
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
if (isset($normalizedKeys['content-encoding'])) {
$encoding = $headers[$normalizedKeys['content-encoding']];
if ($encoding[0] == 'gzip' || $encoding[0] == 'deflate') {
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
$stream = new Psr7\InflateStream(
Psr7\stream_for($stream)
);
$headers['x-encoded-content-encoding']
= $headers[$normalizedKeys['content-encoding']];
// Remove content-encoding header
unset($headers[$normalizedKeys['content-encoding']]);
// Fix content-length header
if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length']
= $headers[$normalizedKeys['content-length']];
$length = (int) $stream->getSize();
if ($length == 0) {
if ($length === 0) {
unset($headers[$normalizedKeys['content-length']]);
} else {
$headers[$normalizedKeys['content-length']] = [$length];
@ -176,13 +192,27 @@ class StreamHandler
*
* @param StreamInterface $source
* @param StreamInterface $sink
* @param string $contentLength Header specifying the amount of
* data to read.
*
* @return StreamInterface
* @throws \RuntimeException when the sink option is invalid.
*/
private function drain(StreamInterface $source, StreamInterface $sink)
{
Psr7\copy_to_stream($source, $sink);
private function drain(
StreamInterface $source,
StreamInterface $sink,
$contentLength
) {
// If a content-length header is provided, then stop reading once
// that number of bytes has been read. This can prevent infinitely
// reading from a stream when dealing with servers that do not honor
// Connection: Close headers.
Psr7\copy_to_stream(
$source,
$sink,
strlen($contentLength) > 0 ? (int) $contentLength : -1
);
$sink->seek(0);
$source->close();
@ -279,7 +309,7 @@ class StreamHandler
return $this->createResource(
function () use ($request, &$http_response_header, $context) {
$resource = fopen($request->getUri(), 'r', null, $context);
$resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context);
$this->lastHeaders = $http_response_header;
return $resource;
}
@ -341,7 +371,9 @@ class StreamHandler
private function add_timeout(RequestInterface $request, &$options, $value, &$params)
{
$options['http']['timeout'] = $value;
if ($value > 0) {
$options['http']['timeout'] = $value;
}
}
private function add_verify(RequestInterface $request, &$options, $value, &$params)
@ -359,12 +391,14 @@ class StreamHandler
}
} elseif ($value === false) {
$options['ssl']['verify_peer'] = false;
$options['ssl']['verify_peer_name'] = false;
return;
} else {
throw new \InvalidArgumentException('Invalid verify request option');
}
$options['ssl']['verify_peer'] = true;
$options['ssl']['verify_peer_name'] = true;
$options['ssl']['allow_self_signed'] = false;
}
@ -416,7 +450,7 @@ class StreamHandler
'bytes_transferred', 'bytes_max'];
$value = \GuzzleHttp\debug_resource($value);
$ident = $request->getMethod() . ' ' . $request->getUri();
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
$this->addNotification(
$params,
function () use ($ident, $value, $map, $args) {

View file

@ -62,11 +62,8 @@ class HandlerStack
*/
public function __invoke(RequestInterface $request, array $options)
{
if (!$this->cached) {
$this->cached = $this->resolve();
}
$handler = $this->resolve();
$handler = $this->cached;
return $handler($request, $options);
}
@ -193,15 +190,19 @@ class HandlerStack
*/
public function resolve()
{
if (!($prev = $this->handler)) {
throw new \LogicException('No handler has been specified');
if (!$this->cached) {
if (!($prev = $this->handler)) {
throw new \LogicException('No handler has been specified');
}
foreach (array_reverse($this->stack) as $fn) {
$prev = $fn[0]($prev);
}
$this->cached = $prev;
}
foreach (array_reverse($this->stack) as $fn) {
$prev = $fn[0]($prev);
}
return $prev;
return $this->cached;
}
/**

View file

@ -2,9 +2,7 @@
namespace GuzzleHttp;
use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
@ -64,9 +62,7 @@ final class Middleware
if ($code < 400) {
return $response;
}
throw $code > 499
? new ServerException("Server error: $code", $request, $response)
: new ClientException("Client error: $code", $request, $response);
throw RequestException::create($request, $response);
}
);
};
@ -79,9 +75,14 @@ final class Middleware
* @param array $container Container to hold the history (by reference).
*
* @return callable Returns a function that accepts the next handler.
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
*/
public static function history(array &$container)
public static function history(&$container)
{
if (!is_array($container) && !$container instanceof \ArrayAccess) {
throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
}
return function (callable $handler) use (&$container) {
return function ($request, array $options) use ($handler, &$container) {
return $handler($request, $options)->then(

View file

@ -52,11 +52,11 @@ class Pool implements PromisorInterface
$iterable = \GuzzleHttp\Promise\iter_for($requests);
$requests = function () use ($iterable, $client, $opts) {
foreach ($iterable as $rfn) {
foreach ($iterable as $key => $rfn) {
if ($rfn instanceof RequestInterface) {
yield $client->sendAsync($rfn, $opts);
yield $key => $client->sendAsync($rfn, $opts);
} elseif (is_callable($rfn)) {
yield $rfn($opts);
yield $key => $rfn($opts);
} else {
throw new \InvalidArgumentException('Each value yielded by '
. 'the iterator must be a Psr7\Http\Message\RequestInterface '

View file

@ -15,25 +15,25 @@ class UriTemplate
private $variables;
/** @var array Hash for quick operator lookups */
private static $operatorHash = array(
'' => array('prefix' => '', 'joiner' => ',', 'query' => false),
'+' => array('prefix' => '', 'joiner' => ',', 'query' => false),
'#' => array('prefix' => '#', 'joiner' => ',', 'query' => false),
'.' => array('prefix' => '.', 'joiner' => '.', 'query' => false),
'/' => array('prefix' => '/', 'joiner' => '/', 'query' => false),
';' => array('prefix' => ';', 'joiner' => ';', 'query' => true),
'?' => array('prefix' => '?', 'joiner' => '&', 'query' => true),
'&' => array('prefix' => '&', 'joiner' => '&', 'query' => true)
);
private static $operatorHash = [
'' => ['prefix' => '', 'joiner' => ',', 'query' => false],
'+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
'#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
'.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
'/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
'?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
'&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
];
/** @var array Delimiters */
private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$',
'&', '\'', '(', ')', '*', '+', ',', ';', '=');
private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
'&', '\'', '(', ')', '*', '+', ',', ';', '='];
/** @var array Percent encoded delimiters */
private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D',
private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
'%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
'%3B', '%3D');
'%3B', '%3D'];
public function expand($template, array $variables)
{
@ -60,7 +60,7 @@ class UriTemplate
*/
private function parseExpression($expression)
{
$result = array();
$result = [];
if (isset(self::$operatorHash[$expression[0]])) {
$result['operator'] = $expression[0];
@ -71,12 +71,12 @@ class UriTemplate
foreach (explode(',', $expression) as $value) {
$value = trim($value);
$varspec = array();
$varspec = [];
if ($colonPos = strpos($value, ':')) {
$varspec['value'] = substr($value, 0, $colonPos);
$varspec['modifier'] = ':';
$varspec['position'] = (int) substr($value, $colonPos + 1);
} elseif (substr($value, -1) == '*') {
} elseif (substr($value, -1) === '*') {
$varspec['modifier'] = '*';
$varspec['value'] = substr($value, 0, -1);
} else {
@ -98,9 +98,9 @@ class UriTemplate
*/
private function expandMatch(array $matches)
{
static $rfc1738to3986 = array('+' => '%20', '%7e' => '~');
static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
$replacements = array();
$replacements = [];
$parsed = self::parseExpression($matches[1]);
$prefix = self::$operatorHash[$parsed['operator']]['prefix'];
$joiner = self::$operatorHash[$parsed['operator']]['joiner'];
@ -119,7 +119,7 @@ class UriTemplate
if (is_array($variable)) {
$isAssoc = $this->isAssoc($variable);
$kvp = array();
$kvp = [];
foreach ($variable as $key => $var) {
if ($isAssoc) {
@ -131,14 +131,14 @@ class UriTemplate
if (!$isNestedArray) {
$var = rawurlencode($var);
if ($parsed['operator'] == '+' ||
$parsed['operator'] == '#'
if ($parsed['operator'] === '+' ||
$parsed['operator'] === '#'
) {
$var = $this->decodeReserved($var);
}
}
if ($value['modifier'] == '*') {
if ($value['modifier'] === '*') {
if ($isAssoc) {
if ($isNestedArray) {
// Nested arrays must allow for deeply nested
@ -160,7 +160,7 @@ class UriTemplate
if (empty($variable)) {
$actuallyUseQuery = false;
} elseif ($value['modifier'] == '*') {
} elseif ($value['modifier'] === '*') {
$expanded = implode($joiner, $kvp);
if ($isAssoc) {
// Don't prepend the value name when using the explode
@ -181,17 +181,17 @@ class UriTemplate
}
} else {
if ($value['modifier'] == ':') {
if ($value['modifier'] === ':') {
$variable = substr($variable, 0, $value['position']);
}
$expanded = rawurlencode($variable);
if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
$expanded = $this->decodeReserved($expanded);
}
}
if ($actuallyUseQuery) {
if (!$expanded && $joiner != '&') {
if (!$expanded && $joiner !== '&') {
$expanded = $value['value'];
} else {
$expanded = $value['value'] . '=' . $expanded;

View file

@ -5,7 +5,6 @@ use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler;
use Psr\Http\Message\StreamInterface;
/**
* Expands a URI template
@ -104,8 +103,12 @@ function debug_resource($value = null)
function choose_handler()
{
$handler = null;
if (extension_loaded('curl')) {
if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
} elseif (function_exists('curl_exec')) {
$handler = new CurlHandler();
} elseif (function_exists('curl_multi_exec')) {
$handler = new CurlMultiHandler();
}
if (ini_get('allow_url_fopen')) {
@ -278,3 +281,49 @@ function is_host_in_noproxy($host, array $noProxyArray)
return false;
}
/**
* Wrapper for json_decode that throws when an error occurs.
*
* @param string $json JSON data to parse
* @param bool $assoc When true, returned objects will be converted
* into associative arrays.
* @param int $depth User specified recursion depth.
* @param int $options Bitmask of JSON decode options.
*
* @return mixed
* @throws \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(
'json_decode error: ' . json_last_error_msg());
}
return $data;
}
/**
* Wrapper for JSON encoding that throws when an error occurs.
*
* @param mixed $value The value being encoded
* @param int $options JSON encode option bitmask
* @param int $depth Set the maximum depth. Must be greater than zero.
*
* @return string
* @throws \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(
'json_encode error: ' . json_last_error_msg());
}
return $json;
}

View file

@ -1,5 +1,21 @@
# CHANGELOG
## 1.2.0 - 2016-05-18
* Update to now catch `\Throwable` on PHP 7+
## 1.1.0 - 2016-03-07
* Update EachPromise to prevent recurring on a iterator when advancing, as this
could trigger fatal generator errors.
* Update Promise to allow recursive waiting without unwrapping exceptions.
## 1.0.3 - 2015-10-15
* Update EachPromise to immediately resolve when the underlying promise iterator
is empty. Previously, such a promise would throw an exception when its `wait`
function was called.
## 1.0.2 - 2015-05-15
* Conditionally require functions.php.

View file

@ -208,7 +208,7 @@ of the promise is called.
```php
$promise = new Promise(function () use (&$promise) {
$promise->deliver('foo');
$promise->resolve('foo');
});
// Calling wait will return the value of the promise.
@ -227,11 +227,11 @@ $promise->wait(); // throws the exception.
```
Calling `wait` on a promise that has been fulfilled will not trigger the wait
function. It will simply return the previously delivered value.
function. It will simply return the previously resolved value.
```php
$promise = new Promise(function () { die('this is not called!'); });
$promise->deliver('foo');
$promise->resolve('foo');
echo $promise->wait(); // outputs "foo"
```
@ -268,7 +268,7 @@ $promise->reject('foo');
$promise->wait(false);
```
When unwrapping a promise, the delivered value of the promise will be waited
When unwrapping a promise, the resolved value of the promise will be waited
upon until the unwrapped value is not a promise. This means that if you resolve
promise A with a promise B and unwrap promise A, the value returned by the
wait function will be the value delivered to promise B.
@ -496,6 +496,6 @@ deferred, it is a small price to pay for keeping the stack size constant.
$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->deliver('foo');
$promise->resolve('foo');
// prints "foo"
```

View file

@ -24,6 +24,9 @@ class EachPromise implements PromisorInterface
/** @var Promise */
private $aggregate;
/** @var bool */
private $mutex;
/**
* Configuration hash can include the following key value pairs:
*
@ -72,6 +75,8 @@ class EachPromise implements PromisorInterface
$this->createPromise();
$this->iterable->rewind();
$this->refillPending();
} catch (\Throwable $e) {
$this->aggregate->reject($e);
} catch (\Exception $e) {
$this->aggregate->reject($e);
}
@ -81,8 +86,14 @@ class EachPromise implements PromisorInterface
private function createPromise()
{
$this->mutex = false;
$this->aggregate = new Promise(function () {
reset($this->pending);
if (empty($this->pending) && !$this->iterable->valid()) {
$this->aggregate->resolve(null);
return;
}
// Consume a potentially fluctuating list of promises while
// ensuring that indexes are maintained (precluding array_shift).
while ($promise = current($this->pending)) {
@ -164,11 +175,25 @@ class EachPromise implements PromisorInterface
private function advanceIterator()
{
// Place a lock on the iterator so that we ensure to not recurse,
// preventing fatal generator errors.
if ($this->mutex) {
return false;
}
$this->mutex = true;
try {
$this->iterable->next();
$this->mutex = false;
return true;
} catch (\Throwable $e) {
$this->aggregate->reject($e);
$this->mutex = false;
return false;
} catch (\Exception $e) {
$this->aggregate->reject($e);
$this->mutex = false;
return false;
}
}
@ -181,9 +206,11 @@ class EachPromise implements PromisorInterface
}
unset($this->pending[$idx]);
$this->advanceIterator();
if (!$this->checkIfFinished()) {
// Only refill pending promises if we are not locked, preventing the
// EachPromise to recursively invoke the provided iterator, which
// cause a fatal error: "Cannot resume an already running generator"
if ($this->advanceIterator() && !$this->checkIfFinished()) {
// Add more pending promises if possible.
$this->refillPending();
}

View file

@ -37,6 +37,8 @@ class FulfilledPromise implements PromiseInterface
if ($p->getState() === self::PENDING) {
try {
$p->resolve($onFulfilled($value));
} catch (\Throwable $e) {
$p->reject($e);
} catch (\Exception $e) {
$p->reject($e);
}

View file

@ -61,17 +61,19 @@ class Promise implements PromiseInterface
{
$this->waitIfPending();
if (!$unwrap) {
return null;
}
$inner = $this->result instanceof PromiseInterface
? $this->result->wait($unwrap)
: $this->result;
if ($this->result instanceof PromiseInterface) {
return $this->result->wait($unwrap);
} elseif ($this->state === self::FULFILLED) {
return $this->result;
} else {
// It's rejected so "unwrap" and throw an exception.
throw exception_for($this->result);
if ($unwrap) {
if ($this->result instanceof PromiseInterface
|| $this->state === self::FULFILLED
) {
return $inner;
} else {
// It's rejected so "unwrap" and throw an exception.
throw exception_for($inner);
}
}
}
@ -93,6 +95,8 @@ class Promise implements PromiseInterface
$this->cancelFn = null;
try {
$fn();
} catch (\Throwable $e) {
$this->reject($e);
} catch (\Exception $e) {
$this->reject($e);
}
@ -204,6 +208,8 @@ class Promise implements PromiseInterface
// Forward rejections down the chain.
$promise->reject($value);
}
} catch (\Throwable $reason) {
$promise->reject($reason);
} catch (\Exception $reason) {
$promise->reject($reason);
}
@ -257,11 +263,10 @@ class Promise implements PromiseInterface
$this->waitList = null;
foreach ($waitList as $result) {
descend:
$result->waitIfPending();
if ($result->result instanceof Promise) {
while ($result->result instanceof Promise) {
$result = $result->result;
goto descend;
$result->waitIfPending();
}
}
}

View file

@ -38,6 +38,9 @@ class RejectedPromise implements PromiseInterface
try {
// Return a resolved promise if onRejected does not throw.
$p->resolve($onRejected($reason));
} catch (\Throwable $e) {
// onRejected threw, so return a rejected promise.
$p->reject($e);
} catch (\Exception $e) {
// onRejected threw, so return a rejected promise.
$p->reject($e);

View file

@ -56,6 +56,7 @@ class TaskQueue
*/
public function run()
{
/** @var callable $task */
while ($task = array_shift($this->queue)) {
$task();
}

View file

@ -42,6 +42,8 @@ function task(callable $task)
$queue->add(function () use ($task, $promise) {
try {
$promise->resolve($task());
} catch (\Throwable $e) {
$promise->reject($e);
} catch (\Exception $e) {
$promise->reject($e);
}
@ -97,11 +99,11 @@ function rejection_for($reason)
*
* @param mixed $reason
*
* @return \Exception
* @return \Exception|\Throwable
*/
function exception_for($reason)
{
return $reason instanceof \Exception
return $reason instanceof \Exception || $reason instanceof \Throwable
? $reason
: new RejectionException($reason);
}
@ -146,9 +148,11 @@ function inspect(PromiseInterface $promise)
'value' => $promise->wait()
];
} catch (RejectionException $e) {
return ['state' => 'rejected', 'reason' => $e->getReason()];
return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
} catch (\Throwable $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
} catch (\Exception $e) {
return ['state' => 'rejected', 'reason' => $e];
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
}
}
@ -184,6 +188,7 @@ function inspect_all($promises)
*
* @return array
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
*/
function unwrap($promises)
{
@ -304,10 +309,10 @@ function settle($promises)
return each(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = ['state' => 'fulfilled', 'value' => $value];
$results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
function ($reason, $idx) use (&$results) {
$results[$idx] = ['state' => 'rejected', 'reason' => $reason];
$results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
)->then(function () use (&$results) {
ksort($results);

View file

@ -1,5 +1,47 @@
# CHANGELOG
## 1.3.1 - 2016-06-25
* Fix `Uri::__toString` for network path references, e.g. `//example.org`.
* Fix missing lowercase normalization for host.
* Fix handling of URI components in case they are `'0'` in a lot of places,
e.g. as a user info password.
* Fix `Uri::withAddedHeader` to correctly merge headers with different case.
* Fix trimming of header values in `Uri::withAddedHeader`. Header values may
be surrounded by whitespace which should be ignored according to RFC 7230
Section 3.2.4. This does not apply to header names.
* Fix `Uri::withAddedHeader` with an array of header values.
* Fix `Uri::resolve` when base path has no slash and handling of fragment.
* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the
key/value both in encoded as well as decoded form to those methods. This is
consistent with withPath, withQuery etc.
* Fix `ServerRequest::withoutAttribute` when attribute value is null.
## 1.3.0 - 2016-04-13
* Added remaining interfaces needed for full PSR7 compatibility
(ServerRequestInterface, UploadedFileInterface, etc.).
* Added support for stream_for from scalars.
* Can now extend Uri.
* Fixed a bug in validating request methods by making it more permissive.
## 1.2.3 - 2016-02-18
* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
streams, which can sometimes return fewer bytes than requested with `fread`.
* Fixed handling of gzipped responses with FNAME headers.
## 1.2.2 - 2016-01-22
* Added support for URIs without any authority.
* Added support for HTTP 451 'Unavailable For Legal Reasons.'
* Added support for using '0' as a filename.
* Added support for including non-standard ports in Host headers.
## 1.2.1 - 2015-11-02
* Now supporting negative offsets when seeking to SEEK_END.
## 1.2.0 - 2015-08-15
* Body as `"0"` is now properly added to a response.

View file

@ -9,5 +9,21 @@ coverage:
view-coverage:
open artifacts/coverage/index.html
check-tag:
$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
tag: check-tag
@echo Tagging $(TAG)
chag update $(TAG)
git commit -a -m '$(TAG) release'
chag tag
@echo "Release has been created. Push using 'make release'"
@echo "Changes made in the release commit"
git diff HEAD~1 HEAD
release: check-tag
git push origin master
git push origin $(TAG)
clean:
rm -rf artifacts/*

View file

@ -1,9 +1,11 @@
# PSR-7 Message Implementation
This repository contains a partial [PSR-7](http://www.php-fig.org/psr/psr-7/)
This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
message implementation, several stream decorators, and some helpful
functionality like query string parsing. Currently missing
ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
functionality like query string parsing.
[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7)
# Stream implementation
@ -25,9 +27,9 @@ $a = Psr7\stream_for('abc, ');
$b = Psr7\stream_for('123.');
$composed = new Psr7\AppendStream([$a, $b]);
$composed->addStream(Psr7\stream_for(' Above all listen to me').
$composed->addStream(Psr7\stream_for(' Above all listen to me'));
echo $composed(); // abc, 123. Above all listen to me.
echo $composed; // abc, 123. Above all listen to me.
```
@ -35,7 +37,7 @@ echo $composed(); // abc, 123. Above all listen to me.
`GuzzleHttp\Psr7\BufferStream`
Provides a buffer stream that can be written to to fill a buffer, and read
Provides a buffer stream that can be written to fill a buffer, and read
from to remove bytes from the buffer.
This stream returns a "hwm" metadata value that tells upstream consumers
@ -92,7 +94,7 @@ $stream = Psr7\stream_for();
// Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10);
$stream->write('01234567890123456789');
$dropping->write('01234567890123456789');
echo $stream; // 0123456789
```
@ -103,7 +105,7 @@ echo $stream; // 0123456789
Compose stream implementations based on a hash of functions.
Allows for easy testing and extension of a provided stream without needing to
Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.
```php
@ -498,7 +500,7 @@ an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
Build a query string from an array of key value pairs.
This function can use the return value of parseQuery() to build a query string.
This function can use the return value of parse_query() to build a query string.
This function does not modify the provided keys when an array is encountered
(like http_build_query would).
@ -524,7 +526,7 @@ The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.
## `GuzzleHttp\Psr7\Uri::removeDotSegments`
`public static function removeDotSegments($path) -> UriInterface`
`public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path.
@ -533,7 +535,7 @@ See http://tools.ietf.org/html/rfc3986#section-5.2.4
## `GuzzleHttp\Psr7\Uri::resolve`
`public static function resolve(UriInterface $base, $rel) -> UriInterface`
`public static function resolve(UriInterface $base, $rel): UriInterface`
Resolve a base URI with a relative URI and return a new URI.
@ -542,39 +544,26 @@ See http://tools.ietf.org/html/rfc3986#section-5
## `GuzzleHttp\Psr7\Uri::withQueryValue`
`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
Create a new URI with a specific query string value.
Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::withoutQueryValue`
`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
Create a new URI with a specific query string value removed.
Any existing query string values that exactly match the provided key are
removed.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::fromParts`
`public static function fromParts(array $parts) -> UriInterface`
`public static function fromParts(array $parts): UriInterface`
Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
# Not Implemented
A few aspects of PSR-7 are not implemented in this project. A pull request for
any of these features is welcome:
- `Psr\Http\Message\ServerRequestInterface`
- `Psr\Http\Message\UploadedFileInterface`

View file

@ -29,7 +29,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.4-dev"
}
}
}

View file

@ -52,8 +52,7 @@ class CachingStream implements StreamInterface
if ($size === null) {
$size = $this->cacheEntireStream();
}
// Because 0 is the first byte, we seek to size - 1.
$byte = $size - 1 - $offset;
$byte = $size + $offset;
} else {
throw new \InvalidArgumentException('Invalid whence');
}
@ -61,9 +60,12 @@ class CachingStream implements StreamInterface
$diff = $byte - $this->stream->getSize();
if ($diff > 0) {
// If the seek byte is greater the number of read bytes, then read
// the difference of bytes to cache the bytes and inherently seek.
$this->read($diff);
// Read the remoteStream until we have read in at least the amount
// of bytes requested, or we reach the end of the file.
while ($diff > 0 && !$this->remoteStream->eof()) {
$this->read($diff);
$diff = $byte - $this->stream->getSize();
}
} else {
// We can just do a normal seek since we've already seen this byte.
$this->stream->seek($byte);

View file

@ -20,10 +20,33 @@ class InflateStream implements StreamInterface
public function __construct(StreamInterface $stream)
{
// Skip the first 10 bytes
$stream = new LimitStream($stream, -1, 10);
// read the first 10 bytes, ie. gzip header
$header = $stream->read(10);
$filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
// Skip the header, that is 10 + length of filename + 1 (nil) bytes
$stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
$resource = StreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource);
}
/**
* @param StreamInterface $stream
* @param $header
* @return int
*/
private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
{
$filename_header_length = 0;
if (substr(bin2hex($header), 6, 2) === '08') {
// we have a filename, read until nil
$filename_header_length = 1;
while ($stream->read(1) !== chr(0)) {
$filename_header_length++;
}
}
return $filename_header_length;
}
}

View file

@ -8,11 +8,11 @@ use Psr\Http\Message\StreamInterface;
*/
trait MessageTrait
{
/** @var array Cached HTTP header collection with lowercase key to values */
/** @var array Map of all registered headers, as original name => array of values */
private $headers = [];
/** @var array Actual key to list of values per header. */
private $headerLines = [];
/** @var array Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string */
private $protocol = '1.1';
@ -38,18 +38,25 @@ trait MessageTrait
public function getHeaders()
{
return $this->headerLines;
return $this->headers;
}
public function hasHeader($header)
{
return isset($this->headers[strtolower($header)]);
return isset($this->headerNames[strtolower($header)]);
}
public function getHeader($header)
{
$name = strtolower($header);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
$header = strtolower($header);
if (!isset($this->headerNames[$header])) {
return [];
}
$header = $this->headerNames[$header];
return $this->headers[$header];
}
public function getHeaderLine($header)
@ -59,59 +66,56 @@ trait MessageTrait
public function withHeader($header, $value)
{
$new = clone $this;
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) {
$new->headers[$name] = [trim($value)];
} else {
$new->headers[$name] = $value;
foreach ($new->headers[$name] as &$v) {
$v = trim($v);
}
$value = [$value];
}
// Remove the header lines.
foreach (array_keys($new->headerLines) as $key) {
if (strtolower($key) === $name) {
unset($new->headerLines[$key]);
}
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
// Add the header line.
$new->headerLines[$header] = $new->headers[$name];
$new = clone $this;
if (isset($new->headerNames[$normalized])) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
return $new;
}
public function withAddedHeader($header, $value)
{
if (!$this->hasHeader($header)) {
return $this->withHeader($header, $value);
if (!is_array($value)) {
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
$new = clone $this;
$new->headers[strtolower($header)][] = $value;
$new->headerLines[$header][] = $value;
if (isset($new->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$new->headers[$header] = array_merge($this->headers[$header], $value);
} else {
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
}
return $new;
}
public function withoutHeader($header)
{
if (!$this->hasHeader($header)) {
$normalized = strtolower($header);
if (!isset($this->headerNames[$normalized])) {
return $this;
}
$new = clone $this;
$name = strtolower($header);
unset($new->headers[$name]);
$header = $this->headerNames[$normalized];
foreach (array_keys($new->headerLines) as $key) {
if (strtolower($key) === $name) {
unset($new->headerLines[$key]);
}
}
$new = clone $this;
unset($new->headers[$header], $new->headerNames[$normalized]);
return $new;
}
@ -138,21 +142,42 @@ trait MessageTrait
private function setHeaders(array $headers)
{
$this->headerLines = $this->headers = [];
$this->headerNames = $this->headers = [];
foreach ($headers as $header => $value) {
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) {
$value = trim($value);
$this->headers[$name][] = $value;
$this->headerLines[$header][] = $value;
$value = [$value];
}
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
if (isset($this->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$this->headers[$header] = array_merge($this->headers[$header], $value);
} else {
foreach ($value as $v) {
$v = trim($v);
$this->headers[$name][] = $v;
$this->headerLines[$header][] = $v;
}
$this->headerNames[$normalized] = $header;
$this->headers[$header] = $value;
}
}
}
/**
* Trims whitespace from the header values.
*
* Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
*
* header-field = field-name ":" OWS field-value OWS
* OWS = *( SP / HTAB )
*
* @param string[] $values Header values
*
* @return string[] Trimmed header values
*
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
*/
private function trimHeaderValues(array $values)
{
return array_map(function ($value) {
return trim($value, " \t");
}, $values);
}
}

View file

@ -113,7 +113,7 @@ class MultipartStream implements StreamInterface
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');
if (!$disposition) {
$headers['Content-Disposition'] = $filename
$headers['Content-Disposition'] = ($filename === '0' || $filename)
? sprintf('form-data; name="%s"; filename="%s"',
$name,
basename($filename))
@ -130,7 +130,7 @@ class MultipartStream implements StreamInterface
// Set a default Content-Type if one was not supplied
$type = $this->getHeader($headers, 'content-type');
if (!$type && $filename) {
if (!$type && ($filename === '0' || $filename)) {
if ($type = mimetype_from_filename($filename)) {
$headers['Content-Type'] = $type;
}

View file

@ -11,9 +11,7 @@ use Psr\Http\Message\UriInterface;
*/
class Request implements RequestInterface
{
use MessageTrait {
withHeader as protected withParentHeader;
}
use MessageTrait;
/** @var string */
private $method;
@ -25,40 +23,33 @@ class Request implements RequestInterface
private $uri;
/**
* @param null|string $method HTTP method for the request.
* @param null|string $uri URI for the request.
* @param array $headers Headers for the message.
* @param string|resource|StreamInterface $body Message body.
* @param string $protocolVersion HTTP protocol version.
*
* @throws InvalidArgumentException for an invalid URI
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
*/
public function __construct(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
$version = '1.1'
) {
if (is_string($uri)) {
if (!($uri instanceof UriInterface)) {
$uri = new Uri($uri);
} elseif (!($uri instanceof UriInterface)) {
throw new \InvalidArgumentException(
'URI must be a string or Psr\Http\Message\UriInterface'
);
}
$this->method = strtoupper($method);
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $protocolVersion;
$this->protocol = $version;
$host = $uri->getHost();
if ($host && !$this->hasHeader('Host')) {
$this->updateHostFromUri($host);
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
if ($body) {
if ($body !== '' && $body !== null) {
$this->stream = stream_for($body);
}
}
@ -70,10 +61,10 @@ class Request implements RequestInterface
}
$target = $this->uri->getPath();
if ($target == null) {
if ($target == '') {
$target = '/';
}
if ($this->uri->getQuery()) {
if ($this->uri->getQuery() != '') {
$target .= '?' . $this->uri->getQuery();
}
@ -120,30 +111,32 @@ class Request implements RequestInterface
$new->uri = $uri;
if (!$preserveHost) {
if ($host = $uri->getHost()) {
$new->updateHostFromUri($host);
}
$new->updateHostFromUri();
}
return $new;
}
public function withHeader($header, $value)
private function updateHostFromUri()
{
/** @var Request $newInstance */
$newInstance = $this->withParentHeader($header, $value);
return $newInstance;
}
$host = $this->uri->getHost();
private function updateHostFromUri($host)
{
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
if ($port = $this->uri->getPort()) {
if ($host == '') {
return;
}
if (($port = $this->uri->getPort()) !== null) {
$host .= ':' . $port;
}
$this->headerLines = ['Host' => [$host]] + $this->headerLines;
$this->headers = ['host' => [$host]] + $this->headers;
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
$header = 'Host';
$this->headerNames['host'] = 'Host';
}
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
}
}

View file

@ -59,6 +59,7 @@ class Response implements ResponseInterface
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
@ -71,18 +72,18 @@ class Response implements ResponseInterface
511 => 'Network Authentication Required',
];
/** @var null|string */
/** @var string */
private $reasonPhrase = '';
/** @var int */
private $statusCode = 200;
/**
* @param int $status Status code for the response, if any.
* @param array $headers Headers for the response, if any.
* @param mixed $body Stream body.
* @param string $version Protocol version.
* @param string $reason Reason phrase (a default will be used if possible).
* @param int $status Status code
* @param array $headers Response headers
* @param string|null|resource|StreamInterface $body Response body
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/
public function __construct(
$status = 200,
@ -93,12 +94,12 @@ class Response implements ResponseInterface
) {
$this->statusCode = (int) $status;
if ($body !== null) {
if ($body !== '' && $body !== null) {
$this->stream = stream_for($body);
}
$this->setHeaders($headers);
if (!$reason && isset(self::$phrases[$this->statusCode])) {
if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status];
} else {
$this->reasonPhrase = (string) $reason;
@ -121,7 +122,7 @@ class Response implements ResponseInterface
{
$new = clone $this;
$new->statusCode = (int) $code;
if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) {
if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
$reasonPhrase = self::$phrases[$new->statusCode];
}
$new->reasonPhrase = $reasonPhrase;

View file

@ -0,0 +1,346 @@
<?php
namespace GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
/**
* Server-side HTTP request
*
* Extends the Request definition to add methods for accessing incoming data,
* specifically server parameters, cookies, matched path parameters, query
* string arguments, body parameters, and upload file information.
*
* "Attributes" are discovered via decomposing the request (and usually
* specifically the URI path), and typically will be injected by the application.
*
* Requests are considered immutable; all methods that might change state are
* implemented such that they retain the internal state of the current
* message and return a new instance that contains the changed state.
*/
class ServerRequest extends Request implements ServerRequestInterface
{
/**
* @var array
*/
private $attributes = [];
/**
* @var array
*/
private $cookieParams = [];
/**
* @var null|array|object
*/
private $parsedBody;
/**
* @var array
*/
private $queryParams = [];
/**
* @var array
*/
private $serverParams;
/**
* @var array
*/
private $uploadedFiles = [];
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|null|resource|StreamInterface $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
*/
public function __construct(
$method,
$uri,
array $headers = [],
$body = null,
$version = '1.1',
array $serverParams = []
) {
$this->serverParams = $serverParams;
parent::__construct($method, $uri, $headers, $body, $version);
}
/**
* Return an UploadedFile instance array.
*
* @param array $files A array which respect $_FILES structure
* @throws InvalidArgumentException for unrecognized values
* @return array
*/
public static function normalizeFiles(array $files)
{
$normalized = [];
foreach ($files as $key => $value) {
if ($value instanceof UploadedFileInterface) {
$normalized[$key] = $value;
} elseif (is_array($value) && isset($value['tmp_name'])) {
$normalized[$key] = self::createUploadedFileFromSpec($value);
} elseif (is_array($value)) {
$normalized[$key] = self::normalizeFiles($value);
continue;
} else {
throw new InvalidArgumentException('Invalid value in files specification');
}
}
return $normalized;
}
/**
* Create and return an UploadedFile instance from a $_FILES specification.
*
* If the specification represents an array of values, this method will
* delegate to normalizeNestedFileSpec() and return that return value.
*
* @param array $value $_FILES struct
* @return array|UploadedFileInterface
*/
private static function createUploadedFileFromSpec(array $value)
{
if (is_array($value['tmp_name'])) {
return self::normalizeNestedFileSpec($value);
}
return new UploadedFile(
$value['tmp_name'],
(int) $value['size'],
(int) $value['error'],
$value['name'],
$value['type']
);
}
/**
* Normalize an array of file specifications.
*
* Loops through all nested files and returns a normalized array of
* UploadedFileInterface instances.
*
* @param array $files
* @return UploadedFileInterface[]
*/
private static function normalizeNestedFileSpec(array $files = [])
{
$normalizedFiles = [];
foreach (array_keys($files['tmp_name']) as $key) {
$spec = [
'tmp_name' => $files['tmp_name'][$key],
'size' => $files['size'][$key],
'error' => $files['error'][$key],
'name' => $files['name'][$key],
'type' => $files['type'][$key],
];
$normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
}
return $normalizedFiles;
}
/**
* Return a ServerRequest populated with superglobals:
* $_GET
* $_POST
* $_COOKIE
* $_FILES
* $_SERVER
*
* @return ServerRequestInterface
*/
public static function fromGlobals()
{
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
$headers = function_exists('getallheaders') ? getallheaders() : [];
$uri = self::getUriFromGlobals();
$body = 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);
return $serverRequest
->withCookieParams($_COOKIE)
->withQueryParams($_GET)
->withParsedBody($_POST)
->withUploadedFiles(self::normalizeFiles($_FILES));
}
/**
* Get a Uri populated with values from $_SERVER.
*
* @return UriInterface
*/
public static function getUriFromGlobals() {
$uri = new Uri('');
if (isset($_SERVER['HTTPS'])) {
$uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
}
if (isset($_SERVER['HTTP_HOST'])) {
$uri = $uri->withHost($_SERVER['HTTP_HOST']);
} elseif (isset($_SERVER['SERVER_NAME'])) {
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
}
if (isset($_SERVER['SERVER_PORT'])) {
$uri = $uri->withPort($_SERVER['SERVER_PORT']);
}
if (isset($_SERVER['REQUEST_URI'])) {
$uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
}
if (isset($_SERVER['QUERY_STRING'])) {
$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
}
return $uri;
}
/**
* {@inheritdoc}
*/
public function getServerParams()
{
return $this->serverParams;
}
/**
* {@inheritdoc}
*/
public function getUploadedFiles()
{
return $this->uploadedFiles;
}
/**
* {@inheritdoc}
*/
public function withUploadedFiles(array $uploadedFiles)
{
$new = clone $this;
$new->uploadedFiles = $uploadedFiles;
return $new;
}
/**
* {@inheritdoc}
*/
public function getCookieParams()
{
return $this->cookieParams;
}
/**
* {@inheritdoc}
*/
public function withCookieParams(array $cookies)
{
$new = clone $this;
$new->cookieParams = $cookies;
return $new;
}
/**
* {@inheritdoc}
*/
public function getQueryParams()
{
return $this->queryParams;
}
/**
* {@inheritdoc}
*/
public function withQueryParams(array $query)
{
$new = clone $this;
$new->queryParams = $query;
return $new;
}
/**
* {@inheritdoc}
*/
public function getParsedBody()
{
return $this->parsedBody;
}
/**
* {@inheritdoc}
*/
public function withParsedBody($data)
{
$new = clone $this;
$new->parsedBody = $data;
return $new;
}
/**
* {@inheritdoc}
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* {@inheritdoc}
*/
public function getAttribute($attribute, $default = null)
{
if (false === array_key_exists($attribute, $this->attributes)) {
return $default;
}
return $this->attributes[$attribute];
}
/**
* {@inheritdoc}
*/
public function withAttribute($attribute, $value)
{
$new = clone $this;
$new->attributes[$attribute] = $value;
return $new;
}
/**
* {@inheritdoc}
*/
public function withoutAttribute($attribute)
{
if (false === array_key_exists($attribute, $this->attributes)) {
return $this;
}
$new = clone $this;
unset($new->attributes[$attribute]);
return $new;
}
}

View file

@ -38,7 +38,7 @@ class Stream implements StreamInterface
* This constructor accepts an associative array of options.
*
* - size: (int) If a read stream would otherwise have an indeterminate
* size, but the size is known due to foreknownledge, then you can
* size, but the size is known due to foreknowledge, then you can
* provide that size, in bytes.
* - metadata: (array) Any additional metadata to return when the metadata
* of the stream is accessed.

View file

@ -0,0 +1,316 @@
<?php
namespace GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;
class UploadedFile implements UploadedFileInterface
{
/**
* @var int[]
*/
private static $errors = [
UPLOAD_ERR_OK,
UPLOAD_ERR_INI_SIZE,
UPLOAD_ERR_FORM_SIZE,
UPLOAD_ERR_PARTIAL,
UPLOAD_ERR_NO_FILE,
UPLOAD_ERR_NO_TMP_DIR,
UPLOAD_ERR_CANT_WRITE,
UPLOAD_ERR_EXTENSION,
];
/**
* @var string
*/
private $clientFilename;
/**
* @var string
*/
private $clientMediaType;
/**
* @var int
*/
private $error;
/**
* @var null|string
*/
private $file;
/**
* @var bool
*/
private $moved = false;
/**
* @var int
*/
private $size;
/**
* @var StreamInterface|null
*/
private $stream;
/**
* @param StreamInterface|string|resource $streamOrFile
* @param int $size
* @param int $errorStatus
* @param string|null $clientFilename
* @param string|null $clientMediaType
*/
public function __construct(
$streamOrFile,
$size,
$errorStatus,
$clientFilename = null,
$clientMediaType = null
) {
$this->setError($errorStatus);
$this->setSize($size);
$this->setClientFilename($clientFilename);
$this->setClientMediaType($clientMediaType);
if ($this->isOk()) {
$this->setStreamOrFile($streamOrFile);
}
}
/**
* Depending on the value set file or stream variable
*
* @param mixed $streamOrFile
* @throws InvalidArgumentException
*/
private function setStreamOrFile($streamOrFile)
{
if (is_string($streamOrFile)) {
$this->file = $streamOrFile;
} elseif (is_resource($streamOrFile)) {
$this->stream = new Stream($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new InvalidArgumentException(
'Invalid stream or file provided for UploadedFile'
);
}
}
/**
* @param int $error
* @throws InvalidArgumentException
*/
private function setError($error)
{
if (false === is_int($error)) {
throw new InvalidArgumentException(
'Upload file error status must be an integer'
);
}
if (false === in_array($error, UploadedFile::$errors)) {
throw new InvalidArgumentException(
'Invalid error status for UploadedFile'
);
}
$this->error = $error;
}
/**
* @param int $size
* @throws InvalidArgumentException
*/
private function setSize($size)
{
if (false === is_int($size)) {
throw new InvalidArgumentException(
'Upload file size must be an integer'
);
}
$this->size = $size;
}
/**
* @param mixed $param
* @return boolean
*/
private function isStringOrNull($param)
{
return in_array(gettype($param), ['string', 'NULL']);
}
/**
* @param mixed $param
* @return boolean
*/
private function isStringNotEmpty($param)
{
return is_string($param) && false === empty($param);
}
/**
* @param string|null $clientFilename
* @throws InvalidArgumentException
*/
private function setClientFilename($clientFilename)
{
if (false === $this->isStringOrNull($clientFilename)) {
throw new InvalidArgumentException(
'Upload file client filename must be a string or null'
);
}
$this->clientFilename = $clientFilename;
}
/**
* @param string|null $clientMediaType
* @throws InvalidArgumentException
*/
private function setClientMediaType($clientMediaType)
{
if (false === $this->isStringOrNull($clientMediaType)) {
throw new InvalidArgumentException(
'Upload file client media type must be a string or null'
);
}
$this->clientMediaType = $clientMediaType;
}
/**
* Return true if there is no upload error
*
* @return boolean
*/
private function isOk()
{
return $this->error === UPLOAD_ERR_OK;
}
/**
* @return boolean
*/
public function isMoved()
{
return $this->moved;
}
/**
* @throws RuntimeException if is moved or not ok
*/
private function validateActive()
{
if (false === $this->isOk()) {
throw new RuntimeException('Cannot retrieve stream due to upload error');
}
if ($this->isMoved()) {
throw new RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
/**
* {@inheritdoc}
* @throws RuntimeException if the upload was not successful.
*/
public function getStream()
{
$this->validateActive();
if ($this->stream instanceof StreamInterface) {
return $this->stream;
}
return new LazyOpenStream($this->file, 'r+');
}
/**
* {@inheritdoc}
*
* @see http://php.net/is_uploaded_file
* @see http://php.net/move_uploaded_file
* @param string $targetPath Path to which to move the uploaded file.
* @throws RuntimeException if the upload was not successful.
* @throws InvalidArgumentException if the $path specified is invalid.
* @throws RuntimeException on any error during the move operation, or on
* the second or subsequent call to the method.
*/
public function moveTo($targetPath)
{
$this->validateActive();
if (false === $this->isStringNotEmpty($targetPath)) {
throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a non-empty string'
);
}
if ($this->file) {
$this->moved = php_sapi_name() == 'cli'
? rename($this->file, $targetPath)
: move_uploaded_file($this->file, $targetPath);
} else {
copy_to_stream(
$this->getStream(),
new LazyOpenStream($targetPath, 'w')
);
$this->moved = true;
}
if (false === $this->moved) {
throw new RuntimeException(
sprintf('Uploaded file could not be moved to %s', $targetPath)
);
}
}
/**
* {@inheritdoc}
*
* @return int|null The file size in bytes or null if unknown.
*/
public function getSize()
{
return $this->size;
}
/**
* {@inheritdoc}
*
* @see http://php.net/manual/en/features.file-upload.errors.php
* @return int One of PHP's UPLOAD_ERR_XXX constants.
*/
public function getError()
{
return $this->error;
}
/**
* {@inheritdoc}
*
* @return string|null The filename sent by the client or null if none
* was provided.
*/
public function getClientFilename()
{
return $this->clientFilename;
}
/**
* {@inheritdoc}
*/
public function getClientMediaType()
{
return $this->clientMediaType;
}
}

View file

@ -4,10 +4,11 @@ namespace GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
/**
* Basic PSR-7 URI implementation.
* PSR-7 URI implementation.
*
* @link https://github.com/phly/http This class is based upon
* Matthew Weier O'Phinney's URI implementation in phly/http.
* @author Michael Dowling
* @author Tobias Schultze
* @author Matthew Weier O'Phinney
*/
class Uri implements UriInterface
{
@ -42,11 +43,11 @@ class Uri implements UriInterface
private $fragment = '';
/**
* @param string $uri URI to parse and wrap.
* @param string $uri URI to parse
*/
public function __construct($uri = '')
{
if ($uri != null) {
if ($uri != '') {
$parts = parse_url($uri);
if ($parts === false) {
throw new \InvalidArgumentException("Unable to parse URI: $uri");
@ -60,7 +61,7 @@ class Uri implements UriInterface
return self::createUriString(
$this->scheme,
$this->getAuthority(),
$this->getPath(),
$this->path,
$this->query,
$this->fragment
);
@ -86,7 +87,7 @@ class Uri implements UriInterface
$results = [];
$segments = explode('/', $path);
foreach ($segments as $segment) {
if ($segment == '..') {
if ($segment === '..') {
array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment;
@ -102,7 +103,7 @@ class Uri implements UriInterface
}
// Add the trailing slash if necessary
if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/';
}
@ -112,74 +113,62 @@ class Uri implements UriInterface
/**
* Resolve a base URI with a relative URI and return a new URI.
*
* @param UriInterface $base Base URI
* @param string $rel Relative URI
* @param UriInterface $base Base URI
* @param string|UriInterface $rel Relative URI
*
* @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/
public static function resolve(UriInterface $base, $rel)
{
if ($rel === null || $rel === '') {
return $base;
}
if (!($rel instanceof UriInterface)) {
$rel = new self($rel);
}
// Return the relative uri as-is if it has a scheme.
if ($rel->getScheme()) {
return $rel->withPath(static::removeDotSegments($rel->getPath()));
if ((string) $rel === '') {
// we can simply return the same base URI instance for this same-document reference
return $base;
}
$relParts = [
'scheme' => $rel->getScheme(),
'authority' => $rel->getAuthority(),
'path' => $rel->getPath(),
'query' => $rel->getQuery(),
'fragment' => $rel->getFragment()
];
if ($rel->getScheme() != '') {
return $rel->withPath(self::removeDotSegments($rel->getPath()));
}
$parts = [
'scheme' => $base->getScheme(),
'authority' => $base->getAuthority(),
'path' => $base->getPath(),
'query' => $base->getQuery(),
'fragment' => $base->getFragment()
];
if (!empty($relParts['authority'])) {
$parts['authority'] = $relParts['authority'];
$parts['path'] = self::removeDotSegments($relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
} elseif (!empty($relParts['path'])) {
if (substr($relParts['path'], 0, 1) == '/') {
$parts['path'] = self::removeDotSegments($relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
if ($rel->getAuthority() != '') {
$targetAuthority = $rel->getAuthority();
$targetPath = self::removeDotSegments($rel->getPath());
$targetQuery = $rel->getQuery();
} else {
$targetAuthority = $base->getAuthority();
if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else {
if (!empty($parts['authority']) && empty($parts['path'])) {
$mergedPath = '/';
if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
$mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
if ($targetAuthority != '' && $base->getPath() === '') {
$targetPath = '/' . $rel->getPath();
} else {
$lastSlashPos = strrpos($base->getPath(), '/');
if ($lastSlashPos === false) {
$targetPath = $rel->getPath();
} else {
$targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
}
}
}
$parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
$targetPath = self::removeDotSegments($targetPath);
$targetQuery = $rel->getQuery();
}
} elseif (!empty($relParts['query'])) {
$parts['query'] = $relParts['query'];
} elseif ($relParts['fragment'] != null) {
$parts['fragment'] = $relParts['fragment'];
}
return new self(static::createUriString(
$parts['scheme'],
$parts['authority'],
$parts['path'],
$parts['query'],
$parts['fragment']
return new self(self::createUriString(
$base->getScheme(),
$targetAuthority,
$targetPath,
$targetQuery,
$rel->getFragment()
));
}
@ -189,26 +178,22 @@ class Uri implements UriInterface
* Any existing query string values that exactly match the provided key are
* removed.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Query string key value pair to remove.
* @param string $key Query string key to remove.
*
* @return UriInterface
*/
public static function withoutQueryValue(UriInterface $uri, $key)
{
$current = $uri->getQuery();
if (!$current) {
if ($current == '') {
return $uri;
}
$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});
return $uri->withQuery(implode('&', $result));
}
@ -219,30 +204,33 @@ class Uri implements UriInterface
* Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
* A value of null will set the query string key without a value, e.g. "key"
* instead of "key=value".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string $value Value to set.
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string|null $value Value to set
*
* @return UriInterface
*/
public static function withQueryValue(UriInterface $uri, $key, $value)
{
$current = $uri->getQuery();
$key = strtr($key, self::$replaceQuery);
if (!$current) {
if ($current == '') {
$result = [];
} else {
$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});
}
// Query string separators ("=", "&") within the key or value need to be encoded
// (while preventing double-encoding) before setting the query string. All other
// chars that need percent-encoding will be encoded by withQuery().
$key = strtr($key, self::$replaceQuery);
if ($value !== null) {
$result[] = $key . '=' . strtr($value, self::$replaceQuery);
} else {
@ -273,16 +261,16 @@ class Uri implements UriInterface
public function getAuthority()
{
if (empty($this->host)) {
if ($this->host == '') {
return '';
}
$authority = $this->host;
if (!empty($this->userInfo)) {
if ($this->userInfo != '') {
$authority = $this->userInfo . '@' . $authority;
}
if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
if ($this->port !== null) {
$authority .= ':' . $this->port;
}
@ -306,7 +294,7 @@ class Uri implements UriInterface
public function getPath()
{
return $this->path == null ? '' : $this->path;
return $this->path;
}
public function getQuery()
@ -329,14 +317,14 @@ class Uri implements UriInterface
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->scheme, $new->host, $new->port);
$new->port = $new->filterPort($new->port);
return $new;
}
public function withUserInfo($user, $password = null)
{
$info = $user;
if ($password) {
if ($password != '') {
$info .= ':' . $password;
}
@ -351,6 +339,8 @@ class Uri implements UriInterface
public function withHost($host)
{
$host = $this->filterHost($host);
if ($this->host === $host) {
return $this;
}
@ -362,7 +352,7 @@ class Uri implements UriInterface
public function withPort($port)
{
$port = $this->filterPort($this->scheme, $this->host, $port);
$port = $this->filterPort($port);
if ($this->port === $port) {
return $this;
@ -375,12 +365,6 @@ class Uri implements UriInterface
public function withPath($path)
{
if (!is_string($path)) {
throw new \InvalidArgumentException(
'Invalid path provided; must be a string'
);
}
$path = $this->filterPath($path);
if ($this->path === $path) {
@ -394,17 +378,6 @@ class Uri implements UriInterface
public function withQuery($query)
{
if (!is_string($query) && !method_exists($query, '__toString')) {
throw new \InvalidArgumentException(
'Query string must be a string'
);
}
$query = (string) $query;
if (substr($query, 0, 1) === '?') {
$query = substr($query, 1);
}
$query = $this->filterQueryAndFragment($query);
if ($this->query === $query) {
@ -418,10 +391,6 @@ class Uri implements UriInterface
public function withFragment($fragment)
{
if (substr($fragment, 0, 1) === '#') {
$fragment = substr($fragment, 1);
}
$fragment = $this->filterQueryAndFragment($fragment);
if ($this->fragment === $fragment) {
@ -436,7 +405,7 @@ class Uri implements UriInterface
/**
* Apply parse_url parts to a URI.
*
* @param $parts Array of parse_url parts to apply.
* @param array $parts Array of parse_url parts to apply.
*/
private function applyParts(array $parts)
{
@ -444,9 +413,11 @@ class Uri implements UriInterface
? $this->filterScheme($parts['scheme'])
: '';
$this->userInfo = isset($parts['user']) ? $parts['user'] : '';
$this->host = isset($parts['host']) ? $parts['host'] : '';
$this->port = !empty($parts['port'])
? $this->filterPort($this->scheme, $this->host, $parts['port'])
$this->host = isset($parts['host'])
? $this->filterHost($parts['host'])
: '';
$this->port = isset($parts['port'])
? $this->filterPort($parts['port'])
: null;
$this->path = isset($parts['path'])
? $this->filterPath($parts['path'])
@ -476,27 +447,36 @@ class Uri implements UriInterface
{
$uri = '';
if (!empty($scheme)) {
$uri .= $scheme . '://';
if ($scheme != '') {
$uri .= $scheme . ':';
}
if (!empty($authority)) {
$uri .= $authority;
if ($authority != '') {
$uri .= '//' . $authority;
}
if ($path != null) {
// Add a leading slash if necessary.
if ($uri && substr($path, 0, 1) !== '/') {
$uri .= '/';
if ($path != '') {
if ($path[0] !== '/') {
if ($authority != '') {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && $path[1] === '/') {
if ($authority == '') {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . ltrim($path, '/');
}
}
$uri .= $path;
}
if ($query != null) {
if ($query != '') {
$uri .= '?' . $query;
}
if ($fragment != null) {
if ($fragment != '') {
$uri .= '#' . $fragment;
}
@ -507,70 +487,87 @@ class Uri implements UriInterface
* Is a given port non-standard for the current scheme?
*
* @param string $scheme
* @param string $host
* @param int $port
* @param int $port
*
* @return bool
*/
private static function isNonStandardPort($scheme, $host, $port)
private static function isNonStandardPort($scheme, $port)
{
if (!$scheme && $port) {
return true;
}
if (!$host || !$port) {
return false;
}
return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
}
/**
* @param string $scheme
*
* @return string
*
* @throws \InvalidArgumentException If the scheme is invalid.
*/
private function filterScheme($scheme)
{
$scheme = strtolower($scheme);
$scheme = rtrim($scheme, ':/');
if (!is_string($scheme)) {
throw new \InvalidArgumentException('Scheme must be a string');
}
return $scheme;
return strtolower($scheme);
}
/**
* @param string $scheme
* @param string $host
* @param int $port
*
* @return string
*
* @throws \InvalidArgumentException If the host is invalid.
*/
private function filterHost($host)
{
if (!is_string($host)) {
throw new \InvalidArgumentException('Host must be a string');
}
return strtolower($host);
}
/**
* @param int|null $port
*
* @return int|null
*
* @throws \InvalidArgumentException If the port is invalid.
*/
private function filterPort($scheme, $host, $port)
private function filterPort($port)
{
if (null !== $port) {
$port = (int) $port;
if (1 > $port || 0xffff < $port) {
throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
);
}
if ($port === null) {
return null;
}
return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
$port = (int) $port;
if (1 > $port || 0xffff < $port) {
throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
);
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
}
/**
* Filters the path of a URI
*
* @param $path
* @param string $path
*
* @return string
*
* @throws \InvalidArgumentException If the path is invalid.
*/
private function filterPath($path)
{
if (!is_string($path)) {
throw new \InvalidArgumentException('Path must be a string');
}
return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$path
);
@ -579,14 +576,20 @@ class Uri implements UriInterface
/**
* Filters the query string or fragment of a URI.
*
* @param $str
* @param string $str
*
* @return string
*
* @throws \InvalidArgumentException If the query or fragment is invalid.
*/
private function filterQueryAndFragment($str)
{
if (!is_string($str)) {
throw new \InvalidArgumentException('Query and fragment must be a string');
}
return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'],
$str
);

View file

@ -4,6 +4,7 @@ namespace GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
@ -68,22 +69,24 @@ function uri_for($uri)
* - metadata: Array of custom metadata.
* - size: Size of the stream.
*
* @param resource|string|StreamInterface $resource Entity body data
* @param array $options Additional options
* @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
function stream_for($resource = '', array $options = [])
{
if (is_scalar($resource)) {
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
}
switch (gettype($resource)) {
case 'string':
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
case 'resource':
return new Stream($resource, $options);
case 'object':
@ -209,6 +212,14 @@ function modify_request(RequestInterface $request, array $changes)
// Remove the host header if one is on the URI
if ($host = $changes['uri']->getHost()) {
$changes['set_headers']['Host'] = $host;
if ($port = $changes['uri']->getPort()) {
$standardPorts = ['http' => 80, 'https' => 443];
$scheme = $changes['uri']->getScheme();
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
$changes['set_headers']['Host'] .= ':'.$port;
}
}
}
$uri = $changes['uri'];
}
@ -226,6 +237,19 @@ function modify_request(RequestInterface $request, array $changes)
$uri = $uri->withQuery($changes['query']);
}
if ($request instanceof ServerRequestInterface) {
return new ServerRequest(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
$headers,
isset($changes['body']) ? $changes['body'] : $request->getBody(),
isset($changes['version'])
? $changes['version']
: $request->getProtocolVersion(),
$request->getServerParams()
);
}
return new Request(
isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri,
@ -422,7 +446,7 @@ function readline(StreamInterface $stream, $maxLength = null)
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
if ($byte === "\n" || ++$size === $maxLength - 1) {
break;
}
}
@ -441,7 +465,7 @@ function parse_request($message)
{
$data = _parse_message($message);
$matches = [];
if (!preg_match('/^[a-zA-Z]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
throw new \InvalidArgumentException('Invalid request string');
}
$parts = explode(' ', $data['start-line'], 3);
@ -535,7 +559,7 @@ function parse_query($str, $urlEncoding = true)
/**
* Build a query string from an array of key value pairs.
*
* This function can use the return value of parseQuery() to build a query
* This function can use the return value of parse_query() to build a query
* string. This function does not modify the provided keys when an array is
* encountered (like http_build_query would).
*
@ -553,9 +577,9 @@ function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
if ($encoding === false) {
$encoder = function ($str) { return $str; };
} elseif ($encoding == PHP_QUERY_RFC3986) {
} elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode';
} elseif ($encoding == PHP_QUERY_RFC1738) {
} elseif ($encoding === PHP_QUERY_RFC1738) {
$encoder = 'urlencode';
} else {
throw new \InvalidArgumentException('Invalid type');

View file

@ -34,6 +34,14 @@
</conditions>
</rule>
<rule name="Erase HTTP_PROXY" patternSyntax="Wildcard">
<match url="*.*" />
<serverVariables>
<set name="HTTP_PROXY" value="" />
</serverVariables>
<action type="None" />
</rule>
<!-- To redirect all users to access the site WITH the 'www.' prefix,
http://example.com/foo will be redirected to http://www.example.com/foo)
adapt and uncomment the following: -->