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>
</IfModule> </IfModule>
# Add headers to all responses. # Various header fixes.
<IfModule mod_headers.c> <IfModule mod_headers.c>
# Disable content sniffing, since it's an attack vector. # Disable content sniffing, since it's an attack vector.
Header always set X-Content-Type-Options nosniff Header always set X-Content-Type-Options nosniff
# Disable Proxy header, since it's an attack vector.
RequestHeader unset Proxy
</IfModule> </IfModule>

52
composer.lock generated
View file

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

View file

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

View file

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

View file

@ -206,7 +206,7 @@ class ConfigDependencyManager {
* @return int * @return int
* The comparison result for uasort(). * 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'); $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
if ($weight_cmp === 0) { if ($weight_cmp === 0) {
@ -227,7 +227,7 @@ class ConfigDependencyManager {
* @return int * @return int
* The comparison result for uasort(). * 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; $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
if ($weight_cmp === 0) { 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(), 'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
], ],
'handler' => $this->stack, '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); $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 * 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 * @code
* ckeditor_stylesheets[] = css/ckeditor-iframe.css * ckeditor_stylesheets:
* - css/ckeditor-iframe.css
* @endcode * @endcode
*/ */
function _ckeditor_theme_css($theme = NULL) { 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 // Check xdebug.max_nesting_level, as some pages will not work if it is too
// low. // low.
if (extension_loaded('xdebug')) { 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: * Regions:
* - page.header: Items for the header region. * - page.header: Items for the header region.
* - page.highlighted: Items for the highlighted region.
* - page.primary_menu: Items for the primary menu region. * - page.primary_menu: Items for the primary menu region.
* - page.secondary_menu: Items for the secondary menu region. * - page.secondary_menu: Items for the secondary menu region.
* - page.highlighted: Items for the highlighted content 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, * You can also define an array of host names that can be accessed directly,
* bypassing the proxy, in $settings['http_client_config']['proxy']['no']. * 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']['http'] = 'http://proxy_user:proxy_pass@example.com:8080';
# $settings['http_client_config']['proxy']['https'] = '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", "name": "guzzlehttp/promises",
"version": "1.0.2", "version": "1.2.0",
"version_normalized": "1.0.2.0", "version_normalized": "1.2.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "97fe7210def29451ec74923b27e552238defd75a" "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a", "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
"reference": "97fe7210def29451ec74923b27e552238defd75a", "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -760,7 +760,7 @@
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0" "phpunit/phpunit": "~4.0"
}, },
"time": "2015-08-15 19:37:21", "time": "2016-05-18 16:56:05",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -845,17 +845,17 @@
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "1.2.0", "version": "1.3.1",
"version_normalized": "1.2.0.0", "version_normalized": "1.3.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e" "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -868,11 +868,11 @@
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0" "phpunit/phpunit": "~4.0"
}, },
"time": "2015-08-15 19:32:36", "time": "2016-06-24 23:00:38",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0-dev" "dev-master": "1.4-dev"
} }
}, },
"installation-source": "dist", "installation-source": "dist",
@ -1369,12 +1369,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/log.git", "url": "https://github.com/php-fig/log.git",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" "reference": "1.0.0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", "url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0",
"reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", "reference": "1.0.0",
"shasum": "" "shasum": ""
}, },
"time": "2012-12-21 11:40:51", "time": "2012-12-21 11:40:51",
@ -1718,7 +1718,7 @@
], ],
"authors": [ "authors": [
{ {
"name": "Symfony CMF Community", "name": "Symfony CMF community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors" "homepage": "https://github.com/symfony-cmf/Routing/contributors"
} }
], ],
@ -2783,34 +2783,34 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "6.1.0", "version": "6.2.1",
"version_normalized": "6.1.0.0", "version_normalized": "6.2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81" "reference": "3f808fba627f2c5b69e2501217bf31af349c1427"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/66fd14b4d0b8f2389eaf37c5458608c7cb793a81", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81", "reference": "3f808fba627f2c5b69e2501217bf31af349c1427",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"guzzlehttp/promises": "~1.0", "guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "~1.1", "guzzlehttp/psr7": "^1.3.1",
"php": ">=5.5.0" "php": ">=5.5"
}, },
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "^4.0",
"psr/log": "~1.0" "psr/log": "^1.0"
}, },
"time": "2015-09-08 17:36:26", "time": "2016-07-15 17:22:37",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "6.1-dev" "dev-master": "6.2-dev"
} }
}, },
"installation-source": "dist", "installation-source": "dist",

View file

@ -1,5 +1,7 @@
language: php language: php
sudo: false
php: php:
- 5.5 - 5.5
- 5.6 - 5.6
@ -12,16 +14,17 @@ before_script:
- composer install --no-interaction --prefer-source --dev - composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14 - ~/.nvm/nvm.sh install v0.6.14
- ~/.nvm/nvm.sh run 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 script: make test
matrix: matrix:
allow_failures: allow_failures:
- php: hhvm - php: hhvm
- php: 7.0
fast_finish: true fast_finish: true
before_deploy: before_deploy:
- rvm 1.9.3 do gem install mime-types -v 2.6.2
- make package - make package
deploy: deploy:

View file

@ -1,5 +1,49 @@
# CHANGELOG # 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 ## 6.1.0 - 2015-09-08
* Feature: Added the `on_stats` request option to provide access to transfer * 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

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

View file

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

View file

