Update Guzzle to 7.9.2

https://github.com/guzzle/guzzle/releases
This commit is contained in:
wn_ 2024-10-07 20:22:01 +00:00
parent df489df309
commit 124c4e2542
70 changed files with 623 additions and 355 deletions

92
composer.lock generated
View File

@ -217,22 +217,22 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.8.1", "version": "7.9.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104" "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1", "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
@ -243,9 +243,9 @@
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2", "bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*", "ext-curl": "*",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1", "php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.36 || ^9.6.15", "phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {
@ -323,7 +323,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.8.1" "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
}, },
"funding": [ "funding": [
{ {
@ -339,28 +339,28 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-12-03T20:35:24+00:00" "time": "2024-07-24T11:22:20+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "2.0.1", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d" "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2.5 || ^8.0" "php": "^7.2.5 || ^8.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -406,7 +406,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.1" "source": "https://github.com/guzzle/promises/tree/2.0.3"
}, },
"funding": [ "funding": [
{ {
@ -422,20 +422,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-03T15:11:55+00:00" "time": "2024-07-18T10:29:17+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.6.1", "version": "2.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -449,9 +449,9 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@ -522,7 +522,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.6.1" "source": "https://github.com/guzzle/psr7/tree/2.7.0"
}, },
"funding": [ "funding": [
{ {
@ -538,7 +538,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-27T10:13:57+00:00" "time": "2024-07-18T11:15:46+00:00"
}, },
{ {
"name": "j4mie/idiorm", "name": "j4mie/idiorm",
@ -769,20 +769,20 @@
}, },
{ {
"name": "psr/http-factory", "name": "psr/http-factory",
"version": "1.0.2", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-factory.git", "url": "https://github.com/php-fig/http-factory.git",
"reference": "e616d01114759c4c489f93b099585439f795fe35" "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "e616d01114759c4c489f93b099585439f795fe35", "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0" "psr/http-message": "^1.0 || ^2.0"
}, },
"type": "library", "type": "library",
@ -806,7 +806,7 @@
"homepage": "https://www.php-fig.org/" "homepage": "https://www.php-fig.org/"
} }
], ],
"description": "Common interfaces for PSR-7 HTTP message factories", "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [ "keywords": [
"factory", "factory",
"http", "http",
@ -818,9 +818,9 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-factory/tree/1.0.2" "source": "https://github.com/php-fig/http-factory"
}, },
"time": "2023-04-10T20:10:41+00:00" "time": "2024-04-15T12:06:14+00:00"
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
@ -1051,25 +1051,25 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.0.2", "version": "v3.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0.2" "php": ">=8.1"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.0-dev" "dev-main": "3.5-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@ -1098,7 +1098,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
}, },
"funding": [ "funding": [
{ {
@ -1114,7 +1114,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-01-02T09:55:41+00:00" "time": "2024-04-18T09:32:20+00:00"
}, },
{ {
"name": "thecodingmachine/safe", "name": "thecodingmachine/safe",
@ -3339,7 +3339,7 @@
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": {},
"platform-dev": [], "platform-dev": {},
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

View File

@ -11,7 +11,7 @@ return array(
'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'), 'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Soundasleep\\' => array($vendorDir . '/soundasleep/html2text/src'), 'Soundasleep\\' => array($vendorDir . '/soundasleep/html2text/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'), 'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),

View File

@ -176,8 +176,8 @@ class ComposerStaticInit19fc2ff1c0f9a92279c7979386bb2056
), ),
'Psr\\Http\\Message\\' => 'Psr\\Http\\Message\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/http-factory/src', 0 => __DIR__ . '/..' . '/psr/http-message/src',
1 => __DIR__ . '/..' . '/psr/http-message/src', 1 => __DIR__ . '/..' . '/psr/http-factory/src',
), ),
'Psr\\Http\\Client\\' => 'Psr\\Http\\Client\\' =>
array ( array (

View File

@ -293,23 +293,23 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.8.1", "version": "7.9.2",
"version_normalized": "7.8.1.0", "version_normalized": "7.9.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104" "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1", "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
@ -320,9 +320,9 @@
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2", "bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*", "ext-curl": "*",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1", "php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.36 || ^9.6.15", "phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {
@ -330,7 +330,7 @@
"ext-intl": "Required for Internationalized Domain Name (IDN) support", "ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware" "psr/log": "Required for using the Log middleware"
}, },
"time": "2023-12-03T20:35:24+00:00", "time": "2024-07-24T11:22:20+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "bamarni-bin": {
@ -402,7 +402,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.8.1" "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
}, },
"funding": [ "funding": [
{ {
@ -422,27 +422,27 @@
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "2.0.1", "version": "2.0.3",
"version_normalized": "2.0.1.0", "version_normalized": "2.0.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d" "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2.5 || ^8.0" "php": "^7.2.5 || ^8.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"time": "2023-08-03T15:11:55+00:00", "time": "2024-07-18T10:29:17+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "bamarni-bin": {
@ -488,7 +488,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.1" "source": "https://github.com/guzzle/promises/tree/2.0.3"
}, },
"funding": [ "funding": [
{ {
@ -508,17 +508,17 @@
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.6.1", "version": "2.7.0",
"version_normalized": "2.6.1.0", "version_normalized": "2.7.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -532,14 +532,14 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
}, },
"time": "2023-08-27T10:13:57+00:00", "time": "2024-07-18T11:15:46+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "bamarni-bin": {
@ -607,7 +607,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.6.1" "source": "https://github.com/guzzle/psr7/tree/2.7.0"
}, },
"funding": [ "funding": [
{ {
@ -1844,24 +1844,24 @@
}, },
{ {
"name": "psr/http-factory", "name": "psr/http-factory",
"version": "1.0.2", "version": "1.1.0",
"version_normalized": "1.0.2.0", "version_normalized": "1.1.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-factory.git", "url": "https://github.com/php-fig/http-factory.git",
"reference": "e616d01114759c4c489f93b099585439f795fe35" "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "e616d01114759c4c489f93b099585439f795fe35", "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0" "psr/http-message": "^1.0 || ^2.0"
}, },
"time": "2023-04-10T20:10:41+00:00", "time": "2024-04-15T12:06:14+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -1884,7 +1884,7 @@
"homepage": "https://www.php-fig.org/" "homepage": "https://www.php-fig.org/"
} }
], ],
"description": "Common interfaces for PSR-7 HTTP message factories", "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [ "keywords": [
"factory", "factory",
"http", "http",
@ -1896,7 +1896,7 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-factory/tree/1.0.2" "source": "https://github.com/php-fig/http-factory"
}, },
"install-path": "../psr/http-factory" "install-path": "../psr/http-factory"
}, },
@ -3153,27 +3153,27 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.0.2", "version": "v3.5.0",
"version_normalized": "3.0.2.0", "version_normalized": "3.5.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0.2" "php": ">=8.1"
}, },
"time": "2022-01-02T09:55:41+00:00", "time": "2024-04-18T09:32:20+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.0-dev" "dev-main": "3.5-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",
@ -3203,7 +3203,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
}, },
"funding": [ "funding": [
{ {

View File

@ -3,7 +3,7 @@
'name' => '__root__', 'name' => '__root__',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'df33ddaea1e46b5b923440d6383fa3ae85c4d60b', 'reference' => 'df489df309dc831b357a9cc36fe72ad5a99d22e0',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -13,7 +13,7 @@
'__root__' => array( '__root__' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => 'df33ddaea1e46b5b923440d6383fa3ae85c4d60b', 'reference' => 'df489df309dc831b357a9cc36fe72ad5a99d22e0',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -56,27 +56,27 @@
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'guzzlehttp/guzzle' => array( 'guzzlehttp/guzzle' => array(
'pretty_version' => '7.8.1', 'pretty_version' => '7.9.2',
'version' => '7.8.1.0', 'version' => '7.9.2.0',
'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104', 'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/promises' => array( 'guzzlehttp/promises' => array(
'pretty_version' => '2.0.1', 'pretty_version' => '2.0.3',
'version' => '2.0.1.0', 'version' => '2.0.3.0',
'reference' => '111166291a0f8130081195ac4556a5587d7f1b5d', 'reference' => '6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises', 'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'guzzlehttp/psr7' => array( 'guzzlehttp/psr7' => array(
'pretty_version' => '2.6.1', 'pretty_version' => '2.7.0',
'version' => '2.6.1.0', 'version' => '2.7.0.0',
'reference' => 'be45764272e8873c72dbe3d2edcfdfcc3bc9f727', 'reference' => 'a70f5c95fb43bc83f07c9c948baa0dc1829bf201',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(), 'aliases' => array(),
@ -262,9 +262,9 @@
), ),
), ),
'psr/http-factory' => array( 'psr/http-factory' => array(
'pretty_version' => '1.0.2', 'pretty_version' => '1.1.0',
'version' => '1.0.2.0', 'version' => '1.1.0.0',
'reference' => 'e616d01114759c4c489f93b099585439f795fe35', 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory', 'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(), 'aliases' => array(),
@ -463,9 +463,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/deprecation-contracts' => array( 'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.0.2', 'pretty_version' => 'v3.5.0',
'version' => '3.0.2.0', 'version' => '3.5.0.0',
'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(), 'aliases' => array(),

View File

@ -3,6 +3,37 @@
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
## 7.9.2 - 2024-07-24
### Fixed
- Adjusted handler selection to use cURL if its version is 7.21.2 or higher, rather than 7.34.0
## 7.9.1 - 2024-07-19
### Fixed
- Fix TLS 1.3 check for HTTP/2 requests
## 7.9.0 - 2024-07-18
### Changed
- Improve protocol version checks to provide feedback around unsupported protocols
- Only select the cURL handler by default if 7.34.0 or higher is linked
- Improved `CurlMultiHandler` to avoid busy wait if possible
- Dropped support for EOL `guzzlehttp/psr7` v1
- Improved URI user info redaction in errors
## 7.8.2 - 2024-07-18
### Added
- Support for PHP 8.4
## 7.8.1 - 2023-12-03 ## 7.8.1 - 2023-12-03
### Changed ### Changed

View File

@ -62,11 +62,11 @@ composer require guzzlehttp/guzzle
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | | Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------| |---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | | 3.x | EOL (2016-10-31) | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 |
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | | 4.x | EOL (2016-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | | 5.x | EOL (2019-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | | 6.x | EOL (2023-10-31) | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.4 | | 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.5 |
[guzzle-3-repo]: https://github.com/guzzle/guzzle3 [guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x [guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x

View File

@ -50,11 +50,39 @@
"homepage": "https://github.com/Tobion" "homepage": "https://github.com/Tobion"
} }
], ],
"repositories": [
{
"type": "package",
"package": {
"name": "guzzle/client-integration-tests",
"version": "v3.0.2",
"dist": {
"url": "https://codeload.github.com/guzzle/client-integration-tests/zip/2c025848417c1135031fdf9c728ee53d0a7ceaee",
"type": "zip"
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.11",
"php-http/message": "^1.0 || ^2.0",
"guzzlehttp/psr7": "^1.7 || ^2.0",
"th3n3rd/cartesian-product": "^0.3"
},
"autoload": {
"psr-4": {
"Http\\Client\\Tests\\": "src/"
}
},
"bin": [
"bin/http_test_server"
]
}
}
],
"require": { "require": {
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1", "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "guzzlehttp/psr7": "^2.7.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
}, },
@ -64,9 +92,9 @@
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
"bamarni/composer-bin-plugin": "^1.8.2", "bamarni/composer-bin-plugin": "^1.8.2",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1", "php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.36 || ^9.6.15", "phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {

View File

@ -11,7 +11,7 @@ final class BodySummarizer implements BodySummarizerInterface
*/ */
private $truncateAt; private $truncateAt;
public function __construct(int $truncateAt = null) public function __construct(?int $truncateAt = null)
{ {
$this->truncateAt = $truncateAt; $this->truncateAt = $truncateAt;
} }
@ -22,7 +22,7 @@ final class BodySummarizer implements BodySummarizerInterface
public function summarize(MessageInterface $message): ?string public function summarize(MessageInterface $message): ?string
{ {
return $this->truncateAt === null return $this->truncateAt === null
? \GuzzleHttp\Psr7\Message::bodySummary($message) ? Psr7\Message::bodySummary($message)
: \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); : Psr7\Message::bodySummary($message, $this->truncateAt);
} }
} }

View File

@ -52,7 +52,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
* *
* @param array $config Client configuration settings. * @param array $config Client configuration settings.
* *
* @see \GuzzleHttp\RequestOptions for a list of available request options. * @see RequestOptions for a list of available request options.
*/ */
public function __construct(array $config = []) public function __construct(array $config = [])
{ {
@ -202,7 +202,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
* *
* @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/ */
public function getConfig(string $option = null) public function getConfig(?string $option = null)
{ {
return $option === null return $option === null
? $this->config ? $this->config

View File

@ -80,5 +80,5 @@ interface ClientInterface
* *
* @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0. * @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/ */
public function getConfig(string $option = null); public function getConfig(?string $option = null);
} }

View File

@ -103,7 +103,7 @@ class CookieJar implements CookieJarInterface
}, $this->getIterator()->getArrayCopy()); }, $this->getIterator()->getArrayCopy());
} }
public function clear(string $domain = null, string $path = null, string $name = null): void public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
{ {
if (!$domain) { if (!$domain) {
$this->cookies = []; $this->cookies = [];

View File

@ -62,7 +62,7 @@ interface CookieJarInterface extends \Countable, \IteratorAggregate
* @param string|null $path Clears cookies matching a domain and path * @param string|null $path Clears cookies matching a domain and path
* @param string|null $name Clears cookies matching a domain, path, and name * @param string|null $name Clears cookies matching a domain, path, and name
*/ */
public function clear(string $domain = null, string $path = null, string $name = null): void; public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void;
/** /**
* Discard all sessions cookies. * Discard all sessions cookies.

View File

@ -14,7 +14,7 @@ class BadResponseException extends RequestException
string $message, string $message,
RequestInterface $request, RequestInterface $request,
ResponseInterface $response, ResponseInterface $response,
\Throwable $previous = null, ?\Throwable $previous = null,
array $handlerContext = [] array $handlerContext = []
) { ) {
parent::__construct($message, $request, $response, $previous, $handlerContext); parent::__construct($message, $request, $response, $previous, $handlerContext);

View File

@ -25,7 +25,7 @@ class ConnectException extends TransferException implements NetworkExceptionInte
public function __construct( public function __construct(
string $message, string $message,
RequestInterface $request, RequestInterface $request,
\Throwable $previous = null, ?\Throwable $previous = null,
array $handlerContext = [] array $handlerContext = []
) { ) {
parent::__construct($message, 0, $previous); parent::__construct($message, 0, $previous);

View File

@ -7,7 +7,6 @@ use GuzzleHttp\BodySummarizerInterface;
use Psr\Http\Client\RequestExceptionInterface; use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/** /**
* HTTP Request exception * HTTP Request exception
@ -32,8 +31,8 @@ class RequestException extends TransferException implements RequestExceptionInte
public function __construct( public function __construct(
string $message, string $message,
RequestInterface $request, RequestInterface $request,
ResponseInterface $response = null, ?ResponseInterface $response = null,
\Throwable $previous = null, ?\Throwable $previous = null,
array $handlerContext = [] array $handlerContext = []
) { ) {
// Set the code of the exception if the response is set and not future. // Set the code of the exception if the response is set and not future.
@ -63,10 +62,10 @@ class RequestException extends TransferException implements RequestExceptionInte
*/ */
public static function create( public static function create(
RequestInterface $request, RequestInterface $request,
ResponseInterface $response = null, ?ResponseInterface $response = null,
\Throwable $previous = null, ?\Throwable $previous = null,
array $handlerContext = [], array $handlerContext = [],
BodySummarizerInterface $bodySummarizer = null ?BodySummarizerInterface $bodySummarizer = null
): self { ): self {
if (!$response) { if (!$response) {
return new self( return new self(
@ -90,8 +89,7 @@ class RequestException extends TransferException implements RequestExceptionInte
$className = __CLASS__; $className = __CLASS__;
} }
$uri = $request->getUri(); $uri = \GuzzleHttp\Psr7\Utils::redactUserInfo($request->getUri());
$uri = static::obfuscateUri($uri);
// Client Error: `GET /` resulted in a `404 Not Found` response: // Client Error: `GET /` resulted in a `404 Not Found` response:
// <html> ... (truncated) // <html> ... (truncated)
@ -113,20 +111,6 @@ class RequestException extends TransferException implements RequestExceptionInte
return new $className($message, $request, $response, $previous, $handlerContext); return new $className($message, $request, $response, $previous, $handlerContext);
} }
/**
* Obfuscates URI if there is a username and a password present
*/
private static function obfuscateUri(UriInterface $uri): UriInterface
{
$userInfo = $uri->getUserInfo();
if (false !== ($pos = \strpos($userInfo, ':'))) {
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
}
return $uri;
}
/** /**
* Get the request that caused the exception * Get the request that caused the exception
*/ */

View File

@ -11,6 +11,7 @@ use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats; use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils; use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/** /**
* Creates curl resources from a request * Creates curl resources from a request
@ -46,6 +47,16 @@ class CurlFactory implements CurlFactoryInterface
public function create(RequestInterface $request, array $options): EasyHandle public function create(RequestInterface $request, array $options): EasyHandle
{ {
$protocolVersion = $request->getProtocolVersion();
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
if (!self::supportsHttp2()) {
throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request);
}
} elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
throw new ConnectException(sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request);
}
if (isset($options['curl']['body_as_string'])) { if (isset($options['curl']['body_as_string'])) {
$options['_body_as_string'] = $options['curl']['body_as_string']; $options['_body_as_string'] = $options['curl']['body_as_string'];
unset($options['curl']['body_as_string']); unset($options['curl']['body_as_string']);
@ -72,6 +83,42 @@ class CurlFactory implements CurlFactoryInterface
return $easy; return $easy;
} }
private static function supportsHttp2(): bool
{
static $supportsHttp2 = null;
if (null === $supportsHttp2) {
$supportsHttp2 = self::supportsTls12()
&& defined('CURL_VERSION_HTTP2')
&& (\CURL_VERSION_HTTP2 & \curl_version()['features']);
}
return $supportsHttp2;
}
private static function supportsTls12(): bool
{
static $supportsTls12 = null;
if (null === $supportsTls12) {
$supportsTls12 = \CURL_SSLVERSION_TLSv1_2 & \curl_version()['features'];
}
return $supportsTls12;
}
private static function supportsTls13(): bool
{
static $supportsTls13 = null;
if (null === $supportsTls13) {
$supportsTls13 = defined('CURL_SSLVERSION_TLSv1_3')
&& (\CURL_SSLVERSION_TLSv1_3 & \curl_version()['features']);
}
return $supportsTls13;
}
public function release(EasyHandle $easy): void public function release(EasyHandle $easy): void
{ {
$resource = $easy->handle; $resource = $easy->handle;
@ -147,7 +194,7 @@ class CurlFactory implements CurlFactoryInterface
'error' => \curl_error($easy->handle), 'error' => \curl_error($easy->handle),
'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
] + \curl_getinfo($easy->handle); ] + \curl_getinfo($easy->handle);
$ctx[self::CURL_VERSION_STR] = \curl_version()['version']; $ctx[self::CURL_VERSION_STR] = self::getCurlVersion();
$factory->release($easy); $factory->release($easy);
// Retry when nothing is present or when curl failed to rewind. // Retry when nothing is present or when curl failed to rewind.
@ -158,6 +205,17 @@ class CurlFactory implements CurlFactoryInterface
return self::createRejection($easy, $ctx); return self::createRejection($easy, $ctx);
} }
private static function getCurlVersion(): string
{
static $curlVersion = null;
if (null === $curlVersion) {
$curlVersion = \curl_version()['version'];
}
return $curlVersion;
}
private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
{ {
static $connectionErrors = [ static $connectionErrors = [
@ -194,15 +252,22 @@ class CurlFactory implements CurlFactoryInterface
); );
} }
$uri = $easy->request->getUri();
$sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri);
$message = \sprintf( $message = \sprintf(
'cURL error %s: %s (%s)', 'cURL error %s: %s (%s)',
$ctx['errno'], $ctx['errno'],
$ctx['error'], $sanitizedError,
'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
); );
$uriString = (string) $easy->request->getUri();
if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { if ('' !== $sanitizedError) {
$message .= \sprintf(' for %s', $uriString); $redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($uri)->__toString();
if ($redactedUriString !== '' && false === \strpos($sanitizedError, $redactedUriString)) {
$message .= \sprintf(' for %s', $redactedUriString);
}
} }
// Create a connection exception if it was a specific error code. // Create a connection exception if it was a specific error code.
@ -213,6 +278,24 @@ class CurlFactory implements CurlFactoryInterface
return P\Create::rejectionFor($error); return P\Create::rejectionFor($error);
} }
private static function sanitizeCurlError(string $error, UriInterface $uri): string
{
if ('' === $error) {
return $error;
}
$baseUri = $uri->withQuery('')->withFragment('');
$baseUriString = $baseUri->__toString();
if ('' === $baseUriString) {
return $error;
}
$redactedUriString = \GuzzleHttp\Psr7\Utils::redactUserInfo($baseUri)->__toString();
return str_replace($baseUriString, $redactedUriString, $error);
}
/** /**
* @return array<int|string, mixed> * @return array<int|string, mixed>
*/ */
@ -232,10 +315,11 @@ class CurlFactory implements CurlFactoryInterface
} }
$version = $easy->request->getProtocolVersion(); $version = $easy->request->getProtocolVersion();
if ($version == 1.1) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; if ('2' === $version || '2.0' === $version) {
} elseif ($version == 2.0) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
} elseif ('1.1' === $version) {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
} else { } else {
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
} }
@ -390,8 +474,10 @@ class CurlFactory implements CurlFactoryInterface
// The empty string enables all available decoders and implicitly // The empty string enables all available decoders and implicitly
// sets a matching 'Accept-Encoding' header. // sets a matching 'Accept-Encoding' header.
$conf[\CURLOPT_ENCODING] = ''; $conf[\CURLOPT_ENCODING] = '';
// But as the user did not specify any acceptable encodings we need // But as the user did not specify any encoding preference,
// to overwrite this implicit header with an empty one. // let's leave it up to server by preventing curl from sending
// the header, which will be interpreted as 'Accept-Encoding: *'.
// https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding
$conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
} }
} }
@ -455,23 +541,35 @@ class CurlFactory implements CurlFactoryInterface
} }
if (isset($options['crypto_method'])) { if (isset($options['crypto_method'])) {
if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) { $protocolVersion = $easy->request->getProtocolVersion();
if (!defined('CURL_SSLVERSION_TLSv1_0')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL'); // If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2
if ('2' === $protocolVersion || '2.0' === $protocolVersion) {
if (
\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']
|| \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']
|| \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']
) {
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
if (!self::supportsTls13()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
} else {
throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
} }
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0; $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) { } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_1')) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
}
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1; $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
} elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) { } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_2')) { if (!self::supportsTls12()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL'); throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
} }
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2; $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
} elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) { } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
if (!defined('CURL_SSLVERSION_TLSv1_3')) { if (!self::supportsTls13()) {
throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL'); throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
} }
$conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3; $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;

View File

@ -2,6 +2,7 @@
namespace GuzzleHttp\Handler; namespace GuzzleHttp\Handler;
use Closure;
use GuzzleHttp\Promise as P; use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise; use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\PromiseInterface;
@ -159,6 +160,9 @@ class CurlMultiHandler
} }
} }
// Run curl_multi_exec in the queue to enable other async tasks to run
P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
// Step through the task queue which may add additional requests. // Step through the task queue which may add additional requests.
P\Utils::queue()->run(); P\Utils::queue()->run();
@ -169,11 +173,24 @@ class CurlMultiHandler
} }
while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) { while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
// Prevent busy looping for slow HTTP requests.
\curl_multi_select($this->_mh, $this->selectTimeout);
} }
$this->processMessages(); $this->processMessages();
} }
/**
* Runs \curl_multi_exec() inside the event loop, to prevent busy looping
*/
private function tickInQueue(): void
{
if (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
\curl_multi_select($this->_mh, 0);
P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
}
}
/** /**
* Runs until all outstanding connections have completed. * Runs until all outstanding connections have completed.
*/ */

View File

@ -52,21 +52,21 @@ class MockHandler implements \Countable
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable|null $onRejected Callback to invoke when the return value is rejected. * @param callable|null $onRejected Callback to invoke when the return value is rejected.
*/ */
public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack public static function createWithMiddleware(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null): HandlerStack
{ {
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected)); return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
} }
/** /**
* The passed in value must be an array of * The passed in value must be an array of
* {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions, * {@see ResponseInterface} objects, Exceptions,
* callables, or Promises. * callables, or Promises.
* *
* @param array<int, mixed>|null $queue The parameters to be passed to the append function, as an indexed array. * @param array<int, mixed>|null $queue The parameters to be passed to the append function, as an indexed array.
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
* @param callable|null $onRejected Callback to invoke when the return value is rejected. * @param callable|null $onRejected Callback to invoke when the return value is rejected.
*/ */
public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) public function __construct(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null)
{ {
$this->onFulfilled = $onFulfilled; $this->onFulfilled = $onFulfilled;
$this->onRejected = $onRejected; $this->onRejected = $onRejected;
@ -200,7 +200,7 @@ class MockHandler implements \Countable
private function invokeStats( private function invokeStats(
RequestInterface $request, RequestInterface $request,
array $options, array $options,
ResponseInterface $response = null, ?ResponseInterface $response = null,
$reason = null $reason = null
): void { ): void {
if (isset($options['on_stats'])) { if (isset($options['on_stats'])) {

View File

@ -40,6 +40,12 @@ class StreamHandler
\usleep($options['delay'] * 1000); \usleep($options['delay'] * 1000);
} }
$protocolVersion = $request->getProtocolVersion();
if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) {
throw new ConnectException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request);
}
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null; $startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
try { try {
@ -83,8 +89,8 @@ class StreamHandler
array $options, array $options,
RequestInterface $request, RequestInterface $request,
?float $startTime, ?float $startTime,
ResponseInterface $response = null, ?ResponseInterface $response = null,
\Throwable $error = null ?\Throwable $error = null
): void { ): void {
if (isset($options['on_stats'])) { if (isset($options['on_stats'])) {
$stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
@ -273,7 +279,7 @@ class StreamHandler
// HTTP/1.1 streams using the PHP stream wrapper require a // HTTP/1.1 streams using the PHP stream wrapper require a
// Connection: close header // Connection: close header
if ($request->getProtocolVersion() == '1.1' if ($request->getProtocolVersion() === '1.1'
&& !$request->hasHeader('Connection') && !$request->hasHeader('Connection')
) { ) {
$request = $request->withHeader('Connection', 'close'); $request = $request->withHeader('Connection', 'close');

View File

@ -44,7 +44,7 @@ class HandlerStack
* handler is provided, the best handler for your * handler is provided, the best handler for your
* system will be utilized. * system will be utilized.
*/ */
public static function create(callable $handler = null): self public static function create(?callable $handler = null): self
{ {
$stack = new self($handler ?: Utils::chooseHandler()); $stack = new self($handler ?: Utils::chooseHandler());
$stack->push(Middleware::httpErrors(), 'http_errors'); $stack->push(Middleware::httpErrors(), 'http_errors');
@ -58,7 +58,7 @@ class HandlerStack
/** /**
* @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler.
*/ */
public function __construct(callable $handler = null) public function __construct(?callable $handler = null)
{ {
$this->handler = $handler; $this->handler = $handler;
} }
@ -131,7 +131,7 @@ class HandlerStack
* @param callable(callable): callable $middleware Middleware function * @param callable(callable): callable $middleware Middleware function
* @param string $name Name to register for this middleware. * @param string $name Name to register for this middleware.
*/ */
public function unshift(callable $middleware, string $name = null): void public function unshift(callable $middleware, ?string $name = null): void
{ {
\array_unshift($this->stack, [$middleware, $name]); \array_unshift($this->stack, [$middleware, $name]);
$this->cached = null; $this->cached = null;

View File

@ -68,7 +68,7 @@ class MessageFormatter implements MessageFormatterInterface
* @param ResponseInterface|null $response Response that was received * @param ResponseInterface|null $response Response that was received
* @param \Throwable|null $error Exception that was received * @param \Throwable|null $error Exception that was received
*/ */
public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string
{ {
$cache = []; $cache = [];

View File

@ -14,5 +14,5 @@ interface MessageFormatterInterface
* @param ResponseInterface|null $response Response that was received * @param ResponseInterface|null $response Response that was received
* @param \Throwable|null $error Exception that was received * @param \Throwable|null $error Exception that was received
*/ */
public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string; public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string;
} }

View File

@ -55,7 +55,7 @@ final class Middleware
* *
* @return callable(callable): callable Returns a function that accepts the next handler. * @return callable(callable): callable Returns a function that accepts the next handler.
*/ */
public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null): callable
{ {
return static function (callable $handler) use ($bodySummarizer): callable { return static function (callable $handler) use ($bodySummarizer): callable {
return static function ($request, array $options) use ($handler, $bodySummarizer) { return static function ($request, array $options) use ($handler, $bodySummarizer) {
@ -132,7 +132,7 @@ final class Middleware
* *
* @return callable Returns a function that accepts the next handler. * @return callable Returns a function that accepts the next handler.
*/ */
public static function tap(callable $before = null, callable $after = null): callable public static function tap(?callable $before = null, ?callable $after = null): callable
{ {
return static function (callable $handler) use ($before, $after): callable { return static function (callable $handler) use ($before, $after): callable {
return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
@ -176,7 +176,7 @@ final class Middleware
* *
* @return callable Returns a function that accepts the next handler. * @return callable Returns a function that accepts the next handler.
*/ */
public static function retry(callable $decider, callable $delay = null): callable public static function retry(callable $decider, ?callable $delay = null): callable
{ {
return static function (callable $handler) use ($decider, $delay): RetryMiddleware { return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
return new RetryMiddleware($decider, $handler, $delay); return new RetryMiddleware($decider, $handler, $delay);

View File

@ -76,8 +76,8 @@ class PrepareBodyMiddleware
$expect = $options['expect'] ?? null; $expect = $options['expect'] ?? null;
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 // Return if disabled or using HTTP/1.0
if ($expect === false || $request->getProtocolVersion() < 1.1) { if ($expect === false || $request->getProtocolVersion() === '1.0') {
return; return;
} }

View File

@ -61,7 +61,7 @@ final class RequestOptions
* Specifies whether or not cookies are used in a request or what cookie * Specifies whether or not cookies are used in a request or what cookie
* jar to use or what cookies to send. This option only works if your * jar to use or what cookies to send. This option only works if your
* handler has the `cookie` middleware. Valid values are `false` and * handler has the `cookie` middleware. Valid values are `false` and
* an instance of {@see \GuzzleHttp\Cookie\CookieJarInterface}. * an instance of {@see Cookie\CookieJarInterface}.
*/ */
public const COOKIES = 'cookies'; public const COOKIES = 'cookies';

View File

@ -40,7 +40,7 @@ class RetryMiddleware
* and returns the number of * and returns the number of
* milliseconds to delay. * milliseconds to delay.
*/ */
public function __construct(callable $decider, callable $nextHandler, callable $delay = null) public function __construct(callable $decider, callable $nextHandler, ?callable $delay = null)
{ {
$this->decider = $decider; $this->decider = $decider;
$this->nextHandler = $nextHandler; $this->nextHandler = $nextHandler;
@ -110,7 +110,7 @@ class RetryMiddleware
}; };
} }
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null): PromiseInterface
{ {
$options['delay'] = ($this->delay)(++$options['retries'], $response, $request); $options['delay'] = ($this->delay)(++$options['retries'], $response, $request);

View File

@ -46,8 +46,8 @@ final class TransferStats
*/ */
public function __construct( public function __construct(
RequestInterface $request, RequestInterface $request,
ResponseInterface $response = null, ?ResponseInterface $response = null,
float $transferTime = null, ?float $transferTime = null,
$handlerErrorData = null, $handlerErrorData = null,
array $handlerStats = [] array $handlerStats = []
) { ) {

View File

@ -71,7 +71,7 @@ final class Utils
return \STDOUT; return \STDOUT;
} }
return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w'); return Psr7\Utils::tryFopen('php://output', 'w');
} }
/** /**
@ -87,7 +87,7 @@ final class Utils
{ {
$handler = null; $handler = null;
if (\defined('CURLOPT_CUSTOMREQUEST')) { if (\defined('CURLOPT_CUSTOMREQUEST') && \function_exists('curl_version') && version_compare(curl_version()['version'], '7.21.2') >= 0) {
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) { 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')) { } elseif (\function_exists('curl_exec')) {

View File

@ -1,6 +1,20 @@
# CHANGELOG # CHANGELOG
## 2.0.3 - 2024-07-18
### Changed
- PHP 8.4 support
## 2.0.2 - 2023-12-03
### Changed
- Replaced `call_user_func*` with native calls
## 2.0.1 - 2023-08-03 ## 2.0.1 - 2023-08-03
### Changed ### Changed

View File

@ -38,10 +38,10 @@ composer require guzzlehttp/promises
## Version Guidance ## Version Guidance
| Version | Status | PHP Version | | Version | Status | PHP Version |
|---------|------------------------|--------------| |---------|---------------------|--------------|
| 1.x | Bug and security fixes | >=5.5,<8.3 | | 1.x | Security fixes only | >=5.5,<8.3 |
| 2.x | Latest | >=7.2.5,<8.4 | | 2.x | Latest | >=7.2.5,<8.5 |
## Quick Start ## Quick Start

View File

@ -29,8 +29,8 @@
"php": "^7.2.5 || ^8.0" "php": "^7.2.5 || ^8.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -84,8 +84,8 @@ final class Coroutine implements PromiseInterface
} }
public function then( public function then(
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
return $this->result->then($onFulfilled, $onRejected); return $this->result->then($onFulfilled, $onRejected);
} }

View File

@ -19,14 +19,12 @@ final class Each
* index, and the aggregate promise. The callback can invoke any necessary * index, and the aggregate promise. The callback can invoke any necessary
* side effects and choose to resolve or reject the aggregate if needed. * side effects and choose to resolve or reject the aggregate if needed.
* *
* @param mixed $iterable Iterator or array to iterate over. * @param mixed $iterable Iterator or array to iterate over.
* @param callable $onFulfilled
* @param callable $onRejected
*/ */
public static function of( public static function of(
$iterable, $iterable,
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
return (new EachPromise($iterable, [ return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled, 'fulfilled' => $onFulfilled,
@ -44,14 +42,12 @@ final class Each
* *
* @param mixed $iterable * @param mixed $iterable
* @param int|callable $concurrency * @param int|callable $concurrency
* @param callable $onFulfilled
* @param callable $onRejected
*/ */
public static function ofLimit( public static function ofLimit(
$iterable, $iterable,
$concurrency, $concurrency,
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
return (new EachPromise($iterable, [ return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled, 'fulfilled' => $onFulfilled,
@ -67,12 +63,11 @@ final class Each
* *
* @param mixed $iterable * @param mixed $iterable
* @param int|callable $concurrency * @param int|callable $concurrency
* @param callable $onFulfilled
*/ */
public static function ofLimitAll( public static function ofLimitAll(
$iterable, $iterable,
$concurrency, $concurrency,
callable $onFulfilled = null ?callable $onFulfilled = null
): PromiseInterface { ): PromiseInterface {
return self::ofLimit( return self::ofLimit(
$iterable, $iterable,

View File

@ -135,7 +135,7 @@ class EachPromise implements PromisorInterface
// Add only up to N pending promises. // Add only up to N pending promises.
$concurrency = is_callable($this->concurrency) $concurrency = is_callable($this->concurrency)
? call_user_func($this->concurrency, count($this->pending)) ? ($this->concurrency)(count($this->pending))
: $this->concurrency; : $this->concurrency;
$concurrency = max($concurrency - count($this->pending), 0); $concurrency = max($concurrency - count($this->pending), 0);
// Concurrency may be set to 0 to disallow new promises. // Concurrency may be set to 0 to disallow new promises.
@ -170,8 +170,7 @@ class EachPromise implements PromisorInterface
$this->pending[$idx] = $promise->then( $this->pending[$idx] = $promise->then(
function ($value) use ($idx, $key): void { function ($value) use ($idx, $key): void {
if ($this->onFulfilled) { if ($this->onFulfilled) {
call_user_func( ($this->onFulfilled)(
$this->onFulfilled,
$value, $value,
$key, $key,
$this->aggregate $this->aggregate
@ -181,8 +180,7 @@ class EachPromise implements PromisorInterface
}, },
function ($reason) use ($idx, $key): void { function ($reason) use ($idx, $key): void {
if ($this->onRejected) { if ($this->onRejected) {
call_user_func( ($this->onRejected)(
$this->onRejected,
$reason, $reason,
$key, $key,
$this->aggregate $this->aggregate

View File

@ -31,8 +31,8 @@ class FulfilledPromise implements PromiseInterface
} }
public function then( public function then(
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
// Return itself if there is no onFulfilled function. // Return itself if there is no onFulfilled function.
if (!$onFulfilled) { if (!$onFulfilled) {

View File

@ -25,16 +25,16 @@ class Promise implements PromiseInterface
* @param callable $cancelFn Fn that when invoked cancels the promise. * @param callable $cancelFn Fn that when invoked cancels the promise.
*/ */
public function __construct( public function __construct(
callable $waitFn = null, ?callable $waitFn = null,
callable $cancelFn = null ?callable $cancelFn = null
) { ) {
$this->waitFn = $waitFn; $this->waitFn = $waitFn;
$this->cancelFn = $cancelFn; $this->cancelFn = $cancelFn;
} }
public function then( public function then(
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
if ($this->state === self::PENDING) { if ($this->state === self::PENDING) {
$p = new Promise(null, [$this, 'cancel']); $p = new Promise(null, [$this, 'cancel']);

View File

@ -27,8 +27,8 @@ interface PromiseInterface
* @param callable $onRejected Invoked when the promise is rejected. * @param callable $onRejected Invoked when the promise is rejected.
*/ */
public function then( public function then(
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface; ): PromiseInterface;
/** /**

View File

@ -31,8 +31,8 @@ class RejectedPromise implements PromiseInterface
} }
public function then( public function then(
callable $onFulfilled = null, ?callable $onFulfilled = null,
callable $onRejected = null ?callable $onRejected = null
): PromiseInterface { ): PromiseInterface {
// If there's no onRejected callback then just return self. // If there's no onRejected callback then just return self.
if (!$onRejected) { if (!$onRejected) {

View File

@ -21,7 +21,7 @@ final class Utils
* *
* @param TaskQueueInterface|null $assign Optionally specify a new queue instance. * @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
*/ */
public static function queue(TaskQueueInterface $assign = null): TaskQueueInterface public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface
{ {
static $queue; static $queue;

View File

@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.7.0 - 2024-07-18
### Added
- Add `Utils::redactUserInfo()` method
- Add ability to encode bools as ints in `Query::build`
## 2.6.3 - 2024-07-18
### Fixed
- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null`
### Changed
- PHP 8.4 support
## 2.6.2 - 2023-12-03
### Fixed
- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints
### Changed
- Updated links in docs to their canonical versions
- Replaced `call_user_func*` with native calls
## 2.6.1 - 2023-08-27 ## 2.6.1 - 2023-08-27
### Fixed ### Fixed

View File

@ -24,8 +24,8 @@ composer require guzzlehttp/psr7
| Version | Status | PHP Version | | Version | Status | PHP Version |
|---------|---------------------|--------------| |---------|---------------------|--------------|
| 1.x | Security fixes only | >=5.4,<8.1 | | 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.4 | | 2.x | Latest | >=7.2.5,<8.5 |
## AppendStream ## AppendStream
@ -273,7 +273,7 @@ class EofCallbackStream implements StreamInterface
// Invoke the callback when EOF is hit. // Invoke the callback when EOF is hit.
if ($this->eof()) { if ($this->eof()) {
call_user_func($this->callback); ($this->callback)();
} }
return $result; return $result;
@ -436,7 +436,7 @@ will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
## `GuzzleHttp\Psr7\Query::build` ## `GuzzleHttp\Psr7\Query::build`
`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` `public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string`
Build a query string from an array of key value pairs. Build a query string from an array of key value pairs.
@ -498,11 +498,18 @@ a message.
## `GuzzleHttp\Psr7\Utils::readLine` ## `GuzzleHttp\Psr7\Utils::readLine`
`public static function readLine(StreamInterface $stream, int $maxLength = null): string` `public static function readLine(StreamInterface $stream, ?int $maxLength = null): string`
Read a line from the stream up to the maximum allowed buffer length. Read a line from the stream up to the maximum allowed buffer length.
## `GuzzleHttp\Psr7\Utils::redactUserInfo`
`public static function redactUserInfo(UriInterface $uri): UriInterface`
Redact the password in the user info part of a URI.
## `GuzzleHttp\Psr7\Utils::streamFor` ## `GuzzleHttp\Psr7\Utils::streamFor`
`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` `public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`
@ -637,7 +644,7 @@ this library also provides additional functionality when working with URIs as st
An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
the base URI. Relative references can be divided into several forms according to the base URI. Relative references can be divided into several forms according to
[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): [RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2):
- network-path references, e.g. `//example.com/path` - network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path` - absolute-path references, e.g. `/path`
@ -674,7 +681,7 @@ termed a relative-path reference.
### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` ### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` `public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool`
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
@ -696,8 +703,8 @@ or the standard port. This method can be used independently of the implementatio
`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` `public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
Composes a URI reference string from its various components according to Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called [RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need
manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
### `GuzzleHttp\Psr7\Uri::fromParts` ### `GuzzleHttp\Psr7\Uri::fromParts`
@ -741,8 +748,8 @@ Determines if a modified URL should be considered cross-origin with respect to a
## Reference Resolution ## Reference Resolution
`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according `GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web
do when resolving a link in a website based on the current request URI. browsers do when resolving a link in a website based on the current request URI.
### `GuzzleHttp\Psr7\UriResolver::resolve` ### `GuzzleHttp\Psr7\UriResolver::resolve`
@ -755,7 +762,7 @@ Converts the relative URI into a new URI that is resolved against the base URI.
`public static function removeDotSegments(string $path): string` `public static function removeDotSegments(string $path): string`
Removes dot segments from a path and returns the new path according to Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). [RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4).
### `GuzzleHttp\Psr7\UriResolver::relativize` ### `GuzzleHttp\Psr7\UriResolver::relativize`
@ -781,7 +788,7 @@ echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // pr
## Normalization and Comparison ## Normalization and Comparison
`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to `GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). [RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6).
### `GuzzleHttp\Psr7\UriNormalizer::normalize` ### `GuzzleHttp\Psr7\UriNormalizer::normalize`

View File

@ -60,9 +60,9 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.39 || ^9.6.20"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"

View File

@ -33,7 +33,7 @@ final class CachingStream implements StreamInterface
*/ */
public function __construct( public function __construct(
StreamInterface $stream, StreamInterface $stream,
StreamInterface $target = null ?StreamInterface $target = null
) { ) {
$this->remoteStream = $stream; $this->remoteStream = $stream;
$this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));

View File

@ -54,7 +54,7 @@ final class FnStream implements StreamInterface
public function __destruct() public function __destruct()
{ {
if (isset($this->_fn_close)) { if (isset($this->_fn_close)) {
call_user_func($this->_fn_close); ($this->_fn_close)();
} }
} }
@ -93,7 +93,8 @@ final class FnStream implements StreamInterface
public function __toString(): string public function __toString(): string
{ {
try { try {
return call_user_func($this->_fn___toString); /** @var string */
return ($this->_fn___toString)();
} catch (\Throwable $e) { } catch (\Throwable $e) {
if (\PHP_VERSION_ID >= 70400) { if (\PHP_VERSION_ID >= 70400) {
throw $e; throw $e;
@ -106,67 +107,67 @@ final class FnStream implements StreamInterface
public function close(): void public function close(): void
{ {
call_user_func($this->_fn_close); ($this->_fn_close)();
} }
public function detach() public function detach()
{ {
return call_user_func($this->_fn_detach); return ($this->_fn_detach)();
} }
public function getSize(): ?int public function getSize(): ?int
{ {
return call_user_func($this->_fn_getSize); return ($this->_fn_getSize)();
} }
public function tell(): int public function tell(): int
{ {
return call_user_func($this->_fn_tell); return ($this->_fn_tell)();
} }
public function eof(): bool public function eof(): bool
{ {
return call_user_func($this->_fn_eof); return ($this->_fn_eof)();
} }
public function isSeekable(): bool public function isSeekable(): bool
{ {
return call_user_func($this->_fn_isSeekable); return ($this->_fn_isSeekable)();
} }
public function rewind(): void public function rewind(): void
{ {
call_user_func($this->_fn_rewind); ($this->_fn_rewind)();
} }
public function seek($offset, $whence = SEEK_SET): void public function seek($offset, $whence = SEEK_SET): void
{ {
call_user_func($this->_fn_seek, $offset, $whence); ($this->_fn_seek)($offset, $whence);
} }
public function isWritable(): bool public function isWritable(): bool
{ {
return call_user_func($this->_fn_isWritable); return ($this->_fn_isWritable)();
} }
public function write($string): int public function write($string): int
{ {
return call_user_func($this->_fn_write, $string); return ($this->_fn_write)($string);
} }
public function isReadable(): bool public function isReadable(): bool
{ {
return call_user_func($this->_fn_isReadable); return ($this->_fn_isReadable)();
} }
public function read($length): string public function read($length): string
{ {
return call_user_func($this->_fn_read, $length); return ($this->_fn_read)($length);
} }
public function getContents(): string public function getContents(): string
{ {
return call_user_func($this->_fn_getContents); return ($this->_fn_getContents)();
} }
/** /**
@ -174,6 +175,6 @@ final class FnStream implements StreamInterface
*/ */
public function getMetadata($key = null) public function getMetadata($key = null)
{ {
return call_user_func($this->_fn_getMetadata, $key); return ($this->_fn_getMetadata)($key);
} }
} }

View File

@ -22,7 +22,7 @@ final class Header
foreach ((array) $header as $value) { foreach ((array) $header as $value) {
foreach (self::splitList($value) as $val) { foreach (self::splitList($value) as $val) {
$part = []; $part = [];
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) ?: [] as $kvp) {
if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
$m = $matches[0]; $m = $matches[0];
if (isset($m[1])) { if (isset($m[1])) {

View File

@ -27,10 +27,10 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter
{ {
public function createUploadedFile( public function createUploadedFile(
StreamInterface $stream, StreamInterface $stream,
int $size = null, ?int $size = null,
int $error = \UPLOAD_ERR_OK, int $error = \UPLOAD_ERR_OK,
string $clientFilename = null, ?string $clientFilename = null,
string $clientMediaType = null ?string $clientMediaType = null
): UploadedFileInterface { ): UploadedFileInterface {
if ($size === null) { if ($size === null) {
$size = $stream->getSize(); $size = $stream->getSize();

View File

@ -13,9 +13,9 @@ use Psr\Http\Message\StreamInterface;
* then appends the zlib.inflate filter. The stream is then converted back * then appends the zlib.inflate filter. The stream is then converted back
* to a Guzzle stream resource to be used as a Guzzle stream. * to a Guzzle stream resource to be used as a Guzzle stream.
* *
* @see http://tools.ietf.org/html/rfc1950 * @see https://datatracker.ietf.org/doc/html/rfc1950
* @see http://tools.ietf.org/html/rfc1952 * @see https://datatracker.ietf.org/doc/html/rfc1952
* @see http://php.net/manual/en/filters.compression.php * @see https://www.php.net/manual/en/filters.compression.php
*/ */
final class InflateStream implements StreamInterface final class InflateStream implements StreamInterface
{ {
@ -28,7 +28,7 @@ final class InflateStream implements StreamInterface
{ {
$resource = StreamWrapper::getResource($stream); $resource = StreamWrapper::getResource($stream);
// Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data // Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data
// See http://www.zlib.net/manual.html#Advanced definition of inflateInit2 // See https://www.zlib.net/manual.html#Advanced definition of inflateInit2
// "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection" // "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
// Default window size is 15. // Default window size is 15.
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]); stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]);

View File

@ -146,7 +146,7 @@ final class Message
// If these aren't the same, then one line didn't match and there's an invalid header. // If these aren't the same, then one line didn't match and there's an invalid header.
if ($count !== substr_count($rawHeaders, "\n")) { if ($count !== substr_count($rawHeaders, "\n")) {
// Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
} }
@ -227,9 +227,9 @@ final class Message
public static function parseResponse(string $message): ResponseInterface public static function parseResponse(string $message): ResponseInterface
{ {
$data = self::parseMessage($message); $data = self::parseMessage($message);
// According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
// between status-code and reason-phrase is required. But browsers accept // the space between status-code and reason-phrase is required. But
// responses without space and reason as well. // browsers accept responses without space and reason as well.
if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']); throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
} }

View File

@ -141,7 +141,7 @@ trait MessageTrait
} }
/** /**
* @param array<string|int, string|string[]> $headers * @param (string|string[])[] $headers
*/ */
private function setHeaders(array $headers): void private function setHeaders(array $headers): void
{ {
@ -193,7 +193,7 @@ trait MessageTrait
* *
* @return string[] Trimmed header values * @return string[] Trimmed header values
* *
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
*/ */
private function trimAndValidateHeaderValues(array $values): array private function trimAndValidateHeaderValues(array $values): array
{ {
@ -213,7 +213,7 @@ trait MessageTrait
} }
/** /**
* @see https://tools.ietf.org/html/rfc7230#section-3.2 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
* *
* @param mixed $header * @param mixed $header
*/ */
@ -234,7 +234,7 @@ trait MessageTrait
} }
/** /**
* @see https://tools.ietf.org/html/rfc7230#section-3.2 * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
* *
* field-value = *( field-content / obs-fold ) * field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]

View File

@ -32,7 +32,7 @@ final class MultipartStream implements StreamInterface
* *
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function __construct(array $elements = [], string $boundary = null) public function __construct(array $elements = [], ?string $boundary = null)
{ {
$this->boundary = $boundary ?: bin2hex(random_bytes(20)); $this->boundary = $boundary ?: bin2hex(random_bytes(20));
$this->stream = $this->createStream($elements); $this->stream = $this->createStream($elements);
@ -51,7 +51,7 @@ final class MultipartStream implements StreamInterface
/** /**
* Get the headers needed before transferring the content of a POST file * Get the headers needed before transferring the content of a POST file
* *
* @param array<string, string> $headers * @param string[] $headers
*/ */
private function getHeaders(array $headers): string private function getHeaders(array $headers): string
{ {
@ -112,10 +112,15 @@ final class MultipartStream implements StreamInterface
$stream->addStream(Utils::streamFor("\r\n")); $stream->addStream(Utils::streamFor("\r\n"));
} }
/**
* @param string[] $headers
*
* @return array{0: StreamInterface, 1: string[]}
*/
private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
{ {
// 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 = self::getHeader($headers, 'content-disposition');
if (!$disposition) { if (!$disposition) {
$headers['Content-Disposition'] = ($filename === '0' || $filename) $headers['Content-Disposition'] = ($filename === '0' || $filename)
? sprintf( ? sprintf(
@ -127,7 +132,7 @@ final class MultipartStream implements StreamInterface
} }
// Set a default content-length header if one was no provided // Set a default content-length header if one was no provided
$length = $this->getHeader($headers, 'content-length'); $length = self::getHeader($headers, 'content-length');
if (!$length) { if (!$length) {
if ($length = $stream->getSize()) { if ($length = $stream->getSize()) {
$headers['Content-Length'] = (string) $length; $headers['Content-Length'] = (string) $length;
@ -135,7 +140,7 @@ final 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 = self::getHeader($headers, 'content-type');
if (!$type && ($filename === '0' || $filename)) { if (!$type && ($filename === '0' || $filename)) {
$headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream'; $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
} }
@ -143,11 +148,14 @@ final class MultipartStream implements StreamInterface
return [$stream, $headers]; return [$stream, $headers];
} }
private function getHeader(array $headers, string $key) /**
* @param string[] $headers
*/
private static function getHeader(array $headers, string $key): ?string
{ {
$lowercaseHeader = strtolower($key); $lowercaseHeader = strtolower($key);
foreach ($headers as $k => $v) { foreach ($headers as $k => $v) {
if (strtolower($k) === $lowercaseHeader) { if (strtolower((string) $k) === $lowercaseHeader) {
return $v; return $v;
} }
} }

View File

@ -18,7 +18,7 @@ use Psr\Http\Message\StreamInterface;
*/ */
final class PumpStream implements StreamInterface final class PumpStream implements StreamInterface
{ {
/** @var callable|null */ /** @var callable(int): (string|false|null)|null */
private $source; private $source;
/** @var int|null */ /** @var int|null */
@ -163,9 +163,9 @@ final class PumpStream implements StreamInterface
private function pump(int $length): void private function pump(int $length): void
{ {
if ($this->source) { if ($this->source !== null) {
do { do {
$data = call_user_func($this->source, $length); $data = ($this->source)($length);
if ($data === false || $data === null) { if ($data === false || $data === null) {
$this->source = null; $this->source = null;

View File

@ -63,12 +63,15 @@ final class 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).
* *
* @param array $params Query string parameters. * @param array $params Query string parameters.
* @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 * @param int|false $encoding Set to false to not encode,
* to encode using RFC3986, or PHP_QUERY_RFC1738 * PHP_QUERY_RFC3986 to encode using
* to encode using RFC1738. * RFC3986, or PHP_QUERY_RFC1738 to
* encode using RFC1738.
* @param bool $treatBoolsAsInts Set to true to encode as 0/1, and
* false as false/true.
*/ */
public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string public static function build(array $params, $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string
{ {
if (!$params) { if (!$params) {
return ''; return '';
@ -86,12 +89,14 @@ final class Query
throw new \InvalidArgumentException('Invalid type'); throw new \InvalidArgumentException('Invalid type');
} }
$castBool = $treatBoolsAsInts ? static function ($v) { return (int) $v; } : static function ($v) { return $v ? 'true' : 'false'; };
$qs = ''; $qs = '';
foreach ($params as $k => $v) { foreach ($params as $k => $v) {
$k = $encoder((string) $k); $k = $encoder((string) $k);
if (!is_array($v)) { if (!is_array($v)) {
$qs .= $k; $qs .= $k;
$v = is_bool($v) ? (int) $v : $v; $v = is_bool($v) ? $castBool($v) : $v;
if ($v !== null) { if ($v !== null) {
$qs .= '='.$encoder((string) $v); $qs .= '='.$encoder((string) $v);
} }
@ -99,7 +104,7 @@ final class Query
} else { } else {
foreach ($v as $vv) { foreach ($v as $vv) {
$qs .= $k; $qs .= $k;
$vv = is_bool($vv) ? (int) $vv : $vv; $vv = is_bool($vv) ? $castBool($vv) : $vv;
if ($vv !== null) { if ($vv !== null) {
$qs .= '='.$encoder((string) $vv); $qs .= '='.$encoder((string) $vv);
} }

View File

@ -28,7 +28,7 @@ class Request implements RequestInterface
/** /**
* @param string $method HTTP method * @param string $method HTTP method
* @param string|UriInterface $uri URI * @param string|UriInterface $uri URI
* @param array<string, string|string[]> $headers Request headers * @param (string|string[])[] $headers Request headers
* @param string|resource|StreamInterface|null $body Request body * @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version * @param string $version Protocol version
*/ */
@ -143,7 +143,7 @@ class Request implements RequestInterface
$this->headerNames['host'] = 'Host'; $this->headerNames['host'] = 'Host';
} }
// Ensure Host is the first header. // Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4 // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers; $this->headers = [$header => [$host]] + $this->headers;
} }

View File

@ -86,7 +86,7 @@ class Response implements ResponseInterface
/** /**
* @param int $status Status code * @param int $status Status code
* @param array<string, string|string[]> $headers Response headers * @param (string|string[])[] $headers Response headers
* @param string|resource|StreamInterface|null $body Response body * @param string|resource|StreamInterface|null $body Response body
* @param string $version Protocol version * @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code) * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
@ -96,7 +96,7 @@ class Response implements ResponseInterface
array $headers = [], array $headers = [],
$body = null, $body = null,
string $version = '1.1', string $version = '1.1',
string $reason = null ?string $reason = null
) { ) {
$this->assertStatusCodeRange($status); $this->assertStatusCodeRange($status);

View File

@ -59,7 +59,7 @@ class ServerRequest extends Request implements ServerRequestInterface
/** /**
* @param string $method HTTP method * @param string $method HTTP method
* @param string|UriInterface $uri URI * @param string|UriInterface $uri URI
* @param array<string, string|string[]> $headers Request headers * @param (string|string[])[] $headers Request headers
* @param string|resource|StreamInterface|null $body Request body * @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version * @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal * @param array $serverParams Typically the $_SERVER superglobal

View File

@ -12,8 +12,8 @@ use Psr\Http\Message\StreamInterface;
class Stream implements StreamInterface class Stream implements StreamInterface
{ {
/** /**
* @see http://php.net/manual/function.fopen.php * @see https://www.php.net/manual/en/function.fopen.php
* @see http://php.net/manual/en/function.gzopen.php * @see https://www.php.net/manual/en/function.gzopen.php
*/ */
private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/'; private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/'; private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';

View File

@ -70,7 +70,7 @@ trait StreamDecoratorTrait
{ {
/** @var callable $callable */ /** @var callable $callable */
$callable = [$this->stream, $method]; $callable = [$this->stream, $method];
$result = call_user_func_array($callable, $args); $result = ($callable)(...$args);
// Always return the wrapped object if the result is a return $this // Always return the wrapped object if the result is a return $this
return $result === $this->stream ? $this : $result; return $result === $this->stream ? $this : $result;

View File

@ -69,7 +69,7 @@ final class StreamWrapper
} }
} }
public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
{ {
$options = stream_context_get_options($this->context); $options = stream_context_get_options($this->context);
@ -122,10 +122,28 @@ final class StreamWrapper
} }
/** /**
* @return array<int|string, int> * @return array{
* dev: int,
* ino: int,
* mode: int,
* nlink: int,
* uid: int,
* gid: int,
* rdev: int,
* size: int,
* atime: int,
* mtime: int,
* ctime: int,
* blksize: int,
* blocks: int
* }|false
*/ */
public function stream_stat(): array public function stream_stat()
{ {
if ($this->stream->getSize() === null) {
return false;
}
static $modeMap = [ static $modeMap = [
'r' => 33060, 'r' => 33060,
'rb' => 33060, 'rb' => 33060,
@ -152,7 +170,21 @@ final class StreamWrapper
} }
/** /**
* @return array<int|string, int> * @return array{
* dev: int,
* ino: int,
* mode: int,
* nlink: int,
* uid: int,
* gid: int,
* rdev: int,
* size: int,
* atime: int,
* mtime: int,
* ctime: int,
* blksize: int,
* blocks: int
* }
*/ */
public function url_stat(string $path, int $flags): array public function url_stat(string $path, int $flags): array
{ {

View File

@ -64,8 +64,8 @@ class UploadedFile implements UploadedFileInterface
$streamOrFile, $streamOrFile,
?int $size, ?int $size,
int $errorStatus, int $errorStatus,
string $clientFilename = null, ?string $clientFilename = null,
string $clientMediaType = null ?string $clientMediaType = null
) { ) {
$this->setError($errorStatus); $this->setError($errorStatus);
$this->size = $size; $this->size = $size;
@ -113,7 +113,7 @@ class UploadedFile implements UploadedFileInterface
$this->error = $error; $this->error = $error;
} }
private function isStringNotEmpty($param): bool private static function isStringNotEmpty($param): bool
{ {
return is_string($param) && false === empty($param); return is_string($param) && false === empty($param);
} }
@ -163,7 +163,7 @@ class UploadedFile implements UploadedFileInterface
{ {
$this->validateActive(); $this->validateActive();
if (false === $this->isStringNotEmpty($targetPath)) { if (false === self::isStringNotEmpty($targetPath)) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a non-empty string' 'Invalid path provided for move operation; must be a non-empty string'
); );

View File

@ -41,14 +41,14 @@ class Uri implements UriInterface, \JsonSerializable
/** /**
* Unreserved characters for use in a regex. * Unreserved characters for use in a regex.
* *
* @see https://tools.ietf.org/html/rfc3986#section-2.3 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
*/ */
private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
/** /**
* Sub-delims for use in a regex. * Sub-delims for use in a regex.
* *
* @see https://tools.ietf.org/html/rfc3986#section-2.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
*/ */
private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
@ -162,7 +162,7 @@ class Uri implements UriInterface, \JsonSerializable
* `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
* that format). * that format).
* *
* @see https://tools.ietf.org/html/rfc3986#section-5.3 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
*/ */
public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
{ {
@ -219,7 +219,7 @@ class Uri implements UriInterface, \JsonSerializable
* @see Uri::isNetworkPathReference * @see Uri::isNetworkPathReference
* @see Uri::isAbsolutePathReference * @see Uri::isAbsolutePathReference
* @see Uri::isRelativePathReference * @see Uri::isRelativePathReference
* @see https://tools.ietf.org/html/rfc3986#section-4 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
*/ */
public static function isAbsolute(UriInterface $uri): bool public static function isAbsolute(UriInterface $uri): bool
{ {
@ -231,7 +231,7 @@ class Uri implements UriInterface, \JsonSerializable
* *
* A relative reference that begins with two slash characters is termed an network-path reference. * A relative reference that begins with two slash characters is termed an network-path reference.
* *
* @see https://tools.ietf.org/html/rfc3986#section-4.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/ */
public static function isNetworkPathReference(UriInterface $uri): bool public static function isNetworkPathReference(UriInterface $uri): bool
{ {
@ -243,7 +243,7 @@ class Uri implements UriInterface, \JsonSerializable
* *
* A relative reference that begins with a single slash character is termed an absolute-path reference. * A relative reference that begins with a single slash character is termed an absolute-path reference.
* *
* @see https://tools.ietf.org/html/rfc3986#section-4.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/ */
public static function isAbsolutePathReference(UriInterface $uri): bool public static function isAbsolutePathReference(UriInterface $uri): bool
{ {
@ -258,7 +258,7 @@ class Uri implements UriInterface, \JsonSerializable
* *
* A relative reference that does not begin with a slash character is termed a relative-path reference. * A relative reference that does not begin with a slash character is termed a relative-path reference.
* *
* @see https://tools.ietf.org/html/rfc3986#section-4.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
*/ */
public static function isRelativePathReference(UriInterface $uri): bool public static function isRelativePathReference(UriInterface $uri): bool
{ {
@ -277,9 +277,9 @@ class Uri implements UriInterface, \JsonSerializable
* @param UriInterface $uri The URI to check * @param UriInterface $uri The URI to check
* @param UriInterface|null $base An optional base URI to compare against * @param UriInterface|null $base An optional base URI to compare against
* *
* @see https://tools.ietf.org/html/rfc3986#section-4.4 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
*/ */
public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
{ {
if ($base !== null) { if ($base !== null) {
$uri = UriResolver::resolve($base, $uri); $uri = UriResolver::resolve($base, $uri);
@ -336,8 +336,8 @@ class Uri implements UriInterface, \JsonSerializable
* *
* It has the same behavior as withQueryValue() but for an associative array of key => value. * It has the same behavior as withQueryValue() but for an associative array of key => value.
* *
* @param UriInterface $uri URI to use as a base. * @param UriInterface $uri URI to use as a base.
* @param array<string, string|null> $keyValueArray Associative array of key and values * @param (string|null)[] $keyValueArray Associative array of key and values
*/ */
public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
{ {
@ -353,7 +353,7 @@ class Uri implements UriInterface, \JsonSerializable
/** /**
* Creates a URI from a hash of `parse_url` components. * Creates a URI from a hash of `parse_url` components.
* *
* @see http://php.net/manual/en/function.parse-url.php * @see https://www.php.net/manual/en/function.parse-url.php
* *
* @throws MalformedUriException If the components do not form a valid URI. * @throws MalformedUriException If the components do not form a valid URI.
*/ */
@ -638,7 +638,7 @@ class Uri implements UriInterface, \JsonSerializable
} }
/** /**
* @param string[] $keys * @param (string|int)[] $keys
* *
* @return string[] * @return string[]
*/ */
@ -650,7 +650,9 @@ class Uri implements UriInterface, \JsonSerializable
return []; return [];
} }
$decodedKeys = array_map('rawurldecode', $keys); $decodedKeys = array_map(function ($k): string {
return rawurldecode((string) $k);
}, $keys);
return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);

View File

@ -11,7 +11,7 @@ use Psr\Http\Message\UriInterface;
* *
* @author Tobias Schultze * @author Tobias Schultze
* *
* @see https://tools.ietf.org/html/rfc3986#section-6 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6
*/ */
final class UriNormalizer final class UriNormalizer
{ {
@ -119,7 +119,7 @@ final class UriNormalizer
* @param UriInterface $uri The URI to normalize * @param UriInterface $uri The URI to normalize
* @param int $flags A bitmask of normalizations to apply, see constants * @param int $flags A bitmask of normalizations to apply, see constants
* *
* @see https://tools.ietf.org/html/rfc3986#section-6.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2
*/ */
public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
{ {
@ -174,7 +174,7 @@ final class UriNormalizer
* @param UriInterface $uri2 An URI to compare * @param UriInterface $uri2 An URI to compare
* @param int $normalizations A bitmask of normalizations to apply, see constants * @param int $normalizations A bitmask of normalizations to apply, see constants
* *
* @see https://tools.ietf.org/html/rfc3986#section-6.1 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
*/ */
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
{ {
@ -185,7 +185,7 @@ final class UriNormalizer
{ {
$regex = '/(?:%[A-Fa-f0-9]{2})++/'; $regex = '/(?:%[A-Fa-f0-9]{2})++/';
$callback = function (array $match) { $callback = function (array $match): string {
return strtoupper($match[0]); return strtoupper($match[0]);
}; };
@ -201,7 +201,7 @@ final class UriNormalizer
{ {
$regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
$callback = function (array $match) { $callback = function (array $match): string {
return rawurldecode($match[0]); return rawurldecode($match[0]);
}; };

View File

@ -11,14 +11,14 @@ use Psr\Http\Message\UriInterface;
* *
* @author Tobias Schultze * @author Tobias Schultze
* *
* @see https://tools.ietf.org/html/rfc3986#section-5 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5
*/ */
final class UriResolver final class UriResolver
{ {
/** /**
* Removes dot segments from a path and returns the new path. * Removes dot segments from a path and returns the new path.
* *
* @see http://tools.ietf.org/html/rfc3986#section-5.2.4 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
*/ */
public static function removeDotSegments(string $path): string public static function removeDotSegments(string $path): string
{ {
@ -53,7 +53,7 @@ final class UriResolver
/** /**
* Converts the relative URI into a new URI that is resolved against the base URI. * Converts the relative URI into a new URI that is resolved against the base URI.
* *
* @see http://tools.ietf.org/html/rfc3986#section-5.2 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
*/ */
public static function resolve(UriInterface $base, UriInterface $rel): UriInterface public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
{ {

View File

@ -14,18 +14,18 @@ final class Utils
/** /**
* Remove the items given by the keys, case insensitively from the data. * Remove the items given by the keys, case insensitively from the data.
* *
* @param string[] $keys * @param (string|int)[] $keys
*/ */
public static function caselessRemove(array $keys, array $data): array public static function caselessRemove(array $keys, array $data): array
{ {
$result = []; $result = [];
foreach ($keys as &$key) { foreach ($keys as &$key) {
$key = strtolower($key); $key = strtolower((string) $key);
} }
foreach ($data as $k => $v) { foreach ($data as $k => $v) {
if (!is_string($k) || !in_array(strtolower($k), $keys)) { if (!in_array(strtolower((string) $k), $keys)) {
$result[$k] = $v; $result[$k] = $v;
} }
} }
@ -231,7 +231,7 @@ final class Utils
* @param StreamInterface $stream Stream to read from * @param StreamInterface $stream Stream to read from
* @param int|null $maxLength Maximum buffer length * @param int|null $maxLength Maximum buffer length
*/ */
public static function readLine(StreamInterface $stream, int $maxLength = null): string public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
{ {
$buffer = ''; $buffer = '';
$size = 0; $size = 0;
@ -250,6 +250,20 @@ final class Utils
return $buffer; return $buffer;
} }
/**
* Redact the password in the user info part of a URI.
*/
public static function redactUserInfo(UriInterface $uri): UriInterface
{
$userInfo = $uri->getUserInfo();
if (false !== ($pos = \strpos($userInfo, ':'))) {
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
}
return $uri;
}
/** /**
* Create a new stream based on the input type. * Create a new stream based on the input type.
* *

View File

@ -1,6 +1,6 @@
{ {
"name": "psr/http-factory", "name": "psr/http-factory",
"description": "Common interfaces for PSR-7 HTTP message factories", "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [ "keywords": [
"psr", "psr",
"psr-7", "psr-7",
@ -18,8 +18,11 @@
"homepage": "https://www.php-fig.org/" "homepage": "https://www.php-fig.org/"
} }
], ],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0" "psr/http-message": "^1.0 || ^2.0"
}, },
"autoload": { "autoload": {

View File

@ -15,10 +15,10 @@ interface UploadedFileFactoryInterface
* *
* @param StreamInterface $stream Underlying stream representing the * @param StreamInterface $stream Underlying stream representing the
* uploaded file content. * uploaded file content.
* @param int $size in bytes * @param int|null $size in bytes
* @param int $error PHP file upload error * @param int $error PHP file upload error
* @param string $clientFilename Filename as provided by the client, if any. * @param string|null $clientFilename Filename as provided by the client, if any.
* @param string $clientMediaType Media type as provided by the client, if any. * @param string|null $clientMediaType Media type as provided by the client, if any.
* *
* @return UploadedFileInterface * @return UploadedFileInterface
* *
@ -26,9 +26,9 @@ interface UploadedFileFactoryInterface
*/ */
public function createUploadedFile( public function createUploadedFile(
StreamInterface $stream, StreamInterface $stream,
int $size = null, ?int $size = null,
int $error = \UPLOAD_ERR_OK, int $error = \UPLOAD_ERR_OK,
string $clientFilename = null, ?string $clientFilename = null,
string $clientMediaType = null ?string $clientMediaType = null
): UploadedFileInterface; ): UploadedFileInterface;
} }

View File

@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@ -1,4 +1,4 @@
Copyright (c) 2020-2022 Fabien Potencier Copyright (c) 2020-present Fabien Potencier
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

@ -22,5 +22,5 @@ trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use
This will generate the following message: This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty While not recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application. `function trigger_deprecation() {}` in your application.

View File

@ -15,7 +15,7 @@
} }
], ],
"require": { "require": {
"php": ">=8.0.2" "php": ">=8.1"
}, },
"autoload": { "autoload": {
"files": [ "files": [
@ -25,7 +25,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.0-dev" "dev-main": "3.5-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",