@ -12,7 +12,7 @@ use Psr\Http\Message\UriInterface;
*/ */
interface ClientInterface interface ClientInterface
{ {
const VERSION = '6.1.0'; const VERSION = '6.2.1';
/** /**
* Send an HTTP request. * Send an HTTP request.
@ -44,7 +44,7 @@ interface ClientInterface
* relative path to append to the base path of the client. The URL can * relative path to append to the base path of the client. The URL can
* contain the query string as well. * 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 string|UriInterface $uri URI object or string.
* @param array $options Request options to apply. * @param array $options Request options to apply.
* *

View file

@ -5,7 +5,7 @@ use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; 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 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 * @deprecated
* problematic characters.
*
* @param string $value Value that may or may not need to be quoted
*
* @return string
*/ */
public static function getCookieValue($value) public static function getCookieValue($value)
{ {
if (substr($value, 0, 1) !== '"' &&
substr($value, -1, 1) !== '"' &&
strpbrk($value, ';,=')
) {
$value = '"' . $value . '"';
}
return $value; return $value;
} }
@ -82,7 +70,7 @@ class CookieJar implements CookieJarInterface
* that survives between requests. * that survives between requests.
* *
* @param SetCookie $cookie Being evaluated. * @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 * @return bool
*/ */
public static function shouldPersist( public static function shouldPersist(
@ -245,10 +233,10 @@ class CookieJar implements CookieJarInterface
if ($cookie->matchesPath($path) && if ($cookie->matchesPath($path) &&
$cookie->matchesDomain($host) && $cookie->matchesDomain($host) &&
!$cookie->isExpired() && !$cookie->isExpired() &&
(!$cookie->getSecure() || $scheme == 'https') (!$cookie->getSecure() || $scheme === 'https')
) { ) {
$values[] = $cookie->getName() . '=' $values[] = $cookie->getName() . '='
. self::getCookieValue($cookie->getValue()); . $cookie->getValue();
} }
} }

View file

@ -9,7 +9,7 @@ class FileCookieJar extends CookieJar
/** @var string filename */ /** @var string filename */
private $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; private $storeSessionCookies;
/** /**
@ -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}"); throw new \RuntimeException("Unable to save file {$filename}");
} }
} }
@ -73,9 +74,11 @@ class FileCookieJar extends CookieJar
$json = file_get_contents($filename); $json = file_get_contents($filename);
if (false === $json) { if (false === $json) {
throw new \RuntimeException("Unable to load file {$filename}"); 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)) { if (is_array($data)) {
foreach (json_decode($json, true) as $cookie) { foreach (json_decode($json, true) as $cookie) {
$this->setCookie(new SetCookie($cookie)); $this->setCookie(new SetCookie($cookie));

View file

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

View file

@ -86,8 +86,8 @@ class SetCookie
{ {
$str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
foreach ($this->data as $k => $v) { foreach ($this->data as $k => $v) {
if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) { if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
if ($k == 'Expires') { if ($k === 'Expires') {
$str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
} else { } else {
$str .= ($v === true ? $k : "{$k}={$v}") . '; '; $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
@ -307,7 +307,7 @@ class SetCookie
$cookiePath = $this->getPath(); $cookiePath = $this->getPath();
// Match on exact matches or when path is the default empty "/" // Match on exact matches or when path is the default empty "/"
if ($cookiePath == '/' || $cookiePath == $requestPath) { if ($cookiePath === '/' || $cookiePath == $requestPath) {
return true; return true;
} }
@ -317,12 +317,12 @@ class SetCookie
} }
// Match if the last character of the cookie-path is "/" // Match if the last character of the cookie-path is "/"
if (substr($cookiePath, -1, 1) == '/') { if (substr($cookiePath, -1, 1) === '/') {
return true; return true;
} }
// Match if the first character not included in cookie path is "/" // 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); $level = (int) floor($response->getStatusCode() / 100);
if ($level == '4') { if ($level === 4) {
$label = 'Client error response'; $label = 'Client error';
$className = __NAMESPACE__ . '\\ClientException'; $className = __NAMESPACE__ . '\\ClientException';
} elseif ($level == '5') { } elseif ($level === 5) {
$label = 'Server error response'; $label = 'Server error';
$className = __NAMESPACE__ . '\\ServerException'; $className = __NAMESPACE__ . '\\ServerException';
} else { } else {
$label = 'Unsuccessful response'; $label = 'Unsuccessful request';
$className = __CLASS__; $className = __CLASS__;
} }
$message = $label . ' [url] ' . $request->getUri() // Server Error: `GET /` resulted in a `404 Not Found` response:
. ' [http method] ' . $request->getMethod() // <html> ... (truncated)
. ' [status code] ' . $response->getStatusCode() $message = sprintf(
. ' [reason phrase] ' . $response->getReasonPhrase(); '%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); 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 * Get the request that caused the exception
* *

View file

@ -194,7 +194,7 @@ class CurlFactory implements CurlFactoryInterface
$conf = [ $conf = [
'_headers' => $easy->request->getHeaders(), '_headers' => $easy->request->getHeaders(),
CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
CURLOPT_URL => (string) $easy->request->getUri(), CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false, CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 150, CURLOPT_CONNECTTIMEOUT => 150,
@ -265,6 +265,9 @@ class CurlFactory implements CurlFactoryInterface
$this->removeHeader('Content-Length', $conf); $this->removeHeader('Content-Length', $conf);
} }
$body = $request->getBody(); $body = $request->getBody();
if ($body->isSeekable()) {
$body->rewind();
}
$conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
return $body->read($length); return $body->read($length);
}; };
@ -492,12 +495,14 @@ class CurlFactory implements CurlFactoryInterface
private function createHeaderFn(EasyHandle $easy) private function createHeaderFn(EasyHandle $easy)
{ {
if (!isset($easy->options['on_headers'])) { if (isset($easy->options['on_headers'])) {
$onHeaders = null;
} elseif (!is_callable($easy->options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
} else {
$onHeaders = $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 ( return function ($ch, $h) use (
@ -509,7 +514,7 @@ class CurlFactory implements CurlFactoryInterface
if ($value === '') { if ($value === '') {
$startingResponse = true; $startingResponse = true;
$easy->createResponse(); $easy->createResponse();
if ($onHeaders) { if ($onHeaders !== null) {
try { try {
$onHeaders($easy->response); $onHeaders($easy->response);
} catch (\Exception $e) { } 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']) if (!empty($this->options['decode_content'])
&& isset($normalizedKeys['content-encoding']) && isset($normalizedKeys['content-encoding'])
) { ) {
$headers['x-encoded-content-encoding']
= $headers[$normalizedKeys['content-encoding']];
unset($headers[$normalizedKeys['content-encoding']]); unset($headers[$normalizedKeys['content-encoding']]);
if (isset($normalizedKeys['content-length'])) { if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length']
= $headers[$normalizedKeys['content-length']];
$bodyLength = (int) $this->sink->getSize(); $bodyLength = (int) $this->sink->getSize();
if ($bodyLength) { if ($bodyLength) {
$headers[$normalizedKeys['content-length']] = $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 $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable $onRejected Callback to invoke when the return value is rejected. * @param callable $onRejected Callback to invoke when the return value is rejected.
* *
* @return MockHandler * @return HandlerStack
*/ */
public static function createWithMiddleware( public static function createWithMiddleware(
array $queue = null, array $queue = null,
@ -74,7 +74,7 @@ class MockHandler implements \Countable
$response = array_shift($this->queue); $response = array_shift($this->queue);
if (is_callable($response)) { if (is_callable($response)) {
$response = $response($request, $options); $response = call_user_func($response, $request, $options);
} }
$response = $response instanceof \Exception $response = $response instanceof \Exception
@ -87,6 +87,19 @@ class MockHandler implements \Countable
if ($this->onFulfilled) { if ($this->onFulfilled) {
call_user_func($this->onFulfilled, $value); 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; return $value;
}, },
function ($reason) use ($request, $options) { function ($reason) use ($request, $options) {

View file

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

View file

@ -105,7 +105,12 @@ class StreamHandler
$headers = \GuzzleHttp\headers_from_lines($hdrs); $headers = \GuzzleHttp\headers_from_lines($hdrs);
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream); $stream = Psr7\stream_for($stream);
$sink = $stream;
if (strcasecmp('HEAD', $request->getMethod())) {
$sink = $this->createSink($stream, $options); $sink = $this->createSink($stream, $options);
}
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason); $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
if (isset($options['on_headers'])) { 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) { if ($sink !== $stream) {
$this->drain($stream, $sink); $this->drain(
$stream,
$sink,
$response->getHeaderLine('Content-Length')
);
} }
$this->invokeStats($options, $request, $startTime, $response, null); $this->invokeStats($options, $request, $startTime, $response, null);
@ -138,7 +149,7 @@ class StreamHandler
: fopen('php://temp', 'r+'); : fopen('php://temp', 'r+');
return is_string($sink) return is_string($sink)
? new Psr7\Stream(Psr7\try_fopen($sink, 'r+')) ? new Psr7\LazyOpenStream($sink, 'w+')
: Psr7\stream_for($sink); : Psr7\stream_for($sink);
} }
@ -149,16 +160,21 @@ class StreamHandler
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
if (isset($normalizedKeys['content-encoding'])) { if (isset($normalizedKeys['content-encoding'])) {
$encoding = $headers[$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( $stream = new Psr7\InflateStream(
Psr7\stream_for($stream) Psr7\stream_for($stream)
); );
$headers['x-encoded-content-encoding']
= $headers[$normalizedKeys['content-encoding']];
// Remove content-encoding header // Remove content-encoding header
unset($headers[$normalizedKeys['content-encoding']]); unset($headers[$normalizedKeys['content-encoding']]);
// Fix content-length header // Fix content-length header
if (isset($normalizedKeys['content-length'])) { if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length']
= $headers[$normalizedKeys['content-length']];
$length = (int) $stream->getSize(); $length = (int) $stream->getSize();
if ($length == 0) { if ($length === 0) {
unset($headers[$normalizedKeys['content-length']]); unset($headers[$normalizedKeys['content-length']]);
} else { } else {
$headers[$normalizedKeys['content-length']] = [$length]; $headers[$normalizedKeys['content-length']] = [$length];
@ -176,13 +192,27 @@ class StreamHandler
* *
* @param StreamInterface $source * @param StreamInterface $source
* @param StreamInterface $sink * @param StreamInterface $sink
* @param string $contentLength Header specifying the amount of
* data to read.
* *
* @return StreamInterface * @return StreamInterface
* @throws \RuntimeException when the sink option is invalid. * @throws \RuntimeException when the sink option is invalid.
*/ */
private function drain(StreamInterface $source, StreamInterface $sink) private function drain(
{ StreamInterface $source,
Psr7\copy_to_stream($source, $sink); 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); $sink->seek(0);
$source->close(); $source->close();
@ -279,7 +309,7 @@ class StreamHandler
return $this->createResource( return $this->createResource(
function () use ($request, &$http_response_header, $context) { 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; $this->lastHeaders = $http_response_header;
return $resource; return $resource;
} }
@ -341,8 +371,10 @@ class StreamHandler
private function add_timeout(RequestInterface $request, &$options, $value, &$params) private function add_timeout(RequestInterface $request, &$options, $value, &$params)
{ {
if ($value > 0) {
$options['http']['timeout'] = $value; $options['http']['timeout'] = $value;
} }
}
private function add_verify(RequestInterface $request, &$options, $value, &$params) private function add_verify(RequestInterface $request, &$options, $value, &$params)
{ {
@ -359,12 +391,14 @@ class StreamHandler
} }
} elseif ($value === false) { } elseif ($value === false) {
$options['ssl']['verify_peer'] = false; $options['ssl']['verify_peer'] = false;
$options['ssl']['verify_peer_name'] = false;
return; return;
} else { } else {
throw new \InvalidArgumentException('Invalid verify request option'); throw new \InvalidArgumentException('Invalid verify request option');
} }
$options['ssl']['verify_peer'] = true; $options['ssl']['verify_peer'] = true;
$options['ssl']['verify_peer_name'] = true;
$options['ssl']['allow_self_signed'] = false; $options['ssl']['allow_self_signed'] = false;
} }
@ -416,7 +450,7 @@ class StreamHandler
'bytes_transferred', 'bytes_max']; 'bytes_transferred', 'bytes_max'];
$value = \GuzzleHttp\debug_resource($value); $value = \GuzzleHttp\debug_resource($value);
$ident = $request->getMethod() . ' ' . $request->getUri(); $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
$this->addNotification( $this->addNotification(
$params, $params,
function () use ($ident, $value, $map, $args) { function () use ($ident, $value, $map, $args) {

View file

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

View file

@ -2,9 +2,7 @@
namespace GuzzleHttp; namespace GuzzleHttp;
use GuzzleHttp\Cookie\CookieJarInterface; use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Promise\RejectedPromise; use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7; use GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@ -64,9 +62,7 @@ final class Middleware
if ($code < 400) { if ($code < 400) {
return $response; return $response;
} }
throw $code > 499 throw RequestException::create($request, $response);
? new ServerException("Server error: $code", $request, $response)
: new ClientException("Client error: $code", $request, $response);
} }
); );
}; };
@ -79,9 +75,14 @@ final class Middleware
* @param array $container Container to hold the history (by reference). * @param array $container Container to hold the history (by reference).
* *
* @return callable Returns a function that accepts the next handler. * @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 (callable $handler) use (&$container) {
return function ($request, array $options) use ($handler, &$container) { return function ($request, array $options) use ($handler, &$container) {
return $handler($request, $options)->then( return $handler($request, $options)->then(

View file

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

View file

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

View file

@ -5,7 +5,6 @@ use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler; use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy; use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler; use GuzzleHttp\Handler\StreamHandler;
use Psr\Http\Message\StreamInterface;
/** /**
* Expands a URI template * Expands a URI template
@ -104,8 +103,12 @@ function debug_resource($value = null)
function choose_handler() function choose_handler()
{ {
$handler = null; $handler = null;
if (extension_loaded('curl')) { if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); $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')) { if (ini_get('allow_url_fopen')) {
@ -278,3 +281,49 @@ function is_host_in_noproxy($host, array $noProxyArray)
return false; 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 # 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 ## 1.0.2 - 2015-05-15
* Conditionally require functions.php. * Conditionally require functions.php.

View file

@ -208,7 +208,7 @@ of the promise is called.
```php ```php
$promise = new Promise(function () use (&$promise) { $promise = new Promise(function () use (&$promise) {
$promise->deliver('foo'); $promise->resolve('foo');
}); });
// Calling wait will return the value of the promise. // 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 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 ```php
$promise = new Promise(function () { die('this is not called!'); }); $promise = new Promise(function () { die('this is not called!'); });
$promise->deliver('foo'); $promise->resolve('foo');
echo $promise->wait(); // outputs "foo" echo $promise->wait(); // outputs "foo"
``` ```
@ -268,7 +268,7 @@ $promise->reject('foo');
$promise->wait(false); $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 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 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. 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 = new Promise();
$promise->then(function ($value) { echo $value; }); $promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it. // The promise is the deferred value, so you can deliver a value to it.
$promise->deliver('foo'); $promise->resolve('foo');
// prints "foo" // prints "foo"
``` ```

View file

@ -24,6 +24,9 @@ class EachPromise implements PromisorInterface
/** @var Promise */ /** @var Promise */
private $aggregate; private $aggregate;
/** @var bool */
private $mutex;
/** /**
* Configuration hash can include the following key value pairs: * Configuration hash can include the following key value pairs:
* *
@ -72,6 +75,8 @@ class EachPromise implements PromisorInterface
$this->createPromise(); $this->createPromise();
$this->iterable->rewind(); $this->iterable->rewind();
$this->refillPending(); $this->refillPending();
} catch (\Throwable $e) {
$this->aggregate->reject($e);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->aggregate->reject($e); $this->aggregate->reject($e);
} }
@ -81,8 +86,14 @@ class EachPromise implements PromisorInterface
private function createPromise() private function createPromise()
{ {
$this->mutex = false;
$this->aggregate = new Promise(function () { $this->aggregate = new Promise(function () {
reset($this->pending); reset($this->pending);
if (empty($this->pending) && !$this->iterable->valid()) {
$this->aggregate->resolve(null);
return;
}
// Consume a potentially fluctuating list of promises while // Consume a potentially fluctuating list of promises while
// ensuring that indexes are maintained (precluding array_shift). // ensuring that indexes are maintained (precluding array_shift).
while ($promise = current($this->pending)) { while ($promise = current($this->pending)) {
@ -164,11 +175,25 @@ class EachPromise implements PromisorInterface
private function advanceIterator() 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 { try {
$this->iterable->next(); $this->iterable->next();
$this->mutex = false;
return true; return true;
} catch (\Throwable $e) {
$this->aggregate->reject($e);
$this->mutex = false;
return false;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->aggregate->reject($e); $this->aggregate->reject($e);
$this->mutex = false;
return false; return false;
} }
} }
@ -181,9 +206,11 @@ class EachPromise implements PromisorInterface
} }
unset($this->pending[$idx]); 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. // Add more pending promises if possible.
$this->refillPending(); $this->refillPending();
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,47 @@
# CHANGELOG # 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 ## 1.2.0 - 2015-08-15
* Body as `"0"` is now properly added to a response. * Body as `"0"` is now properly added to a response.

View file

@ -9,5 +9,21 @@ coverage:
view-coverage: view-coverage:
open artifacts/coverage/index.html 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: clean:
rm -rf artifacts/* rm -rf artifacts/*

View file

@ -1,9 +1,11 @@
# PSR-7 Message Implementation # 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 message implementation, several stream decorators, and some helpful
functionality like query string parsing. Currently missing functionality like query string parsing.
ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7)
# Stream implementation # Stream implementation
@ -25,9 +27,9 @@ $a = Psr7\stream_for('abc, ');
$b = Psr7\stream_for('123.'); $b = Psr7\stream_for('123.');
$composed = new Psr7\AppendStream([$a, $b]); $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` `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. from to remove bytes from the buffer.
This stream returns a "hwm" metadata value that tells upstream consumers 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 // Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10); $dropping = new Psr7\DroppingStream($stream, 10);
$stream->write('01234567890123456789'); $dropping->write('01234567890123456789');
echo $stream; // 0123456789 echo $stream; // 0123456789
``` ```
@ -103,7 +105,7 @@ echo $stream; // 0123456789
Compose stream implementations based on a hash of functions. 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. to create a concrete class for a simple extension point.
```php ```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. 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 This function does not modify the provided keys when an array is encountered
(like http_build_query would). (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` ## `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. 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` ## `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. 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` ## `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. Create a new URI with a specific query string value.
Any existing query string values that exactly match the provided key are Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair. removed and replaced with the given key value pair.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::withoutQueryValue` ## `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. Create a new URI with a specific query string value removed.
Any existing query string values that exactly match the provided key are Any existing query string values that exactly match the provided key are
removed. removed.
Note: this function will convert "=" to "%3D" and "&" to "%26".
## `GuzzleHttp\Psr7\Uri::fromParts` ## `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. 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": { "extra": {
"branch-alias": { "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) { if ($size === null) {
$size = $this->cacheEntireStream(); $size = $this->cacheEntireStream();
} }
// Because 0 is the first byte, we seek to size - 1. $byte = $size + $offset;
$byte = $size - 1 - $offset;
} else { } else {
throw new \InvalidArgumentException('Invalid whence'); throw new \InvalidArgumentException('Invalid whence');
} }
@ -61,9 +60,12 @@ class CachingStream implements StreamInterface
$diff = $byte - $this->stream->getSize(); $diff = $byte - $this->stream->getSize();
if ($diff > 0) { if ($diff > 0) {
// If the seek byte is greater the number of read bytes, then read // Read the remoteStream until we have read in at least the amount
// the difference of bytes to cache the bytes and inherently seek. // of bytes requested, or we reach the end of the file.
while ($diff > 0 && !$this->remoteStream->eof()) {
$this->read($diff); $this->read($diff);
$diff = $byte - $this->stream->getSize();
}
} else { } else {
// We can just do a normal seek since we've already seen this byte. // We can just do a normal seek since we've already seen this byte.
$this->stream->seek($byte); $this->stream->seek($byte);

View file

@ -20,10 +20,33 @@ class InflateStream implements StreamInterface
public function __construct(StreamInterface $stream) public function __construct(StreamInterface $stream)
{ {
// Skip the first 10 bytes // read the first 10 bytes, ie. gzip header
$stream = new LimitStream($stream, -1, 10); $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); $resource = StreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource); $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 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 = []; private $headers = [];
/** @var array Actual key to list of values per header. */ /** @var array Map of lowercase header name => original name at registration */
private $headerLines = []; private $headerNames = [];
/** @var string */ /** @var string */
private $protocol = '1.1'; private $protocol = '1.1';
@ -38,18 +38,25 @@ trait MessageTrait
public function getHeaders() public function getHeaders()
{ {
return $this->headerLines; return $this->headers;
} }
public function hasHeader($header) public function hasHeader($header)
{ {
return isset($this->headers[strtolower($header)]); return isset($this->headerNames[strtolower($header)]);
} }
public function getHeader($header) public function getHeader($header)
{ {
$name = strtolower($header); $header = strtolower($header);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
if (!isset($this->headerNames[$header])) {
return [];
}
$header = $this->headerNames[$header];
return $this->headers[$header];
} }
public function getHeaderLine($header) public function getHeaderLine($header)
@ -59,59 +66,56 @@ trait MessageTrait
public function withHeader($header, $value) public function withHeader($header, $value)
{ {
$new = clone $this;
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) { if (!is_array($value)) {
$new->headers[$name] = [trim($value)]; $value = [$value];
} else {
$new->headers[$name] = $value;
foreach ($new->headers[$name] as &$v) {
$v = trim($v);
}
} }
// Remove the header lines. $value = $this->trimHeaderValues($value);
foreach (array_keys($new->headerLines) as $key) { $normalized = strtolower($header);
if (strtolower($key) === $name) {
unset($new->headerLines[$key]);
}
}
// Add the header line. $new = clone $this;
$new->headerLines[$header] = $new->headers[$name]; if (isset($new->headerNames[$normalized])) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
return $new; return $new;
} }
public function withAddedHeader($header, $value) public function withAddedHeader($header, $value)
{ {
if (!$this->hasHeader($header)) { if (!is_array($value)) {
return $this->withHeader($header, $value); $value = [$value];
} }
$value = $this->trimHeaderValues($value);
$normalized = strtolower($header);
$new = clone $this; $new = clone $this;
$new->headers[strtolower($header)][] = $value; if (isset($new->headerNames[$normalized])) {
$new->headerLines[$header][] = $value; $header = $this->headerNames[$normalized];
$new->headers[$header] = array_merge($this->headers[$header], $value);
} else {
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
}
return $new; return $new;
} }
public function withoutHeader($header) public function withoutHeader($header)
{ {
if (!$this->hasHeader($header)) { $normalized = strtolower($header);
if (!isset($this->headerNames[$normalized])) {
return $this; return $this;
} }
$new = clone $this; $header = $this->headerNames[$normalized];
$name = strtolower($header);
unset($new->headers[$name]);
foreach (array_keys($new->headerLines) as $key) { $new = clone $this;
if (strtolower($key) === $name) { unset($new->headers[$header], $new->headerNames[$normalized]);
unset($new->headerLines[$key]);
}
}
return $new; return $new;
} }
@ -138,21 +142,42 @@ trait MessageTrait
private function setHeaders(array $headers) private function setHeaders(array $headers)
{ {
$this->headerLines = $this->headers = []; $this->headerNames = $this->headers = [];
foreach ($headers as $header => $value) { foreach ($headers as $header => $value) {
$header = trim($header);
$name = strtolower($header);
if (!is_array($value)) { if (!is_array($value)) {
$value = trim($value); $value = [$value];
$this->headers[$name][] = $value; }
$this->headerLines[$header][] = $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 { } else {
foreach ($value as $v) { $this->headerNames[$normalized] = $header;
$v = trim($v); $this->headers[$header] = $value;
$this->headers[$name][] = $v;
$this->headerLines[$header][] = $v;
} }
} }
} }
/**
* 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 // Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition'); $disposition = $this->getHeader($headers, 'content-disposition');
if (!$disposition) { if (!$disposition) {
$headers['Content-Disposition'] = $filename $headers['Content-Disposition'] = ($filename === '0' || $filename)
? sprintf('form-data; name="%s"; filename="%s"', ? sprintf('form-data; name="%s"; filename="%s"',
$name, $name,
basename($filename)) basename($filename))
@ -130,7 +130,7 @@ class MultipartStream implements StreamInterface
// Set a default Content-Type if one was not supplied // Set a default Content-Type if one was not supplied
$type = $this->getHeader($headers, 'content-type'); $type = $this->getHeader($headers, 'content-type');
if (!$type && $filename) { if (!$type && ($filename === '0' || $filename)) {
if ($type = mimetype_from_filename($filename)) { if ($type = mimetype_from_filename($filename)) {
$headers['Content-Type'] = $type; $headers['Content-Type'] = $type;
} }

View file

@ -11,9 +11,7 @@ use Psr\Http\Message\UriInterface;
*/ */
class Request implements RequestInterface class Request implements RequestInterface
{ {
use MessageTrait { use MessageTrait;
withHeader as protected withParentHeader;
}
/** @var string */ /** @var string */
private $method; private $method;
@ -25,40 +23,33 @@ class Request implements RequestInterface
private $uri; private $uri;
/** /**
* @param null|string $method HTTP method for the request. * @param string $method HTTP method
* @param null|string $uri URI for the request. * @param string|UriInterface $uri URI
* @param array $headers Headers for the message. * @param array $headers Request headers
* @param string|resource|StreamInterface $body Message body. * @param string|null|resource|StreamInterface $body Request body
* @param string $protocolVersion HTTP protocol version. * @param string $version Protocol version
*
* @throws InvalidArgumentException for an invalid URI
*/ */
public function __construct( public function __construct(
$method, $method,
$uri, $uri,
array $headers = [], array $headers = [],
$body = null, $body = null,
$protocolVersion = '1.1' $version = '1.1'
) { ) {
if (is_string($uri)) { if (!($uri instanceof UriInterface)) {
$uri = new Uri($uri); $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->method = strtoupper($method);
$this->uri = $uri; $this->uri = $uri;
$this->setHeaders($headers); $this->setHeaders($headers);
$this->protocol = $protocolVersion; $this->protocol = $version;
$host = $uri->getHost(); if (!$this->hasHeader('Host')) {
if ($host && !$this->hasHeader('Host')) { $this->updateHostFromUri();
$this->updateHostFromUri($host);
} }
if ($body) { if ($body !== '' && $body !== null) {
$this->stream = stream_for($body); $this->stream = stream_for($body);
} }
} }
@ -70,10 +61,10 @@ class Request implements RequestInterface
} }
$target = $this->uri->getPath(); $target = $this->uri->getPath();
if ($target == null) { if ($target == '') {
$target = '/'; $target = '/';
} }
if ($this->uri->getQuery()) { if ($this->uri->getQuery() != '') {
$target .= '?' . $this->uri->getQuery(); $target .= '?' . $this->uri->getQuery();
} }
@ -120,30 +111,32 @@ class Request implements RequestInterface
$new->uri = $uri; $new->uri = $uri;
if (!$preserveHost) { if (!$preserveHost) {
if ($host = $uri->getHost()) { $new->updateHostFromUri();
$new->updateHostFromUri($host);
}
} }
return $new; return $new;
} }
public function withHeader($header, $value) private function updateHostFromUri()
{ {
/** @var Request $newInstance */ $host = $this->uri->getHost();
$newInstance = $this->withParentHeader($header, $value);
return $newInstance; if ($host == '') {
return;
} }
private function updateHostFromUri($host) if (($port = $this->uri->getPort()) !== null) {
{
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
if ($port = $this->uri->getPort()) {
$host .= ':' . $port; $host .= ':' . $port;
} }
$this->headerLines = ['Host' => [$host]] + $this->headerLines; if (isset($this->headerNames['host'])) {
$this->headers = ['host' => [$host]] + $this->headers; $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', 428 => 'Precondition Required',
429 => 'Too Many Requests', 429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large', 431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error', 500 => 'Internal Server Error',
501 => 'Not Implemented', 501 => 'Not Implemented',
502 => 'Bad Gateway', 502 => 'Bad Gateway',
@ -71,18 +72,18 @@ class Response implements ResponseInterface
511 => 'Network Authentication Required', 511 => 'Network Authentication Required',
]; ];
/** @var null|string */ /** @var string */
private $reasonPhrase = ''; private $reasonPhrase = '';
/** @var int */ /** @var int */
private $statusCode = 200; private $statusCode = 200;
/** /**
* @param int $status Status code for the response, if any. * @param int $status Status code
* @param array $headers Headers for the response, if any. * @param array $headers Response headers
* @param mixed $body Stream body. * @param string|null|resource|StreamInterface $body Response body
* @param string $version Protocol version. * @param string $version Protocol version
* @param string $reason Reason phrase (a default will be used if possible). * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/ */
public function __construct( public function __construct(
$status = 200, $status = 200,
@ -93,12 +94,12 @@ class Response implements ResponseInterface
) { ) {
$this->statusCode = (int) $status; $this->statusCode = (int) $status;
if ($body !== null) { if ($body !== '' && $body !== null) {
$this->stream = stream_for($body); $this->stream = stream_for($body);
} }
$this->setHeaders($headers); $this->setHeaders($headers);
if (!$reason && isset(self::$phrases[$this->statusCode])) { if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$status]; $this->reasonPhrase = self::$phrases[$status];
} else { } else {
$this->reasonPhrase = (string) $reason; $this->reasonPhrase = (string) $reason;
@ -121,7 +122,7 @@ class Response implements ResponseInterface
{ {
$new = clone $this; $new = clone $this;
$new->statusCode = (int) $code; $new->statusCode = (int) $code;
if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) { if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
$reasonPhrase = self::$phrases[$new->statusCode]; $reasonPhrase = self::$phrases[$new->statusCode];
} }
$new->reasonPhrase = $reasonPhrase; $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. * This constructor accepts an associative array of options.
* *
* - size: (int) If a read stream would otherwise have an indeterminate * - 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. * provide that size, in bytes.
* - metadata: (array) Any additional metadata to return when the metadata * - metadata: (array) Any additional metadata to return when the metadata
* of the stream is accessed. * 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; 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 * @author Michael Dowling
* Matthew Weier O'Phinney's URI implementation in phly/http. * @author Tobias Schultze
* @author Matthew Weier O'Phinney
*/ */
class Uri implements UriInterface class Uri implements UriInterface
{ {
@ -42,11 +43,11 @@ class Uri implements UriInterface
private $fragment = ''; private $fragment = '';
/** /**
* @param string $uri URI to parse and wrap. * @param string $uri URI to parse
*/ */
public function __construct($uri = '') public function __construct($uri = '')
{ {
if ($uri != null) { if ($uri != '') {
$parts = parse_url($uri); $parts = parse_url($uri);
if ($parts === false) { if ($parts === false) {
throw new \InvalidArgumentException("Unable to parse URI: $uri"); throw new \InvalidArgumentException("Unable to parse URI: $uri");
@ -60,7 +61,7 @@ class Uri implements UriInterface
return self::createUriString( return self::createUriString(
$this->scheme, $this->scheme,
$this->getAuthority(), $this->getAuthority(),
$this->getPath(), $this->path,
$this->query, $this->query,
$this->fragment $this->fragment
); );
@ -86,7 +87,7 @@ class Uri implements UriInterface
$results = []; $results = [];
$segments = explode('/', $path); $segments = explode('/', $path);
foreach ($segments as $segment) { foreach ($segments as $segment) {
if ($segment == '..') { if ($segment === '..') {
array_pop($results); array_pop($results);
} elseif (!isset($ignoreSegments[$segment])) { } elseif (!isset($ignoreSegments[$segment])) {
$results[] = $segment; $results[] = $segment;
@ -102,7 +103,7 @@ class Uri implements UriInterface
} }
// Add the trailing slash if necessary // Add the trailing slash if necessary
if ($newPath != '/' && isset($ignoreSegments[end($segments)])) { if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
$newPath .= '/'; $newPath .= '/';
} }
@ -113,73 +114,61 @@ class Uri implements UriInterface
* Resolve a base URI with a relative URI and return a new URI. * Resolve a base URI with a relative URI and return a new URI.
* *
* @param UriInterface $base Base URI * @param UriInterface $base Base URI
* @param string $rel Relative URI * @param string|UriInterface $rel Relative URI
* *
* @return UriInterface * @return UriInterface
* @link http://tools.ietf.org/html/rfc3986#section-5.2
*/ */
public static function resolve(UriInterface $base, $rel) public static function resolve(UriInterface $base, $rel)
{ {
if ($rel === null || $rel === '') {
return $base;
}
if (!($rel instanceof UriInterface)) { if (!($rel instanceof UriInterface)) {
$rel = new self($rel); $rel = new self($rel);
} }
// Return the relative uri as-is if it has a scheme. if ((string) $rel === '') {
if ($rel->getScheme()) { // we can simply return the same base URI instance for this same-document reference
return $rel->withPath(static::removeDotSegments($rel->getPath())); return $base;
} }
$relParts = [ if ($rel->getScheme() != '') {
'scheme' => $rel->getScheme(), return $rel->withPath(self::removeDotSegments($rel->getPath()));
'authority' => $rel->getAuthority(), }
'path' => $rel->getPath(),
'query' => $rel->getQuery(),
'fragment' => $rel->getFragment()
];
$parts = [ if ($rel->getAuthority() != '') {
'scheme' => $base->getScheme(), $targetAuthority = $rel->getAuthority();
'authority' => $base->getAuthority(), $targetPath = self::removeDotSegments($rel->getPath());
'path' => $base->getPath(), $targetQuery = $rel->getQuery();
'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'];
} else { } else {
if (!empty($parts['authority']) && empty($parts['path'])) { $targetAuthority = $base->getAuthority();
$mergedPath = '/'; if ($rel->getPath() === '') {
$targetPath = $base->getPath();
$targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
} else { } else {
$mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1); if ($rel->getPath()[0] === '/') {
$targetPath = $rel->getPath();
} else {
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'];
} }
} elseif (!empty($relParts['query'])) { }
$parts['query'] = $relParts['query']; $targetPath = self::removeDotSegments($targetPath);
} elseif ($relParts['fragment'] != null) { $targetQuery = $rel->getQuery();
$parts['fragment'] = $relParts['fragment']; }
} }
return new self(static::createUriString( return new self(self::createUriString(
$parts['scheme'], $base->getScheme(),
$parts['authority'], $targetAuthority,
$parts['path'], $targetPath,
$parts['query'], $targetQuery,
$parts['fragment'] $rel->getFragment()
)); ));
} }
@ -189,26 +178,22 @@ class Uri implements UriInterface
* Any existing query string values that exactly match the provided key are * Any existing query string values that exactly match the provided key are
* removed. * removed.
* *
* Note: this function will convert "=" to "%3D" and "&" to "%26".
*
* @param UriInterface $uri URI to use as a base. * @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 * @return UriInterface
*/ */
public static function withoutQueryValue(UriInterface $uri, $key) public static function withoutQueryValue(UriInterface $uri, $key)
{ {
$current = $uri->getQuery(); $current = $uri->getQuery();
if (!$current) { if ($current == '') {
return $uri; return $uri;
} }
$result = []; $decodedKey = rawurldecode($key);
foreach (explode('&', $current) as $part) { $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
if (explode('=', $part)[0] !== $key) { return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
$result[] = $part; });
};
}
return $uri->withQuery(implode('&', $result)); 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 * Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair. * 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 UriInterface $uri URI to use as a base.
* @param string $key Key to set. * @param string $key Key to set.
* @param string $value Value to set. * @param string|null $value Value to set
* *
* @return UriInterface * @return UriInterface
*/ */
public static function withQueryValue(UriInterface $uri, $key, $value) public static function withQueryValue(UriInterface $uri, $key, $value)
{ {
$current = $uri->getQuery(); $current = $uri->getQuery();
$key = strtr($key, self::$replaceQuery);
if (!$current) { if ($current == '') {
$result = []; $result = [];
} else { } else {
$result = []; $decodedKey = rawurldecode($key);
foreach (explode('&', $current) as $part) { $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
if (explode('=', $part)[0] !== $key) { return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
$result[] = $part; });
};
}
} }
// 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) { if ($value !== null) {
$result[] = $key . '=' . strtr($value, self::$replaceQuery); $result[] = $key . '=' . strtr($value, self::$replaceQuery);
} else { } else {
@ -273,16 +261,16 @@ class Uri implements UriInterface
public function getAuthority() public function getAuthority()
{ {
if (empty($this->host)) { if ($this->host == '') {
return ''; return '';
} }
$authority = $this->host; $authority = $this->host;
if (!empty($this->userInfo)) { if ($this->userInfo != '') {
$authority = $this->userInfo . '@' . $authority; $authority = $this->userInfo . '@' . $authority;
} }
if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) { if ($this->port !== null) {
$authority .= ':' . $this->port; $authority .= ':' . $this->port;
} }
@ -306,7 +294,7 @@ class Uri implements UriInterface
public function getPath() public function getPath()
{ {
return $this->path == null ? '' : $this->path; return $this->path;
} }
public function getQuery() public function getQuery()
@ -329,14 +317,14 @@ class Uri implements UriInterface
$new = clone $this; $new = clone $this;
$new->scheme = $scheme; $new->scheme = $scheme;
$new->port = $new->filterPort($new->scheme, $new->host, $new->port); $new->port = $new->filterPort($new->port);
return $new; return $new;
} }
public function withUserInfo($user, $password = null) public function withUserInfo($user, $password = null)
{ {
$info = $user; $info = $user;
if ($password) { if ($password != '') {
$info .= ':' . $password; $info .= ':' . $password;
} }
@ -351,6 +339,8 @@ class Uri implements UriInterface
public function withHost($host) public function withHost($host)
{ {
$host = $this->filterHost($host);
if ($this->host === $host) { if ($this->host === $host) {
return $this; return $this;
} }
@ -362,7 +352,7 @@ class Uri implements UriInterface
public function withPort($port) public function withPort($port)
{ {
$port = $this->filterPort($this->scheme, $this->host, $port); $port = $this->filterPort($port);
if ($this->port === $port) { if ($this->port === $port) {
return $this; return $this;
@ -375,12 +365,6 @@ class Uri implements UriInterface
public function withPath($path) public function withPath($path)
{ {
if (!is_string($path)) {
throw new \InvalidArgumentException(
'Invalid path provided; must be a string'
);
}
$path = $this->filterPath($path); $path = $this->filterPath($path);
if ($this->path === $path) { if ($this->path === $path) {
@ -394,17 +378,6 @@ class Uri implements UriInterface
public function withQuery($query) 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); $query = $this->filterQueryAndFragment($query);
if ($this->query === $query) { if ($this->query === $query) {
@ -418,10 +391,6 @@ class Uri implements UriInterface
public function withFragment($fragment) public function withFragment($fragment)
{ {
if (substr($fragment, 0, 1) === '#') {
$fragment = substr($fragment, 1);
}
$fragment = $this->filterQueryAndFragment($fragment); $fragment = $this->filterQueryAndFragment($fragment);
if ($this->fragment === $fragment) { if ($this->fragment === $fragment) {
@ -436,7 +405,7 @@ class Uri implements UriInterface
/** /**
* Apply parse_url parts to a URI. * 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) private function applyParts(array $parts)
{ {
@ -444,9 +413,11 @@ class Uri implements UriInterface
? $this->filterScheme($parts['scheme']) ? $this->filterScheme($parts['scheme'])
: ''; : '';
$this->userInfo = isset($parts['user']) ? $parts['user'] : ''; $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
$this->host = isset($parts['host']) ? $parts['host'] : ''; $this->host = isset($parts['host'])
$this->port = !empty($parts['port']) ? $this->filterHost($parts['host'])
? $this->filterPort($this->scheme, $this->host, $parts['port']) : '';
$this->port = isset($parts['port'])
? $this->filterPort($parts['port'])
: null; : null;
$this->path = isset($parts['path']) $this->path = isset($parts['path'])
? $this->filterPath($parts['path']) ? $this->filterPath($parts['path'])
@ -476,27 +447,36 @@ class Uri implements UriInterface
{ {
$uri = ''; $uri = '';
if (!empty($scheme)) { if ($scheme != '') {
$uri .= $scheme . '://'; $uri .= $scheme . ':';
} }
if (!empty($authority)) { if ($authority != '') {
$uri .= $authority; $uri .= '//' . $authority;
} }
if ($path != null) { if ($path != '') {
// Add a leading slash if necessary. if ($path[0] !== '/') {
if ($uri && substr($path, 0, 1) !== '/') { if ($authority != '') {
$uri .= '/'; // 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; $uri .= $path;
} }
if ($query != null) { if ($query != '') {
$uri .= '?' . $query; $uri .= '?' . $query;
} }
if ($fragment != null) { if ($fragment != '') {
$uri .= '#' . $fragment; $uri .= '#' . $fragment;
} }
@ -507,70 +487,87 @@ class Uri implements UriInterface
* Is a given port non-standard for the current scheme? * Is a given port non-standard for the current scheme?
* *
* @param string $scheme * @param string $scheme
* @param string $host
* @param int $port * @param int $port
*
* @return bool * @return bool
*/ */
private static function isNonStandardPort($scheme, $host, $port) private static function isNonStandardPort($scheme, $port)
{ {
if (!$scheme && $port) { return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
return true;
}
if (!$host || !$port) {
return false;
}
return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
} }
/** /**
* @param string $scheme * @param string $scheme
* *
* @return string * @return string
*
* @throws \InvalidArgumentException If the scheme is invalid.
*/ */
private function filterScheme($scheme) private function filterScheme($scheme)
{ {
$scheme = strtolower($scheme); if (!is_string($scheme)) {
$scheme = rtrim($scheme, ':/'); throw new \InvalidArgumentException('Scheme must be a string');
}
return $scheme; return strtolower($scheme);
} }
/** /**
* @param string $scheme
* @param string $host * @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 * @return int|null
* *
* @throws \InvalidArgumentException If the port is invalid. * @throws \InvalidArgumentException If the port is invalid.
*/ */
private function filterPort($scheme, $host, $port) private function filterPort($port)
{ {
if (null !== $port) { if ($port === null) {
return null;
}
$port = (int) $port; $port = (int) $port;
if (1 > $port || 0xffff < $port) { if (1 > $port || 0xffff < $port) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
sprintf('Invalid port: %d. Must be between 1 and 65535', $port) sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
); );
} }
}
return $this->isNonStandardPort($scheme, $host, $port) ? $port : null; return self::isNonStandardPort($this->scheme, $port) ? $port : null;
} }
/** /**
* Filters the path of a URI * Filters the path of a URI
* *
* @param $path * @param string $path
* *
* @return string * @return string
*
* @throws \InvalidArgumentException If the path is invalid.
*/ */
private function filterPath($path) private function filterPath($path)
{ {
if (!is_string($path)) {
throw new \InvalidArgumentException('Path must be a string');
}
return preg_replace_callback( return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/', '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'], [$this, 'rawurlencodeMatchZero'],
$path $path
); );
@ -579,14 +576,20 @@ class Uri implements UriInterface
/** /**
* Filters the query string or fragment of a URI. * Filters the query string or fragment of a URI.
* *
* @param $str * @param string $str
* *
* @return string * @return string
*
* @throws \InvalidArgumentException If the query or fragment is invalid.
*/ */
private function filterQueryAndFragment($str) private function filterQueryAndFragment($str)
{ {
if (!is_string($str)) {
throw new \InvalidArgumentException('Query and fragment must be a string');
}
return preg_replace_callback( return preg_replace_callback(
'/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
[$this, 'rawurlencodeMatchZero'], [$this, 'rawurlencodeMatchZero'],
$str $str
); );

View file

@ -4,6 +4,7 @@ namespace GuzzleHttp\Psr7;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
@ -68,7 +69,7 @@ function uri_for($uri)
* - metadata: Array of custom metadata. * - metadata: Array of custom metadata.
* - size: Size of the stream. * - size: Size of the stream.
* *
* @param resource|string|StreamInterface $resource Entity body data * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
* @param array $options Additional options * @param array $options Additional options
* *
* @return Stream * @return Stream
@ -76,14 +77,16 @@ function uri_for($uri)
*/ */
function stream_for($resource = '', array $options = []) function stream_for($resource = '', array $options = [])
{ {
switch (gettype($resource)) { if (is_scalar($resource)) {
case 'string':
$stream = fopen('php://temp', 'r+'); $stream = fopen('php://temp', 'r+');
if ($resource !== '') { if ($resource !== '') {
fwrite($stream, $resource); fwrite($stream, $resource);
fseek($stream, 0); fseek($stream, 0);
} }
return new Stream($stream, $options); return new Stream($stream, $options);
}
switch (gettype($resource)) {
case 'resource': case 'resource':
return new Stream($resource, $options); return new Stream($resource, $options);
case 'object': case 'object':
@ -209,6 +212,14 @@ function modify_request(RequestInterface $request, array $changes)
// Remove the host header if one is on the URI // Remove the host header if one is on the URI
if ($host = $changes['uri']->getHost()) { if ($host = $changes['uri']->getHost()) {
$changes['set_headers']['Host'] = $host; $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']; $uri = $changes['uri'];
} }
@ -226,6 +237,19 @@ function modify_request(RequestInterface $request, array $changes)
$uri = $uri->withQuery($changes['query']); $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( return new Request(
isset($changes['method']) ? $changes['method'] : $request->getMethod(), isset($changes['method']) ? $changes['method'] : $request->getMethod(),
$uri, $uri,
@ -422,7 +446,7 @@ function readline(StreamInterface $stream, $maxLength = null)
} }
$buffer .= $byte; $buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached // 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; break;
} }
} }
@ -441,7 +465,7 @@ function parse_request($message)
{ {
$data = _parse_message($message); $data = _parse_message($message);
$matches = []; $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'); throw new \InvalidArgumentException('Invalid request string');
} }
$parts = explode(' ', $data['start-line'], 3); $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. * 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 * string. This function does not modify the provided keys when an array is
* encountered (like http_build_query would). * encountered (like http_build_query would).
* *
@ -553,9 +577,9 @@ function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
if ($encoding === false) { if ($encoding === false) {
$encoder = function ($str) { return $str; }; $encoder = function ($str) { return $str; };
} elseif ($encoding == PHP_QUERY_RFC3986) { } elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode'; $encoder = 'rawurlencode';
} elseif ($encoding == PHP_QUERY_RFC1738) { } elseif ($encoding === PHP_QUERY_RFC1738) {
$encoder = 'urlencode'; $encoder = 'urlencode';
} else { } else {
throw new \InvalidArgumentException('Invalid type'); throw new \InvalidArgumentException('Invalid type');

View file

@ -34,6 +34,14 @@
</conditions> </conditions>
</rule> </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, <!-- 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) http://example.com/foo will be redirected to http://www.example.com/foo)
adapt and uncomment the following: --> adapt and uncomment the following: -->