diff --git a/libs/composer.lock b/libs/composer.lock index 3956610a..764b808a 100644 --- a/libs/composer.lock +++ b/libs/composer.lock @@ -283,35 +283,35 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.13.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" + "reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", - "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b094ded77ee97a6027ad6cf0e8c7b9f88381814c", + "reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/polyfill-php80": "^1.25" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.52 || ^9.6.34" + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -382,7 +382,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.13.0" + "source": "https://github.com/guzzle/psr7/tree/3.0.0" }, "funding": [ { @@ -398,7 +398,7 @@ "type": "tidelift" } ], - "time": "2026-07-16T22:23:49+00:00" + "time": "2026-07-20T13:48:31+00:00" }, { "name": "illuminate/collections", @@ -1204,50 +1204,6 @@ }, "time": "2021-10-29T13:26:27+00:00" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, { "name": "symfony/clock", "version": "v7.4.8", @@ -1399,16 +1355,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.13", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770" + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/a845722765c4f6b2ce88beaf4f4479975b186770", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770", + "url": "https://api.github.com/repos/symfony/mime/zipball/0c1daf58bc931628df0bea26840d1fc8b9a3d34b", + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b", "shasum": "" }, "require": { @@ -1464,7 +1420,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.13" + "source": "https://github.com/symfony/mime/tree/v7.4.15" }, "funding": [ { @@ -1484,7 +1440,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T16:22:37+00:00" + "time": "2026-07-29T07:59:49+00:00" }, { "name": "symfony/polyfill-iconv", @@ -1912,17 +1868,97 @@ "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.38.2", + "name": "symfony/polyfill-php82", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:45:58+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -1969,7 +2005,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -1989,7 +2025,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:51:48+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-php84", @@ -2073,16 +2109,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -2129,7 +2165,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -2149,7 +2185,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T02:25:22+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/translation", @@ -2335,31 +2371,31 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "3.0.6", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0" + "reference": "f0f7ea3d5fc07471187e6369a7f32dbfea6220e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0", - "reference": "395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/f0f7ea3d5fc07471187e6369a7f32dbfea6220e0", + "reference": "f0f7ea3d5fc07471187e6369a7f32dbfea6220e0", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", + "guzzlehttp/psr7": "^2.5 || ^3.0", + "php": ">=8.1", "php-di/php-di": "^6.0|^7.0", "psr/log": "^1|^2|^3", - "zbateson/mb-wrapper": "^2.0", - "zbateson/stream-decorators": "^2.1" + "zbateson/mb-wrapper": "^2.0 || ^3.0", + "zbateson/stream-decorators": "^2.1 || ^3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "*", + "friendsofphp/php-cs-fixer": "^3.0", "monolog/monolog": "^2|^3", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5" }, "suggest": { "ext-iconv": "For best support/performance", @@ -2407,31 +2443,31 @@ "type": "github" } ], - "time": "2026-06-24T13:42:46+00:00" + "time": "2026-07-30T17:53:06+00:00" }, { "name": "zbateson/mb-wrapper", - "version": "2.0.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "50a14c0c9537f978a61cde9fdc192a0267cc9cff" + "reference": "36dd227ed698d9f5fe995ea75dbcc594a2d880bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/50a14c0c9537f978a61cde9fdc192a0267cc9cff", - "reference": "50a14c0c9537f978a61cde9fdc192a0267cc9cff", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/36dd227ed698d9f5fe995ea75dbcc594a2d880bb", + "reference": "36dd227ed698d9f5fe995ea75dbcc594a2d880bb", "shasum": "" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "symfony/polyfill-iconv": "^1.9", "symfony/polyfill-mbstring": "^1.9" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "^10.0|^11.0" }, "suggest": { "ext-iconv": "For best support/performance", @@ -2468,7 +2504,7 @@ ], "support": { "issues": "https://github.com/zbateson/mb-wrapper/issues", - "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.1" + "source": "https://github.com/zbateson/mb-wrapper/tree/3.0.1" }, "funding": [ { @@ -2476,31 +2512,31 @@ "type": "github" } ], - "time": "2024-12-20T22:05:33+00:00" + "time": "2026-06-24T03:02:00+00:00" }, { "name": "zbateson/stream-decorators", - "version": "2.1.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + "reference": "4776323764ec592de417ef54f3c8fc96fd242849" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/4776323764ec592de417ef54f3c8fc96fd242849", + "reference": "4776323764ec592de417ef54f3c8fc96fd242849", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "zbateson/mb-wrapper": "^2.0" + "guzzlehttp/psr7": "^2.5 || ^3.0", + "php": ">=8.1", + "zbateson/mb-wrapper": "^2.0 || ^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "^10.0 || ^11.0" }, "type": "library", "autoload": { @@ -2531,7 +2567,7 @@ ], "support": { "issues": "https://github.com/zbateson/stream-decorators/issues", - "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + "source": "https://github.com/zbateson/stream-decorators/tree/3.0.2" }, "funding": [ { @@ -2539,7 +2575,7 @@ "type": "github" } ], - "time": "2024-04-29T21:42:39+00:00" + "time": "2026-07-24T23:09:40+00:00" } ], "packages-dev": [], diff --git a/libs/vendor/composer/autoload_classmap.php b/libs/vendor/composer/autoload_classmap.php index a1e05575..6b750c2a 100644 --- a/libs/vendor/composer/autoload_classmap.php +++ b/libs/vendor/composer/autoload_classmap.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'AllowDynamicProperties' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php', 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php', @@ -31,9 +32,17 @@ return array( 'Pdo\\Pgsql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', 'Pdo\\Sqlite' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Random\\BrokenRandomEngineError' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php', + 'Random\\CryptoSafeEngine' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php', + 'Random\\Engine' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/Engine.php', + 'Random\\Engine\\Secure' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php', + 'Random\\RandomError' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php', + 'Random\\RandomException' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php', 'ReflectionConstant' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', 'RoundingMode' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', 'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + 'SensitiveParameter' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php', + 'SensitiveParameterValue' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/libs/vendor/composer/autoload_files.php b/libs/vendor/composer/autoload_files.php index 43342a49..e1fbc11f 100644 --- a/libs/vendor/composer/autoload_files.php +++ b/libs/vendor/composer/autoload_files.php @@ -12,10 +12,10 @@ return array( '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', - '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '5897ea0ac4cccf14d323035e65887801' => $vendorDir . '/symfony/polyfill-php82/bootstrap.php', 'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php', '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', '606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php', diff --git a/libs/vendor/composer/autoload_psr4.php b/libs/vendor/composer/autoload_psr4.php index 112ef67a..6350042f 100644 --- a/libs/vendor/composer/autoload_psr4.php +++ b/libs/vendor/composer/autoload_psr4.php @@ -12,6 +12,7 @@ return array( 'Symfony\\Polyfill\\Php85\\' => array($vendorDir . '/symfony/polyfill-php85'), 'Symfony\\Polyfill\\Php84\\' => array($vendorDir . '/symfony/polyfill-php84'), 'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'), + 'Symfony\\Polyfill\\Php82\\' => array($vendorDir . '/symfony/polyfill-php82'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), diff --git a/libs/vendor/composer/autoload_static.php b/libs/vendor/composer/autoload_static.php index 1ac2a95e..cfc1a2cc 100644 --- a/libs/vendor/composer/autoload_static.php +++ b/libs/vendor/composer/autoload_static.php @@ -13,10 +13,10 @@ class ComposerStaticInitbadf1d01c367c06fb591106ea3486c30 '2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', - '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '5897ea0ac4cccf14d323035e65887801' => __DIR__ . '/..' . '/symfony/polyfill-php82/bootstrap.php', 'b33e3d135e5d9e47d845c576147bda89' => __DIR__ . '/..' . '/php-di/php-di/src/functions.php', '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php', '606a39d89246991a373564698c2d8383' => __DIR__ . '/..' . '/symfony/polyfill-php85/bootstrap.php', @@ -36,6 +36,7 @@ class ComposerStaticInitbadf1d01c367c06fb591106ea3486c30 'Symfony\\Polyfill\\Php85\\' => 23, 'Symfony\\Polyfill\\Php84\\' => 23, 'Symfony\\Polyfill\\Php83\\' => 23, + 'Symfony\\Polyfill\\Php82\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, @@ -110,6 +111,10 @@ class ComposerStaticInitbadf1d01c367c06fb591106ea3486c30 array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php83', ), + 'Symfony\\Polyfill\\Php82\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php82', + ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', @@ -216,6 +221,7 @@ class ComposerStaticInitbadf1d01c367c06fb591106ea3486c30 ); public static $classMap = array ( + 'AllowDynamicProperties' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php', 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php', @@ -241,9 +247,17 @@ class ComposerStaticInitbadf1d01c367c06fb591106ea3486c30 'Pdo\\Pgsql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', 'Pdo\\Sqlite' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Random\\BrokenRandomEngineError' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php', + 'Random\\CryptoSafeEngine' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php', + 'Random\\Engine' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/Engine.php', + 'Random\\Engine\\Secure' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php', + 'Random\\RandomError' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php', + 'Random\\RandomException' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php', 'ReflectionConstant' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', 'RoundingMode' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', 'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + 'SensitiveParameter' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php', + 'SensitiveParameterValue' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/libs/vendor/composer/installed.json b/libs/vendor/composer/installed.json index db2ceb1e..ebda0d53 100644 --- a/libs/vendor/composer/installed.json +++ b/libs/vendor/composer/installed.json @@ -289,41 +289,41 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.13.0", - "version_normalized": "2.13.0.0", + "version": "3.0.0", + "version_normalized": "3.0.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" + "reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", - "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b094ded77ee97a6027ad6cf0e8c7b9f88381814c", + "reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/polyfill-php80": "^1.25" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.52 || ^9.6.34" + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, - "time": "2026-07-16T22:23:49+00:00", + "time": "2026-07-20T13:48:31+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -391,7 +391,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.13.0" + "source": "https://github.com/guzzle/psr7/tree/3.0.0" }, "funding": [ { @@ -1255,53 +1255,6 @@ }, "install-path": "../psr/simple-cache" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "version_normalized": "3.0.3.0", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "time": "2019-03-08T08:55:37+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "install-path": "../ralouphie/getallheaders" - }, { "name": "symfony/clock", "version": "v7.4.8", @@ -1459,17 +1412,17 @@ }, { "name": "symfony/mime", - "version": "v7.4.13", - "version_normalized": "7.4.13.0", + "version": "v7.4.15", + "version_normalized": "7.4.15.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770" + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/a845722765c4f6b2ce88beaf4f4479975b186770", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770", + "url": "https://api.github.com/repos/symfony/mime/zipball/0c1daf58bc931628df0bea26840d1fc8b9a3d34b", + "reference": "0c1daf58bc931628df0bea26840d1fc8b9a3d34b", "shasum": "" }, "require": { @@ -1495,7 +1448,7 @@ "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, - "time": "2026-05-23T16:22:37+00:00", + "time": "2026-07-29T07:59:49+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1527,7 +1480,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.13" + "source": "https://github.com/symfony/mime/tree/v7.4.15" }, "funding": [ { @@ -1990,24 +1943,107 @@ "install-path": "../symfony/polyfill-php80" }, { - "name": "symfony/polyfill-php83", - "version": "v1.38.2", - "version_normalized": "1.38.2.0", + "name": "symfony/polyfill-php82", + "version": "v1.38.1", + "version_normalized": "1.38.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", "shasum": "" }, "require": { "php": ">=7.2" }, - "time": "2026-05-27T06:51:48+00:00", + "time": "2026-05-26T12:45:58+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php82" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.41.0", + "version_normalized": "1.41.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2026-07-01T12:47:55+00:00", "type": "library", "extra": { "thanks": { @@ -2050,7 +2086,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -2157,23 +2193,23 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", - "version_normalized": "1.38.1.0", + "version": "v1.41.0", + "version_normalized": "1.41.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { "php": ">=7.2" }, - "time": "2026-05-26T02:25:22+00:00", + "time": "2026-07-01T12:47:55+00:00", "type": "library", "extra": { "thanks": { @@ -2216,7 +2252,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -2428,38 +2464,38 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "3.0.6", - "version_normalized": "3.0.6.0", + "version": "4.0.3", + "version_normalized": "4.0.3.0", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0" + "reference": "f0f7ea3d5fc07471187e6369a7f32dbfea6220e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0", - "reference": "395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/f0f7ea3d5fc07471187e6369a7f32dbfea6220e0", + "reference": "f0f7ea3d5fc07471187e6369a7f32dbfea6220e0", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", + "guzzlehttp/psr7": "^2.5 || ^3.0", + "php": ">=8.1", "php-di/php-di": "^6.0|^7.0", "psr/log": "^1|^2|^3", - "zbateson/mb-wrapper": "^2.0", - "zbateson/stream-decorators": "^2.1" + "zbateson/mb-wrapper": "^2.0 || ^3.0", + "zbateson/stream-decorators": "^2.1 || ^3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "*", + "friendsofphp/php-cs-fixer": "^3.0", "monolog/monolog": "^2|^3", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5" }, "suggest": { "ext-iconv": "For best support/performance", "ext-mbstring": "For best support/performance" }, - "time": "2026-06-24T13:42:46+00:00", + "time": "2026-07-30T17:53:06+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2507,34 +2543,34 @@ }, { "name": "zbateson/mb-wrapper", - "version": "2.0.1", - "version_normalized": "2.0.1.0", + "version": "3.0.1", + "version_normalized": "3.0.1.0", "source": { "type": "git", "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "50a14c0c9537f978a61cde9fdc192a0267cc9cff" + "reference": "36dd227ed698d9f5fe995ea75dbcc594a2d880bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/50a14c0c9537f978a61cde9fdc192a0267cc9cff", - "reference": "50a14c0c9537f978a61cde9fdc192a0267cc9cff", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/36dd227ed698d9f5fe995ea75dbcc594a2d880bb", + "reference": "36dd227ed698d9f5fe995ea75dbcc594a2d880bb", "shasum": "" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "symfony/polyfill-iconv": "^1.9", "symfony/polyfill-mbstring": "^1.9" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "^10.0|^11.0" }, "suggest": { "ext-iconv": "For best support/performance", "ext-mbstring": "For best support/performance" }, - "time": "2024-12-20T22:05:33+00:00", + "time": "2026-06-24T03:02:00+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2567,7 +2603,7 @@ ], "support": { "issues": "https://github.com/zbateson/mb-wrapper/issues", - "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.1" + "source": "https://github.com/zbateson/mb-wrapper/tree/3.0.1" }, "funding": [ { @@ -2579,30 +2615,30 @@ }, { "name": "zbateson/stream-decorators", - "version": "2.1.1", - "version_normalized": "2.1.1.0", + "version": "3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + "reference": "4776323764ec592de417ef54f3c8fc96fd242849" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/4776323764ec592de417ef54f3c8fc96fd242849", + "reference": "4776323764ec592de417ef54f3c8fc96fd242849", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "zbateson/mb-wrapper": "^2.0" + "guzzlehttp/psr7": "^2.5 || ^3.0", + "php": ">=8.1", + "zbateson/mb-wrapper": "^2.0 || ^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "^10.0 || ^11.0" }, - "time": "2024-04-29T21:42:39+00:00", + "time": "2026-07-24T23:09:40+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2633,7 +2669,7 @@ ], "support": { "issues": "https://github.com/zbateson/stream-decorators/issues", - "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + "source": "https://github.com/zbateson/stream-decorators/tree/3.0.2" }, "funding": [ { diff --git a/libs/vendor/composer/installed.php b/libs/vendor/composer/installed.php index 4a94180f..be99a04a 100644 --- a/libs/vendor/composer/installed.php +++ b/libs/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'ab415435789dace46d65303ccbdf9628df3481d8', + 'reference' => 'cf4446f405d95f3fc538a2d3e0aec33d52f075fc', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'ab415435789dace46d65303ccbdf9628df3481d8', + 'reference' => 'cf4446f405d95f3fc538a2d3e0aec33d52f075fc', 'dev_requirement' => false, ), 'carbonphp/carbon-doctrine-types' => array( @@ -56,12 +56,12 @@ 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( - 'pretty_version' => '2.13.0', - 'version' => '2.13.0.0', + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), - 'reference' => 'dad89620b7a6edb60c15858442eb2e408b45d8f4', + 'reference' => 'b094ded77ee97a6027ad6cf0e8c7b9f88381814c', 'dev_requirement' => false, ), 'illuminate/collections' => array( @@ -178,7 +178,7 @@ 'psr/http-factory-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '1.1', ), ), 'psr/http-message' => array( @@ -193,7 +193,7 @@ 'psr/http-message-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '2.0', ), ), 'psr/log' => array( @@ -214,15 +214,6 @@ 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865', 'dev_requirement' => false, ), - 'ralouphie/getallheaders' => array( - 'pretty_version' => '3.0.3', - 'version' => '3.0.3.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ralouphie/getallheaders', - 'aliases' => array(), - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', - 'dev_requirement' => false, - ), 'symfony/clock' => array( 'pretty_version' => 'v7.4.8', 'version' => '7.4.8.0', @@ -242,12 +233,12 @@ 'dev_requirement' => false, ), 'symfony/mime' => array( - 'pretty_version' => 'v7.4.13', - 'version' => '7.4.13.0', + 'pretty_version' => 'v7.4.15', + 'version' => '7.4.15.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/mime', 'aliases' => array(), - 'reference' => 'a845722765c4f6b2ce88beaf4f4479975b186770', + 'reference' => '0c1daf58bc931628df0bea26840d1fc8b9a3d34b', 'dev_requirement' => false, ), 'symfony/polyfill-iconv' => array( @@ -295,13 +286,22 @@ 'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411', 'dev_requirement' => false, ), + 'symfony/polyfill-php82' => array( + 'pretty_version' => 'v1.38.1', + 'version' => '1.38.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php82', + 'aliases' => array(), + 'reference' => '002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b', + 'dev_requirement' => false, + ), 'symfony/polyfill-php83' => array( - 'pretty_version' => 'v1.38.2', - 'version' => '1.38.2.0', + 'pretty_version' => 'v1.41.0', + 'version' => '1.41.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php83', 'aliases' => array(), - 'reference' => '796a26abb75ce49f3a84433cd81bf1009d73d5f8', + 'reference' => '5ea99087fb99c273a9b9236ed4c31e78b16103c6', 'dev_requirement' => false, ), 'symfony/polyfill-php84' => array( @@ -314,12 +314,12 @@ 'dev_requirement' => false, ), 'symfony/polyfill-php85' => array( - 'pretty_version' => 'v1.38.1', - 'version' => '1.38.1.0', + 'pretty_version' => 'v1.41.0', + 'version' => '1.41.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php85', 'aliases' => array(), - 'reference' => 'ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1', + 'reference' => '255fab485aaa1006ed411040c42aecd7b5302d7a', 'dev_requirement' => false, ), 'symfony/translation' => array( @@ -347,30 +347,30 @@ ), ), 'zbateson/mail-mime-parser' => array( - 'pretty_version' => '3.0.6', - 'version' => '3.0.6.0', + 'pretty_version' => '4.0.3', + 'version' => '4.0.3.0', 'type' => 'library', 'install_path' => __DIR__ . '/../zbateson/mail-mime-parser', 'aliases' => array(), - 'reference' => '395c406cc1c5d1eb171d9decb0a3b509e8bd6bc0', + 'reference' => 'f0f7ea3d5fc07471187e6369a7f32dbfea6220e0', 'dev_requirement' => false, ), 'zbateson/mb-wrapper' => array( - 'pretty_version' => '2.0.1', - 'version' => '2.0.1.0', + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', 'type' => 'library', 'install_path' => __DIR__ . '/../zbateson/mb-wrapper', 'aliases' => array(), - 'reference' => '50a14c0c9537f978a61cde9fdc192a0267cc9cff', + 'reference' => '36dd227ed698d9f5fe995ea75dbcc594a2d880bb', 'dev_requirement' => false, ), 'zbateson/stream-decorators' => array( - 'pretty_version' => '2.1.1', - 'version' => '2.1.1.0', + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', 'type' => 'library', 'install_path' => __DIR__ . '/../zbateson/stream-decorators', 'aliases' => array(), - 'reference' => '32a2a62fb0f26313395c996ebd658d33c3f9c4e5', + 'reference' => '4776323764ec592de417ef54f3c8fc96fd242849', 'dev_requirement' => false, ), ), diff --git a/libs/vendor/guzzlehttp/psr7/AGENTS.md b/libs/vendor/guzzlehttp/psr7/AGENTS.md new file mode 100644 index 00000000..bd963331 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/AGENTS.md @@ -0,0 +1,69 @@ +# Agent Guidelines + +## Code and tooling + +- All code must remain compatible with PHP 7.4. Run PHPStan and PHP-CS-Fixer + only under a PHP 7.4.x runtime; never run either tool under any other PHP + major.minor version. +- Use fully qualified `#[\SensitiveParameter]` on concrete executable + parameters when their established role normally carries a secret, + credential-bearing aggregate, or confidential Guzzle-owned container, and + the active frame can throw or invoke throwing code. +- Repeat the attribute on every qualifying owned caller, callee, concrete trait + method, and closure parameter. Do not add it to interfaces, abstract-only + declarations, pure/no-realistic-throw helpers, assignment-only sites, + arbitrary generic payloads, or completed non-recoverable derivatives. +- For PHP 7.4 compatibility, put `#[\SensitiveParameter]` on its own line and + the parameter on the following line, expand the complete parameter list, and + never add a comma after the final parameter. Native trace redaction starts on + PHP 8.2 and does not redact logs, messages, properties, wire traffic, captured + variables, return values, or the separate backtrace `$this`/`object`. +- Always pass an explicit character list to `trim()`, `ltrim()`, and `rtrim()`; + never rely on the default characters. +- Handle `preg_*` engine failures: when the result is used as data, test for + `false` or `null` and throw a `\RuntimeException` including + `preg_last_error_msg()`; boolean validation guards must compare strictly, such + as `=== 1`, so an engine failure can only ever fail closed. Diagnostic + escaping is the narrow exception: use a deterministic bytewise fallback rather + than throwing, so it cannot obscure the original exception. +- Anchor validation patterns to the true end of input with the `D` modifier or + `\z`; a bare `$` accepts a trailing newline. +- Never embed raw control bytes in exception messages and other diagnostics; + escape or redact the offending value first. +- Helper classes that expose only public static methods are `final` and have a + private constructor. +- Resist native PHP serialization when a class holds live state (streams, + resources, handles, callbacks, credentials) or when magic methods such as + `__destruct()` have side effects that untrusted unserialized data could + redirect, as with the file and session write fixes in Guzzle's persisting + cookie jars. Plain data holders, such as Guzzle's in-memory cookie jar, remain + serializable. +- To resist, `__serialize()` and `__unserialize()` both throw + `\LogicException(static::class.' should never be serialized')` and its + unserialized counterpart, usually via the `@internal` + `NonSerializableStreamTrait`. Where a `__destruct()` has such a side effect, + it is enabled only by the constructor and disabled in `__wakeup()` and + `__unserialize()` before throwing, so the protection holds even if the + exception is swallowed. +- In general, numeric inputs should not accept non-finite floats. In situations + where they are accepted and we need to cast to a string, we should branch on + `\is_finite($value)`, using `(string) $value` for the finite case and + `\is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF')` otherwise. +- Never call `strtolower()`, `strtoupper()`, `strcasecmp()`, `stripos()`, or + other locale-sensitive case functions; use the locale-independent + `Utils::asciiToLower()`, `Utils::asciiToUpper()`, `Utils::caselessEquals()`, + and `Utils::caselessContains()` helpers instead. +- Changes in behavior need a `CHANGELOG.md` entry in the unreleased section of + the target branch and an `UPGRADING.md` note when the behavior differs between + major versions. + +## Documentation + +- Wrap markdown prose and PHPDoc text to 80 columns using greedy wrapping. Never + split a markdown link or an inline code span across a line break; a line that + cannot be broken may exceed the limit. Avoid em dashes. +- Keep PHPDoc and the corresponding `docs/` pages in sync: shared prose is + deliberately word-for-word identical, including boilerplate copied verbatim + between related functions, so apply the same edit to every copy. Only + formatting and linking may differ, such as a docs link becoming a PHPDoc + `@see` tag; the wording must never drift. diff --git a/libs/vendor/guzzlehttp/psr7/CHANGELOG.md b/libs/vendor/guzzlehttp/psr7/CHANGELOG.md index 3d678f4e..b9730842 100644 --- a/libs/vendor/guzzlehttp/psr7/CHANGELOG.md +++ b/libs/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -5,6 +5,106 @@ 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/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 3.0.0 - 2026-07-20 + +### Added + +- Add `DiagnosticValue::escape()` to escape controls and malformed UTF-8 in diagnostics +- Add `GuzzleHttp\Psr7\Exception\TimeoutException` for timed-out stream operations +- Add `GuzzleHttp\Psr7\Utils::redactUserInfoInString()` to redact the userinfo of a raw URI string within text +- Promote `GuzzleHttp\Psr7\Rfc3986` to public API with `isValid*()` predicates and `canonicalizeIpv6()` +- Add `GuzzleHttp\Psr7\UriNormalizer::CANONICALIZE_IPV6_HOST` to `PRESERVING_NORMALIZATIONS` + +### Changed + +- Require `psr/http-message:^2.0` and add native parameter and return types +- Require `psr/http-factory:^1.1` +- Reject native PHP serialization of stream implementations +- Preserve request method casing, except `ServerRequest::fromGlobals()` still uppercases +- Reject empty arrays and non-string values as header values +- Reject invalid uploaded file trees and invalid parsed body values +- Reject uploaded file specs missing `tmp_name`, `size`, or `error` +- Reject non-integer and negative uploaded file `error` values +- Reject invalid stream/upload sizes, buffer high-water marks, and dropping-stream limits +- Rewind seekable uploaded-file streams before copying in `UploadedFile::moveTo()` +- Reject negative `read()` lengths across all stream implementations +- Detect the `+` flag anywhere in a mode for `Stream::isReadable()`/`isWritable()` +- Reject empty strings returned by `PumpStream` source callables +- Discard buffered bytes on `PumpStream` close and detach +- Restore the original stream position after `Message::bodySummary()` +- Allow `null` for the `Message::bodySummary()` truncation length to use the default +- Validate `LimitStream` offset/limit and track non-seekable offset by bytes skipped +- Make `FnStream` close and detach terminal, calling close callbacks at most once +- Suppress exceptions from `FnStream` close callbacks during destructor cleanup +- Make `CachingStream::close()` idempotent, preserving remote cleanup after detach +- Do not move the `CachingStream` cursor when a `SEEK_END` target on an unknown-size stream is rejected +- Normalize multiple leading slashes in `Uri::getPath()` and origin-form request targets +- Serialize authority-less `file` URIs with rootless paths without the `//` separator +- Serialize authority-less `file` URIs with empty paths as `file:` instead of the unparseable `file://` +- Remove dot segments above the root per RFC 3986, prefixing authority-less `//` paths with `/.` +- Return a network-path reference from `UriResolver::relativize()` when an empty-path target requires one +- Stop returning an empty reference from `UriResolver::relativize()` when it would inherit the base fragment +- Stop throwing from `UriResolver::relativize()` when an equal-path target's last path segment contains a colon +- Harden URI host validation (delimiters, backslashes, IPv6, embedded ports); require schemes to start with a letter +- Validate `Uri::fromParts()` ports instead of casting them +- Treat ports 80 and 443 as defaults for the `ws` and `wss` schemes +- Use the `ws` and `wss` default ports in `UriComparator::isCrossOrigin()` port comparisons +- Redact all non-empty URI userinfo in `Utils::redactUserInfo()` +- Rebuild server request URIs from `$_SERVER` by `REQUEST_METHOD`, using target authority before `SERVER_PORT` +- Remove userinfo from absolute-form `REQUEST_URI` targets in `ServerRequest::fromGlobals()` +- Reject zero-port `HTTP_HOST` and malformed `SERVER_PORT` in `ServerRequest::getUriFromGlobals()` +- Reject malformed `REQUEST_METHOD` and `SERVER_PROTOCOL` server values in `ServerRequest::fromGlobals()` +- Reject zero-port `Host` and normalize leading-zero ports in `Message::parseRequest()` +- Reject duplicate `Host` headers and validate present values for all request-target forms +- Reject zero-port, hostless, and userinfo absolute-form request targets in `Message::parseRequest()` +- Synchronize the `Host` header in `Request::withUri()` when the URI changes or Host is empty +- Include the URI port in `Host` headers synthesized by `Message::toString()` +- Validate the Host header synthesized by `Message::toString()` from the request URI +- Include non-default URI ports in `Host` headers set by `Utils::modifyRequest()` URI changes +- Accept `OPTIONS *` and `CONNECT` authority-form request targets in `Message::parseRequest()` +- Hide credential-bearing URI, server, and cookie arguments in stack traces on PHP 8.2+ +- Reject malformed HTTP request/response start-lines +- Unfold obsolete HTTP/1.0 line folding for all valid request method tokens +- Reject empty and control-character request targets in `Request::withRequestTarget()` +- Validate iterator chunks passed to `Utils::streamFor()` +- Validate unsupported values passed to `Query::build()` +- Reject non-finite float values in `Query::build()` and `MultipartStream` contents +- Reject non-finite float values in iterator chunks passed to `Utils::streamFor()` +- `Utils::streamFor()` now rejects non-string scalar bodies +- `Uri::withQueryValues()` now rejects non-string values +- Reject invalid `Utils::modifyRequest()` change values +- Use PHP debug type names in type error messages +- Changed `Utils::copyToStream()` to throw when destination streams cannot make progress +- Throw `TimeoutException` from `Stream` read/write and `Utils` copy/hash/readLine on stream timeouts +- Throw `TimeoutException` from `AppendStream::read()`, `CachingStream::read()`, and `Utils::tryGetContents()` on stream timeouts +- Re-throw `TimeoutException` from `InflateStream` when the decoded source stream times out +- Close the compressed source stream from `InflateStream::close()` +- Return the number of bytes copied from `Utils::copyToStream()` +- Throw `OverflowException` when stream byte counts or offsets exceed `PHP_INT_MAX` +- Translate `StreamWrapper` runtime failures to PHP stream failure values +- Stop adding default `Content-Length` to `multipart/form-data` parts (RFC 7578 §4.8) +- Escape multipart `Content-Disposition` parameters and reject unsafe boundaries and part headers +- Preserve trailing whitespace in custom `MultipartStream` part header values +- Preserve explicit custom `MultipartStream` boundary `'0'` instead of replacing it with a generated boundary +- Made static utility classes non-instantiable +- Validate bracketed IP-literal hosts consistently between parsing and `withHost()`, including userinfo forms +- Percent-encode raw control bytes in userinfo before bracketed IP-literal hosts instead of parsing mutated values +- Reject invalid UTF-8 and preserve percent-sequences in userinfo before bracketed IP-literal hosts +- Normalize percent-encoded octets in the URI host to uppercase hex +- Canonicalize IPv6 hosts to RFC 5952 form in `Uri` construction, `fromParts()`, and `withHost()` +- Canonicalize bracketed IPv6 hosts in `UriComparator::isCrossOrigin()` +- Reject malformed percent-sequences and percent-encoded bytes forbidden by the URI host policy +- Extend `CAPITALIZE_PERCENT_ENCODING` and `DECODE_UNRESERVED_CHARACTERS` to userinfo and host +- Trim header list elements with only spaces, horizontal tabs, and line terminators in `Header::splitList()` +- Report header parameter PCRE failures explicitly in `Header::parse()` +- Escape controls and malformed UTF-8 consistently in generated exception messages + +### Removed + +- Dropped support for PHP 7.2 and 7.3 +- Removed the `ralouphie/getallheaders` dependency +- Removed deprecated `Header::normalize()` method + ## 2.13.0 - 2026-07-16 ### Added diff --git a/libs/vendor/guzzlehttp/psr7/CLAUDE.md b/libs/vendor/guzzlehttp/psr7/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/libs/vendor/guzzlehttp/psr7/README.md b/libs/vendor/guzzlehttp/psr7/README.md index 13f2ba5e..8fea29a8 100644 --- a/libs/vendor/guzzlehttp/psr7/README.md +++ b/libs/vendor/guzzlehttp/psr7/README.md @@ -1,930 +1,88 @@ -# PSR-7 Message Implementation +# Guzzle PSR-7 -This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) -message implementation, several stream decorators, and some helpful -functionality like query string parsing. - -![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) -![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) - - -## Features - -This package comes with a number of stream implementations and stream -decorators. +`guzzlehttp/psr7` is a PSR-7 HTTP message implementation for PHP. It provides +request, response, URI, uploaded file, and stream objects that work with Guzzle +and any other library using the PSR-7 interfaces. +Use this package directly when you need to create or inspect PSR-7 messages +without sending HTTP requests. If you only want to make HTTP requests, install +[`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle/blob/8.0/README.md) +instead; it already depends on this package. ## Installation -```shell +```bash composer require guzzlehttp/psr7 ``` ## Version Guidance -| Version | Status | PHP Version | -|---------|---------------------|--------------| -| 1.x | EOL (2024-06-30) | >=5.4,<8.2 | -| 2.x | Latest | >=7.2.5,<8.6 | +| Version | Status | PHP Version | +|---------|--------------|--------------| +| 3.0 | Latest | >=7.4,<8.6 | +| 2.13 | Maintenance | >=7.2.5,<8.6 | +| 1.9 | End of Life | >=5.4,<8.2 | -See [UPGRADING.md](UPGRADING.md) for notes on upgrading from 1.x to 2.0. - - -## AppendStream - -`GuzzleHttp\Psr7\AppendStream` - -Reads from multiple streams, one after the other. +## Quick Start ```php -use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; -$a = Psr7\Utils::streamFor('abc, '); -$b = Psr7\Utils::streamFor('123.'); -$composed = new Psr7\AppendStream([$a, $b]); +$request = new Request('GET', 'https://example.com/api'); +$response = new Response(200, ['Content-Type' => 'text/plain'], 'OK'); +$stream = Utils::streamFor('request or response body'); -$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); - -echo $composed; // abc, 123. Above all listen to me. +echo $request->getMethod(); +echo $response->getStatusCode(); +echo $stream; ``` - -## BufferStream - -`GuzzleHttp\Psr7\BufferStream` - -Provides a buffer stream that can be written to fill a buffer, and read -from to remove bytes from the buffer. - -This stream returns a "hwm" metadata value that tells upstream consumers -what the configured high water mark of the stream is, or the maximum -preferred size of the buffer. +PSR-7 messages and URIs are immutable. Methods such as `withHeader()` and +`withUri()` return a changed copy instead of modifying the original object. +Streams are mutable body handles; reading, writing, seeking, and closing a +stream can change its cursor, contents, or usability. ```php -use GuzzleHttp\Psr7; - -// When more than 1024 bytes are in the buffer, it will begin returning -// 0 to writes. This is an indication that writers should slow down. -$buffer = new Psr7\BufferStream(1024); +$request = $request->withHeader('Accept', 'application/json'); ``` - -## CachingStream - -The CachingStream is used to allow seeking over previously read bytes on -non-seekable streams. This can be useful when transferring a non-seekable -entity body fails due to needing to rewind the stream (for example, resulting -from a redirect). Data that is read from the remote stream will be buffered in -a PHP temp stream so that previously read bytes are cached first in memory, -then on disk. - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); -$stream = new Psr7\CachingStream($original); - -$stream->read(1024); -echo $stream->tell(); -// 1024 - -$stream->seek(0); -echo $stream->tell(); -// 0 -``` - - -## DroppingStream - -`GuzzleHttp\Psr7\DroppingStream` - -Stream decorator that begins dropping data once the size of the underlying -stream becomes too full. - -```php -use GuzzleHttp\Psr7; - -// Create an empty stream -$stream = Psr7\Utils::streamFor(); - -// Start dropping data when the stream has more than 10 bytes -$dropping = new Psr7\DroppingStream($stream, 10); - -$dropping->write('01234567890123456789'); -echo $stream; // 0123456789 -``` - - -## FnStream - -`GuzzleHttp\Psr7\FnStream` - -Compose stream implementations based on a hash of callables. - -Allows for easy testing and extension of a provided stream without needing -to create a concrete class for a simple extension point. - -```php - -use GuzzleHttp\Psr7; - -$stream = Psr7\Utils::streamFor('hi'); -$fnStream = Psr7\FnStream::decorate($stream, [ - 'rewind' => function () use ($stream) { - echo 'About to rewind - '; - $stream->rewind(); - echo 'rewound!'; - } -]); - -$fnStream->rewind(); -// Outputs: About to rewind - rewound! -``` - - -## InflateStream - -`GuzzleHttp\Psr7\InflateStream` - -Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. - -This stream decorator converts the provided stream to a PHP stream resource, -then appends the zlib.inflate filter. The stream is then converted back -to a Guzzle stream resource to be used as a Guzzle stream. - - -## LazyOpenStream - -`GuzzleHttp\Psr7\LazyOpenStream` - -Lazily reads or writes to a file that is opened only after an IO operation -take place on the stream. - -```php -use GuzzleHttp\Psr7; - -$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); -// The file has not yet been opened... - -echo $stream->read(10); -// The file is opened and read from only when needed. -``` - - -## LimitStream - -`GuzzleHttp\Psr7\LimitStream` - -LimitStream can be used to read a subset or slice of an existing stream object. -This can be useful for breaking a large file into smaller pieces to be sent in -chunks (e.g. Amazon S3's multipart upload API). - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); -echo $original->getSize(); -// >>> 1048576 - -// Limit the size of the body to 1024 bytes and start reading from byte 2048 -$stream = new Psr7\LimitStream($original, 1024, 2048); -echo $stream->getSize(); -// >>> 1024 -echo $stream->tell(); -// >>> 0 -``` - - -## MultipartStream - -`GuzzleHttp\Psr7\MultipartStream` - -Stream that when read returns bytes for a streaming multipart or -multipart/form-data stream. - -Each multipart element must contain a `name` and `contents` key. `contents` may -be any non-array value accepted by `GuzzleHttp\Psr7\Utils::streamFor()`, -including closures and invokable objects. Array contents are recursively -expanded into nested form fields. - - -## NoSeekStream - -`GuzzleHttp\Psr7\NoSeekStream` - -NoSeekStream wraps a stream and does not allow seeking. - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor('foo'); -$noSeek = new Psr7\NoSeekStream($original); - -echo $noSeek->read(3); -// foo -var_export($noSeek->isSeekable()); -// false -$noSeek->seek(0); -var_export($noSeek->read(3)); -// NULL -``` - - -## PumpStream - -`GuzzleHttp\Psr7\PumpStream` - -Provides a read only stream that pumps data from a PHP callable. - -When invoking the provided callable, the PumpStream will pass the suggested -number of bytes to read to the callable. The callable can choose to ignore -this value and return fewer or more bytes than requested. Any extra data -returned by the provided callable is buffered internally until drained using -the read() function of the PumpStream. The provided callable MUST return -false or null when there is no more data to read. - -Userland callables that declare no parameters are tolerated by PHP, but -length-aware callables remain the recommended formal shape. - - -## Implementing stream decorators - -Creating a stream decorator is very easy thanks to the -`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that -implement `Psr\Http\Message\StreamInterface` by proxying to an underlying -stream. Just `use` the `StreamDecoratorTrait` and implement your custom -methods. - -For example, let's say we wanted to call a specific function each time the last -byte is read from a stream. This could be implemented by overriding the -`read()` method. - -```php -use Psr\Http\Message\StreamInterface; -use GuzzleHttp\Psr7\StreamDecoratorTrait; - -class EofCallbackStream implements StreamInterface -{ - use StreamDecoratorTrait; - - private $callback; - - private $stream; - - public function __construct(StreamInterface $stream, callable $cb) - { - $this->stream = $stream; - $this->callback = $cb; - } - - public function read($length) - { - $result = $this->stream->read($length); - - // Invoke the callback when EOF is hit. - if ($this->eof()) { - ($this->callback)(); - } - - return $result; - } -} -``` - -This decorator could be added to any existing stream and used like so: - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor('foo'); - -$eofStream = new EofCallbackStream($original, function () { - echo 'EOF!'; -}); - -$eofStream->read(2); -$eofStream->read(1); -// echoes "EOF!" -$eofStream->seek(0); -$eofStream->read(3); -// echoes "EOF!" -``` - - -## PHP StreamWrapper - -You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a -PSR-7 stream as a PHP stream resource. - -Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP -stream from a PSR-7 stream. - -```php -use GuzzleHttp\Psr7\StreamWrapper; - -$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); -$resource = StreamWrapper::getResource($stream); -echo fread($resource, 6); // outputs hello! -``` - - -# Static API - -There are various static methods available under the `GuzzleHttp\Psr7` namespace. - - -## `GuzzleHttp\Psr7\Message::toString` - -`public static function toString(MessageInterface $message): string` - -Returns the string representation of an HTTP message. - -```php -$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); -echo GuzzleHttp\Psr7\Message::toString($request); -``` - - -## `GuzzleHttp\Psr7\Message::bodySummary` - -`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` - -Get a short summary of the message body. - -Will return `null` if the response is not printable. - - -## `GuzzleHttp\Psr7\Message::rewindBody` - -`public static function rewindBody(MessageInterface $message): void` - -Attempts to rewind a message body and throws an exception on failure. - -The body of the message will only be rewound if a call to `tell()` -returns a value other than `0`. - - -## `GuzzleHttp\Psr7\Message::parseMessage` - -`public static function parseMessage(string $message): array` - -Parses an HTTP message into an associative array. - -The array contains the "start-line" key containing the start line of -the message, "headers" key containing an associative array of header -array values, and a "body" key containing the body of the message. - - -## `GuzzleHttp\Psr7\Message::parseRequestUri` - -`public static function parseRequestUri(string $path, array $headers): string` - -Constructs a URI for an HTTP request message. - - -## `GuzzleHttp\Psr7\Message::parseRequest` - -`public static function parseRequest(string $message): Request` - -Parses a request message string into a request object. - - -## `GuzzleHttp\Psr7\Message::parseResponse` - -`public static function parseResponse(string $message): Response` - -Parses a response message string into a response object. - - -## `GuzzleHttp\Psr7\Header::parse` - -`public static function parse(string|array $header): array` - -Parse an array of header values containing ";" separated data into an -array of associative arrays representing the header key value pair data -of the header. When a parameter does not contain a value, but just -contains a key, this function will inject a key with a '' string value. - - -## `GuzzleHttp\Psr7\Header::splitList` - -`public static function splitList(string|string[] $header): string[]` - -Splits a HTTP header defined to contain a comma-separated list into -each individual value: - -``` -$knownEtags = Header::splitList($request->getHeader('if-none-match')); -``` - -Example headers include `accept`, `cache-control` and `if-none-match`. - - -## `GuzzleHttp\Psr7\Header::normalize` (deprecated) - -`public static function normalize(string|array $header): array` - -`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) -which performs the same operation with a cleaned up API and improved -documentation. - -Converts an array of header values that may contain comma separated -headers into an array of headers with no comma separated values. - - -## `GuzzleHttp\Psr7\Query::parse` - -`public static function parse(string $str, int|bool $urlEncoding = true): array` - -Parse a query string into an associative array. - -If multiple values are found for the same key, the value of that key -value pair will become an array. This function does not parse nested -PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` -will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. - - -## `GuzzleHttp\Psr7\Query::build` - -`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. - -This function can use the return value of `parse()` to build a query -string. This function does not modify the provided keys when an array is -encountered (like `http_build_query()` would). - - -## `GuzzleHttp\Psr7\Utils::asciiToLower` - -`public static function asciiToLower(string $string): string` - -Converts ASCII uppercase letters in a string to lowercase. - -Unlike strtolower(), which honors LC_CTYPE before PHP 8.2, the conversion is -locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol -elements require. - - -## `GuzzleHttp\Psr7\Utils::asciiToUpper` - -`public static function asciiToUpper(string $string): string` - -Converts ASCII lowercase letters in a string to uppercase. - -Unlike strtoupper(), which honors LC_CTYPE before PHP 8.2, the conversion is -locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol -elements require. - - -## `GuzzleHttp\Psr7\Utils::asciiUcFirst` - -`public static function asciiUcFirst(string $string): string` - -Converts the first character of a string to uppercase when it is an ASCII -lowercase letter. - -Unlike ucfirst(), which honors LC_CTYPE before PHP 8.2, the conversion is -locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol -elements require. - - -## `GuzzleHttp\Psr7\Utils::caselessContains` - -`public static function caselessContains(string $haystack, string $needle): bool` - -Checks whether the haystack contains the needle, comparing ASCII letters -case-insensitively and without locale sensitivity. - - -## `GuzzleHttp\Psr7\Utils::caselessEquals` - -`public static function caselessEquals(string $left, string $right): bool` - -Checks whether two strings are equal, comparing ASCII letters -case-insensitively and without locale sensitivity. - - -## `GuzzleHttp\Psr7\Utils::caselessRemove` - -`public static function caselessRemove(iterable $keys, $keys, array $data): array` - -Remove the items given by the keys, case insensitively from the data. - - -## `GuzzleHttp\Psr7\Utils::copyToStream` - -`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` - -Copy the contents of a stream into another stream until the given number -of bytes have been read. - -The copy stops if the destination `write()` returns 0, for example a -`BufferStream` at its high water mark or a full `DroppingStream`. For a -guaranteed full copy, use a normal writable stream such as a file or -`php://temp` stream. - - -## `GuzzleHttp\Psr7\Utils::copyToString` - -`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` - -Copy the contents of a stream into a string until the given number of -bytes have been read. - - -## `GuzzleHttp\Psr7\Utils::hash` - -`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` - -Calculate a hash of a stream. - -This method reads the entire stream to calculate a rolling hash, based on -PHP's `hash_init` functions. - - -## `GuzzleHttp\Psr7\Utils::modifyRequest` - -`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` - -Clone and modify a request with the given changes. - -This method is useful for reducing the number of clones needed to mutate -a message. - -- method: (string) Changes the HTTP method. -- set_headers: (array) Sets the given headers. -- remove_headers: (array) Remove the given headers. -- body: (mixed) Sets the given body. Present non-null values are converted with - `GuzzleHttp\Psr7\Utils::streamFor()`, including scalar values, resources, - streams, iterators, callable arrays, closures, invokable objects, and - objects with `__toString()`. String inputs remain literal bodies. -- uri: (UriInterface) Set the URI. -- query: (string) Set the query string value of the URI. -- version: (string) Set the protocol version. - - -## `GuzzleHttp\Psr7\Utils::readLine` - -`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string` - -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` - -`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` - -Create a new stream based on the input type. - -Options is an associative array that can contain the following keys: - -- metadata: Array of custom metadata. -- size: Size of the stream. - -This method accepts the following `$resource` types: - -- `Psr\Http\Message\StreamInterface`: Returns the value as-is. -- `string`: Creates a stream object that uses the given string as the contents. -- `resource`: Creates a stream object that wraps the given PHP stream resource. -- `Iterator`: If the provided value implements `Iterator`, then a read-only - stream object will be created that wraps the given iterable. Each time the - stream is read from, data from the iterator will fill a buffer and will be - continuously called until the buffer is equal to the requested read size. - Subsequent read calls will first read from the buffer and then call `next` - on the underlying iterator until it is exhausted. -- `object` with `__toString()`: If the object has the `__toString()` method, - the object will be cast to a string and then a stream will be returned that - uses the string value. -- `NULL`: When `null` is passed, an empty stream object is returned. -- `callable`: When a callable array, closure, or invokable object is passed and - no earlier resource or object rule applies, a read-only stream object will be - created that invokes the given callable. The callable is invoked with the - suggested number of bytes to read. The callable can return fewer or more bytes - than requested, but MUST return `false` or `null` when there is no more data - to return. Any additional bytes will be buffered and used in subsequent reads. - String inputs are always treated as string bodies, even when they name - callable functions. - -```php -$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); -$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); - -$generator = function ($bytes) { - for ($i = 0; $i < $bytes; $i++) { - yield ' '; - } -} - -$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); -``` - - -## `GuzzleHttp\Psr7\Utils::tryFopen` - -`public static function tryFopen(string $filename, string $mode): resource` - -Safely opens a PHP stream resource using a filename. - -When fopen fails, PHP normally raises a warning. This function adds an -error handler that checks for errors and throws an exception instead. - - -## `GuzzleHttp\Psr7\Utils::tryGetContents` - -`public static function tryGetContents(resource $stream): string` - -Safely gets the contents of a given stream. - -When stream_get_contents fails, PHP normally raises a warning. This -function adds an error handler that checks for errors and throws an -exception instead. - - -## `GuzzleHttp\Psr7\Utils::uriFor` - -`public static function uriFor(string|UriInterface $uri): UriInterface` - -Returns a UriInterface for the given value. - -This function accepts a string or UriInterface and returns a -UriInterface for the given value. If the value is already a -UriInterface, it is returned as-is. - - -## `GuzzleHttp\Psr7\MimeType::fromFilename` - -`public static function fromFilename(string $filename): string|null` - -Determines the mimetype of a file by looking at its extension. - - -## `GuzzleHttp\Psr7\MimeType::fromExtension` - -`public static function fromExtension(string $extension): string|null` - -Maps a file extensions to a mimetype. - - -# Additional URI Methods - -Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, -this library also provides additional functionality when working with URIs as static methods. - -## URI Types - -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, -the base URI. Relative references can be divided into several forms according to -[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2): - -- network-path references, e.g. `//example.com/path` -- absolute-path references, e.g. `/path` -- relative-path references, e.g. `subpath` - -The following methods can be used to identify the type of the URI. - -### `GuzzleHttp\Psr7\Uri::isAbsolute` - -`public static function isAbsolute(UriInterface $uri): bool` - -Whether the URI is absolute, i.e. it has a scheme. - -### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` - -`public static function isNetworkPathReference(UriInterface $uri): bool` - -Whether the URI is a network-path reference. A relative reference that begins with two slash characters is -termed an network-path reference. - -### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` - -`public static function isAbsolutePathReference(UriInterface $uri): bool` - -Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is -termed an absolute-path reference. - -### `GuzzleHttp\Psr7\Uri::isRelativePathReference` - -`public static function isRelativePathReference(UriInterface $uri): bool` - -Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is -termed a relative-path reference. - -### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` - -`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 -fragment component, identical to the base URI. When no base URI is given, only an empty URI reference -(apart from its fragment) is considered a same-document reference. - -## URI Components - -Additional methods to work with URI components. - -### `GuzzleHttp\Psr7\Uri::isDefaultPort` - -`public static function isDefaultPort(UriInterface $uri): bool` - -Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null -or the standard port. This method can be used independently of the implementation. - -### `GuzzleHttp\Psr7\Uri::composeComponents` - -`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` - -Composes a URI reference string from its various components according to -[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need -to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. - -### `GuzzleHttp\Psr7\Uri::fromParts` - -`public static function fromParts(array $parts): UriInterface` - -Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. - - -### `GuzzleHttp\Psr7\Uri::withQueryValue` - -`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` - -Creates a new URI with a specific query string value. Any existing query string values that exactly match the -provided key are removed and replaced with the given key value pair. A value of null will set the query string -key without a value, e.g. "key" instead of "key=value". - -### `GuzzleHttp\Psr7\Uri::withQueryValues` - -`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` - -Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an -associative array of key => value. - -### `GuzzleHttp\Psr7\Uri::withoutQueryValue` - -`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` - -Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the -provided key are removed. - -## Cross-Origin Detection - -`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. - -### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` - -`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` - -Determines if a modified URL should be considered cross-origin with respect to an original URL. - -Two URLs are cross-origin when their scheme, host, or effective port differ. Host comparison is case-insensitive, and missing ports use the default port for `http` or `https`. Other schemes do not receive implicit default ports. - -This helper only compares URI origins. It does not implement redirect handling or credential policy. - -## Reference Resolution - -`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according -to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web -browsers do when resolving a link in a website based on the current request URI. - -### `GuzzleHttp\Psr7\UriResolver::resolve` - -`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` - -Converts the relative URI into a new URI that is resolved against the base URI. - -### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` - -`public static function removeDotSegments(string $path): string` - -Removes dot segments from a path and returns the new path according to -[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4). - -### `GuzzleHttp\Psr7\UriResolver::relativize` - -`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` - -Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): - -```php -(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) -``` - -One use-case is to use the current request URI as base URI and then generate relative links in your documents -to reduce the document size or offer self-contained downloadable document archives. - -```php -$base = new Uri('http://example.com/a/b/'); -echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. -echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. -echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. -echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. -``` - -## Normalization and Comparison - -`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to -[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6). - -### `GuzzleHttp\Psr7\UriNormalizer::normalize` - -`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` - -Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. -This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask -of normalizations to apply. The following normalizations are available: - -- `UriNormalizer::PRESERVING_NORMALIZATIONS` - - Default normalizations which only include the ones that preserve semantics. - -- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` - - All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. - - Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` - -- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` - - Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of - ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should - not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved - characters by URI normalizers. - - Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` - -- `UriNormalizer::CONVERT_EMPTY_PATH` - - Converts the empty path to "/" for http and https URIs. - - Example: `http://example.org` → `http://example.org/` - -- `UriNormalizer::REMOVE_DEFAULT_HOST` - - Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host - "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to - RFC 3986. - - Example: `file://localhost/myfile` → `file:///myfile` - -- `UriNormalizer::REMOVE_DEFAULT_PORT` - - Removes the default port of the given URI scheme from the URI. - - Example: `http://example.org:80/` → `http://example.org/` - -- `UriNormalizer::REMOVE_DOT_SEGMENTS` - - Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would - change the semantics of the URI reference. - - Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` - -- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` - - Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes - and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization - may change the semantics. Encoded slashes (%2F) are not removed. - - Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` - -- `UriNormalizer::SORT_QUERY_PARAMETERS` - - Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be - significant (this is not defined by the standard). So this normalization is not safe and may change the semantics - of the URI. - - Example: `?lang=en&article=fred` → `?article=fred&lang=en` - -### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` - -`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` - -Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given -`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. -This of course assumes they will be resolved against the same base URI. If this is not the case, determination of -equivalence or difference of relative references does not mean anything. - +## Documentation + +- [PSR-7 Messages](docs/psr-7-messages.md) +- [Streams and Decorators](docs/streams-and-decorators.md) +- [URI Helpers](docs/uri-helpers.md) +- [PSR-17 Factories](docs/psr-17-factories.md) +- [Message Helpers](docs/message-helpers.md) +- [Diagnostic Values](docs/diagnostic-values.md) +- [Header and Query Helpers](docs/header-and-query-helpers.md) +- [Stream Helpers](docs/stream-helpers.md) +- [URI and MIME Helpers](docs/uri-and-mime-helpers.md) +- [Upgrade Guide](UPGRADING.md) +- [Changelog](CHANGELOG.md) ## Security -If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. - +If you discover a security vulnerability within this package, please send an +email to security@tidelift.com. All security vulnerabilities will be promptly +addressed. Please do not disclose security-related issues publicly until a fix +has been announced. Please see +[Security Policy](https://github.com/guzzle/psr7/security/policy) for more +information. ## License -Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. - +Guzzle is made available under the MIT License (MIT). Please see +[License File](LICENSE) for more information. ## For Enterprise Available as part of the Tidelift Subscription -The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) +The maintainers of Guzzle and thousands of other packages are working with +Tidelift to deliver commercial support and maintenance for the open source +dependencies you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact dependencies you +use. +[Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/libs/vendor/guzzlehttp/psr7/UPGRADING.md b/libs/vendor/guzzlehttp/psr7/UPGRADING.md index 5608bf9c..7fbd4b57 100644 --- a/libs/vendor/guzzlehttp/psr7/UPGRADING.md +++ b/libs/vendor/guzzlehttp/psr7/UPGRADING.md @@ -1,6 +1,892 @@ Guzzle PSR-7 Upgrade Guide ========================== +2.x to 3.0 +---------- + +Guzzle PSR-7 3.0 is a major release that raises the minimum PHP version, +updates to the PSR-7 v2 interfaces, validates header values more strictly, +preserves explicit request method casing, and rejects several invalid URI, +request, response, upload, query, stream, and multipart values that 2.x +previously accepted or cast. + +#### PHP Version and Dependencies + +Guzzle PSR-7 3.0 requires PHP `^7.4 || ^8.0`. Guzzle PSR-7 2.x supported PHP +`^7.2.5 || ^8.0`. + +If your application still supports PHP 7.2 or 7.3, continue using Guzzle PSR-7 +2.x until your minimum PHP version is raised. + +Guzzle PSR-7 3.0 requires `psr/http-message:^2.0` and +`psr/http-factory:^1.1`. Guzzle PSR-7 2.x supported +`psr/http-message:^1.1 || ^2.0` and `psr/http-factory:^1.0`. If your dependency +constraints pin `psr/http-message` to v1, update them before upgrading. + +Guzzle PSR-7 no longer depends on `ralouphie/getallheaders` and no longer +provides a transitive global `getallheaders()` polyfill. +`ServerRequest::fromGlobals()` continues to collect request headers internally. +Applications that call `getallheaders()` directly on SAPIs where PHP does not +provide it should require their own polyfill. + +#### Header Values + +Header values must now be strings or non-empty arrays of strings. Empty strings +remain valid explicit header values, but empty arrays, `null`, `false`, integers, +floats, and other non-string values are no longer cast or accepted. + +```php +// 2.x, no longer accepted in 3.0 +$response = $response->withHeader('Api-Version', 1); +$response = $response->withHeader('Empty-List', []); + +// 3.0 +$response = $response->withHeader('Api-Version', '1'); +$response = $response->withHeader('Empty-Value', ''); +``` + +Use `withoutHeader()` to remove a header. + +#### Request Method Casing + +Request methods passed explicitly to `Request`, `ServerRequest`, `withMethod()`, +`Message::parseRequest()`, and the PSR-17 factories are no longer uppercased. +PSR-7 treats method names as case-sensitive, so these APIs now preserve the +method exactly as provided. If your application requires uppercase methods, +normalize methods before constructing or modifying requests. + +`ServerRequest::fromGlobals()` is the compatibility-oriented exception. It +continues to uppercase string `REQUEST_METHOD` values read from PHP server +globals, matching Guzzle PSR-7 2.x and common server request behavior. This +normalization only applies when hydrating from globals; it does not apply to +methods passed explicitly to constructors, factories, or `withMethod()`. + +```php +// 2.x +$request = new Request('get', '/'); +$request->getMethod(); // GET + +// 3.0 +$request = new Request('get', '/'); +$request->getMethod(); // get + +// 3.0, server globals +$_SERVER['REQUEST_METHOD'] = 'post'; +$request = ServerRequest::fromGlobals(); +$request->getMethod(); // POST +``` + +#### Native PSR-7 Parameter Types + +Guzzle PSR-7 3.0 requires the argument types documented by PSR-7 more strictly. +It adds the native parameter types from `psr/http-message` v2. Code passing +invalid argument types may now receive PHP `TypeError` exceptions instead of +package-specific `InvalidArgumentException` exceptions or implicit casts. + +Native parameter type changes include: + +- `MessageInterface::withProtocolVersion()` now requires `string`. +- Message header names now require `string`. +- `RequestInterface::withRequestTarget()` and `withMethod()` now require `string`. +- `RequestInterface::withUri()` now requires `bool` for `$preserveHost`. +- `ResponseInterface::withStatus()` now requires `int` status codes and `string` reason phrases. +- Server request attribute names now require `string`. +- `UriInterface::withPort()` now requires `int|null`. +- URI scheme, user info, host, path, query, and fragment mutators now require strings. +- Stream `seek()`, `read()`, `write()`, and `getMetadata()` now require their PSR-7 v2 parameter types. +- `UploadedFileInterface::moveTo()` now requires a string target path. + +Update callers to pass values of the documented type before calling these +methods: + +```php +// 2.x, no longer supported in 3.0 +$response = $response->withStatus('201'); +$uri = $uri->withPort('8080'); + +// 3.0 +$response = $response->withStatus(201); +$uri = $uri->withPort(8080); +``` + +#### Request Modification Changes + +`Utils::modifyRequest()` now validates recognized change values before applying +request modifications. Unknown change keys are still ignored. Explicit `null` +values are no longer treated as omitted recognized changes; omit the key instead. + +Recognized change values must use the documented types: + +- `method`: `string` +- `uri`: `UriInterface` +- `query`: `string` +- `version`: `string` +- `body`: `resource|string|StreamInterface|callable|\Iterator|\Stringable` +- `set_headers`: `array>` +- `remove_headers`: `array` + +When a `uri` change contains a host, the synthesized `Host` header now +includes any non-default URI port, matching the `Request` constructor. 2.x +omitted port zero and every port on schemes other than HTTP and HTTPS. + +#### Uploaded Files + +`ServerRequestInterface::withUploadedFiles()` now rejects invalid nested upload +trees. Every leaf must be an `UploadedFileInterface` instance. + +`ServerRequest::normalizeFiles()` and `ServerRequest::fromGlobals()` now reject +malformed `$_FILES` specifications earlier. Single-file specifications must +contain non-null `tmp_name`, `size`, and `error` values. Single-file and nested +file `size` values and `error` values must be non-negative PHP integers; +numeric strings are no longer cast. If PHP supplies an upload size as a string +because the byte count cannot fit in `PHP_INT_MAX`, it is rejected rather than +truncated or cast. Nested specifications must provide `tmp_name`, `size`, and +`error` as arrays. Every key in `tmp_name` must also exist in `size` and +`error`; additional metadata entries without a matching `tmp_name` entry are +ignored. When nested `name` or `type` metadata is provided, it must also be an +array. + +If your tests or adapters build `$_FILES` arrays manually, populate the full +shape or create `UploadedFile` instances directly. + +```php +// 2.x, no longer accepted in 3.0 +$files = ['file' => ['tmp_name' => '/tmp/php123', 'error' => '0']]; + +// 3.0 +$files = ['file' => ['tmp_name' => '/tmp/php123', 'size' => 123, 'error' => UPLOAD_ERR_OK]]; +``` + +For stream-backed uploads, `UploadedFile::moveTo()` now rewinds seekable streams +before copying them. If application code reads from a seekable uploaded stream +before calling `moveTo()`, 3.0 writes the full stream contents to the target +instead of only the unread suffix. Non-seekable stream-backed uploads continue to +copy from their current position because consumed bytes cannot be replayed. + +#### Parsed Body Values + +`ServerRequestInterface::withParsedBody()` now rejects values other than +`array`, `object`, or `null`. + +```php +// 2.x, no longer accepted in 3.0 +$request = $request->withParsedBody('name=value'); + +// 3.0 +$request = $request->withParsedBody(['name' => 'value']); +``` + +#### URI Host and Scheme Validation + +URI hosts containing URI delimiters, backslashes, embedded ports passed to +`withHost()`, malformed IP-literal brackets, unbracketed IPv6, or other +malformed host forms are no longer accepted. URI schemes containing whitespace +or control characters are also no longer accepted. + +If you previously passed a host and port together to `withHost()`, split them +between `withHost()` and `withPort()`: + +```php +// 2.x, no longer accepted in 3.0 +$uri = $uri->withHost('example.com:8080'); + +// 3.0 +$uri = $uri->withHost('example.com')->withPort(8080); +``` + +Normal URI strings with ports are still supported: + +```php +$uri = new Uri('https://example.com:8080/path'); +``` + +URI parsing now accepts bracketed IPv6 and IPvFuture hosts consistently with +`withHost()` for userinfo and network-path authorities such as +`http://user@[::1]/`, `//[::1]`, and `http://[v7.a:b]/`. Invalid +delimiter-free bracketed literals, such as `[gggg::1]`, are also reported with +the intact host. Bracketed literals containing authority/path delimiters, such +as `[a@b]` or `[v1.a/b]`, still reject after fallback parsing and may report the +mangled parsed host. + +Userinfo before a bracketed IP-literal host is now percent-encoded ahead of +parsing, so raw control bytes yield encoded userinfo, such as `us%01er`, +instead of a silently mutated value, and raw DEL bytes in bracketed hosts are +rejected instead of parsed as a mutated host. Consistent with registered-name +authorities, such userinfo containing invalid UTF-8 is now rejected, +percent-sequences such as `u%41` are preserved rather than decoded, and a +literal `+` is preserved rather than decoded to a space. + +Only an optional numeric port (which may be empty) and a path, query, or +fragment may follow a bracketed IP-literal host. Trailing bytes that are +neither, such as `http://[::1]:80@evil/` or `http://[::1]:80x/`, are now +rejected instead of being reparsed into a different host. + +Parsing still URL-decodes bracketed IP-literal hosts before validation +(registered-name hosts round-trip unchanged), so a literal `+` in a bracketed +IP-literal decodes to a space and is rejected: `withHost('[v1.fe80::a+en1]')` +accepts the literal while parsing `http://[v1.fe80::a+en1]/` rejects it. +Percent-encoding inside a bracketed IP-literal is now rejected during parsing as +well, since RFC 3986 IP-literals contain no percent-encoding, so +`http://[%3A%3A1]/` no longer decodes to `[::1]`; this matches `withHost()` and +`Rfc3986::isValidHost()`. + +Percent-encoded octets in a registered-name host are normalized to uppercase +hex, so a host such as `a%c3%a9b` is represented as `a%C3%A9b`. Malformed +percent sequences and percent-encoded octets that decode to a byte forbidden in +a host, such as `ex%zz` and `%2fhost`, are rejected. + +`Uri::fromParts()` accepts integer and decimal digit string ports, but floats and +other port values are no longer cast. + +Common host forms such as `localhost`, single-label hosts, underscores, Unicode +hosts, valid IPv6 literals, and normal host and port URI strings remain +supported. + +URI schemes must now match RFC 3986 syntax and begin with a letter. + +```php +// 2.x, no longer accepted in 3.0 +$uri = (new Uri())->withScheme('0'); + +// 3.0 +$uri = (new Uri())->withScheme('https'); +``` + +The stricter validation also applies when a request is created or modified from +a custom `UriInterface` implementation and its host is used to generate or +update a `Host` header. + +`ServerRequest::getUriFromGlobals()` now falls back to `SERVER_NAME`, then +`SERVER_ADDR`, then the existing default host behavior for malformed +`HTTP_HOST` values. It also rejects zero-port `HTTP_HOST` authorities and +malformed `SERVER_PORT` values when fallback authority reconstruction needs the +server port. When `REQUEST_URI` is absolute-form or CONNECT authority-form and +supplies a valid authority, that authority is used before fallback `SERVER_PORT` +validation. Origin-form, asterisk-form, missing `REQUEST_URI`, and fallback +reconstruction paths still reject malformed `SERVER_PORT` values when fallback +authority reconstruction needs the server port. + +Absolute-form `REQUEST_URI` userinfo is removed when reconstructing the URI and +request target from globals, including empty userinfo such as +`http://@example.com/`. The host after the last raw `@` remains the URI host. + +`Message::parseRequest()` now applies 3.0 authority rules when deriving a URI +from an origin-form or asterisk-form request target. Host ports with leading +zeroes are normalized for URI reconstruction, and port zero is rejected. +It also rejects duplicate `Host` field lines, including case-insensitive +duplicates. Any present raw `Host` field is validated before returning a parsed +request, even when the request target supplies the URI authority, such as +absolute-form and CONNECT requests. Valid `Host` values may still differ from +the absolute-form or CONNECT request-target authority. + +For server globals, applications that need to reject malformed inbound `Host` +headers should validate the original server parameters before calling +`getUriFromGlobals()` or inspect them afterward. + +`Message::parseRequest()` now applies the same HTTP authority validation to +absolute-form request targets. A zero or padded-zero port +(`http://example.com:0/admin`) is rejected instead of producing a request +whose synthesized `Host` header the same parser rejects elsewhere. +Absolute-form targets with no URI host, such as `file:///etc/passwd`, are +also rejected instead of producing a hostless request URI. + +Absolute-form targets whose authority contains userinfo are also rejected, +including empty userinfo such as `http://@example.com/`. RFC 9110 deprecates +userinfo in http(s) target URIs and directs recipients to treat its presence +as an error; `Host` headers and CONNECT targets already reject it. These +rules apply to absolute-form targets of every scheme. +`ServerRequest::fromGlobals()` is unchanged and continues to strip +`REQUEST_URI` userinfo. + +URI hosts now validate percent-encoding. Malformed sequences such as `ex%zz`, +and percent-encoded octets that decode to bytes the raw host grammar already +rejects (controls, space, DEL, `/`, `?`, `#`, `@`, `\`, `:`, `[`, `]`, and `%` +itself) throw `MalformedUriException` from URI parsing and +`InvalidArgumentException` from `Uri::withHost()`, and are rejected wherever +hosts are validated, including `Host` headers and request targets in +`Message::parseRequest()`. WHATWG-conformant browsers reject all of these hosts; +curl rejects them too, except encoded DEL (`%7F`), which it decodes and forwards +to name resolution. Other percent-encoded octets, including UTF-8 data such as +`a%C3%A9b`, remain accepted and are normalized to uppercase hex. + +IPv6 hosts are now canonicalized to their RFC 5952 form when a URI is +constructed, so `getHost()`, `getAuthority()`, and `(string) $uri` return the +canonical spelling and synthesized `Host` headers use it. Leading zeros are +suppressed, hexadecimal fields are lowercase, and the longest run of two or +more zero fields is collapsed with `::`. Embedded dotted-decimal notation +follows the rendering policy of BIND-derived `inet_ntop()` implementations and +curl 8.11 and newer: exactly the IPv4-mapped (`::ffff:0:0/96`) and deprecated +IPv4-compatible (`::/96`) layouts use it, while other embedded-IPv4 forms, +including translated (NAT64) well-known prefixes such as `64:ff9b::/96` +(RFC 6052), serialize in pure hexadecimal fields. + +```php +// 2.x preserved the spelling as given +(string) new Uri('http://[0:0:0:0:0:0:0:1]/'); // http://[0:0:0:0:0:0:0:1]/ + +// 3.0 +(string) new Uri('http://[0:0:0:0:0:0:0:1]/'); // http://[::1]/ +(string) new Uri('http://[::FFFF:7F00:1]/'); // http://[::ffff:127.0.0.1]/ +(string) new Uri('http://[2001:db8:3:4::192.0.2.33]/'); // http://[2001:db8:3:4::c000:221]/ +``` + +Applications that persist URI strings, for example as cache keys, will observe +the new canonical form for previously non-canonical IPv6 spellings. Equivalent +spellings of the same address now compare as same-origin in +`UriComparator::isCrossOrigin()`, which canonicalizes bracketed IPv6 literals +from any PSR-7 implementation before comparing hosts, and as equivalent in +`UriNormalizer::isEquivalent()`. The new +`UriNormalizer::CANONICALIZE_IPV6_HOST` flag, included in the default +`UriNormalizer::PRESERVING_NORMALIZATIONS`, requests the canonical host from +other PSR-7 implementations through `withHost()` and keeps the result only +when the returned `getHost()` exactly matches the requested spelling; a +nonexact result leaves that step unchanged while other selected normalizations +still apply, and setter exceptions propagate. `UriComparator` does not share +this limitation, since it canonicalizes the extracted host text directly. The +public helper `Rfc3986::canonicalizeIpv6()` exposes the underlying +transformation. + +#### Request Host Synchronization + +`Request::withUri()` now applies PSR-7 Host header synchronization before using +the same-URI no-op shortcut. When the provided URI is the same object already +attached to the request, the method may still return a new request if the URI +has a host and the current Host header is missing, empty, or stale. + +With `$preserveHost = true`, a non-empty Host header is still preserved. Missing +or empty Host headers are treated as absent and are populated from the URI when +the URI contains a host. + +```php +$request = (new Request('GET', 'http://example.com:8124/'))->withoutHeader('Host'); + +$updated = $request->withUri($request->getUri()); + +$updated->getHeaderLine('Host'); // example.com:8124 +``` + +If your application intentionally sends an empty or stale Host header, set it +after calling `withUri()` or preserve a non-empty Host header explicitly. + +`Message::toString()` now applies the same URI host and port synthesis when +serializing a request without a `Host` header. Generated `Host` lines include +non-null URI ports. + +`Message::toString()` also validates the host it synthesizes from the request +URI and throws `InvalidArgumentException` for an invalid host, closing a header- +injection vector. This affects only a custom `UriInterface` implementation that +returns an invalid host when the request has no stored `Host` header; first- +party `Uri` instances always carry a valid host and are unaffected. + +#### URI Paths and Request Targets + +`Uri::getPath()` now normalizes multiple leading slashes to one slash when +returning the path in isolation. Casting the URI to string still preserves the +original URI representation. + +```php +$uri = new Uri('http://example.org//valid///path'); + +$uri->getPath(); // /valid///path +(string) $uri; // http://example.org//valid///path +``` + +`Request::getRequestTarget()` applies the same normalization for URI-derived +origin-form request targets. + +Reference resolution and normalization (`UriResolver`, `UriNormalizer`, and +`Uri::isSameDocumentReference()`) operate on the raw path from the URI string +form and are therefore unaffected by this normalization. + +Authority-less `file` URIs with rootless paths now serialize without the `//` +authority separator: `(string) new Uri('file:foo/bar')` returns `file:foo/bar` +instead of `file://foo/bar`, which reparses with host `foo` and path `/bar`. +Rooted paths such as `file:///myfile` keep their existing serialization. + +Authority-less `file` URIs with empty paths now serialize as `file:` instead of +`file://`, which `new Uri()` itself rejects as unparseable. This affects +degenerate URIs such as `new Uri('file:')` or +`Uri::fromParts(['scheme' => 'file'])`; the serialization of every file URI +with a non-empty path is unchanged. + +`UriResolver::removeDotSegments()` now applies RFC 3986 Section 5.2.4 to `..` +segments above the root of an absolute path: excess `..` segments no longer +consume the root, so a following empty segment is preserved. Resolving `/..//a` +against `http://example.org/base` yields `http://example.org//a` where 2.x +produced `http://example.org/a`. When the resulting URI has no authority, +`UriResolver::resolve()` and `UriNormalizer::normalize()` serialize such a +`//`-leading path with a `/.` prefix (`mailto:/.//a`), like the WHATWG URL +Standard, instead of collapsing the slashes or throwing. + +#### URI Reference Relativization + +`UriResolver::relativize()` now returns a network-path reference (for example +`//example.com`) when the target URI has the same authority as the base URI +but an empty path that no other relative reference round-trips. No path +reference can express such a target, as resolving one always produces a path +of at least `/`, and an empty reference would keep the base path or inherit +the base query or fragment. The returned reference resolves back to the +exact target string, restoring the documented round-trip guarantee for these +targets. + +```php +$base = new Uri('http://example.com/a'); +$target = new Uri('http://example.com'); + +// 2.x +(string) UriResolver::relativize($base, $target); // ../ +// which resolved back to http://example.com/ + +// 3.0 +(string) UriResolver::relativize($base, $target); // //example.com +``` + +The same applies when the base URI has a query or fragment component that an +empty relative reference would otherwise inherit. When the base URI has an +empty path as well and nothing would be inherited, shorter references such +as the empty reference, `#fragment` or `?query` are still returned. + +`relativize()` also no longer returns the empty reference when the target +path equals the base path but the base has a fragment the target lacks, as +the empty reference would reintroduce that fragment. A relative-path +reference, or a query reference when the target has a query, is returned +instead. When the relative-path reference would be a single path segment +containing a colon, which would be mistaken for a scheme name, it is +prefixed with `./` (for example `./a:b`); 2.x threw a `MalformedUriException` +for such targets when the base had a query the target lacked. + +#### HTTP Start-line Parsing + +`Message::parseRequest()` and `Message::parseResponse()` now validate HTTP +start-line fields more strictly. Malformed request methods, request targets +containing whitespace or control characters, malformed protocol versions, +invalid response status codes, invalid response spacing, and reason phrases +containing invalid control characters now throw `InvalidArgumentException`. + +`Request` and `Response` constructors and mutators apply the same validation to +protocol versions, request targets, status codes, and reason phrases. If you +parse raw HTTP messages or construct messages from partially validated input, +normalize or reject invalid values before passing them to Guzzle PSR-7. + +```php +// 2.x-style tolerant input, no longer accepted in 3.0 +Message::parseRequest("GET /foo bar HTTP/1.1\r\nHost: example.com\r\n\r\n"); +new Response(200, [], null, 'HTTP/1.1'); + +// 3.0 +Message::parseRequest("GET /foo%20bar HTTP/1.1\r\nHost: example.com\r\n\r\n"); +new Response(200, [], null, '1.1'); +``` + +`ServerRequest::fromGlobals()` applies the same validation to the +`REQUEST_METHOD` and `SERVER_PROTOCOL` server values. Malformed values that 2.x +hydrated, such as the `SERVER_PROTOCOL` value `INCLUDED` that Apache sets for +server-side include subrequests, now throw `InvalidArgumentException`. Sanitize +`$_SERVER` before calling `fromGlobals()` if such environments must be +tolerated. + +`Request::withRequestTarget('')` throws `InvalidArgumentException`; omit the +explicit request target to derive `/` or the URI-derived target automatically. + +`Message::parseMessage()` no longer unfolds folded HTTP/1.0 messages whose +start line carries control bytes in the request target; such messages now +throw the obsolete-line-folding `InvalidArgumentException`. +`Message::parseRequest()` and `Message::parseResponse()` rejected these +messages either way. + +#### Query Builder Values + +`Query::build()` now rejects unsupported values instead of relying on PHP string +casts. Query values must be scalar, `null`, stringable objects, or flat arrays of +those values. + +Nested arrays, resources, and objects without `__toString()` now throw +`InvalidArgumentException`. + +```php +// Before: could produce warnings or silently mangle the value. +Query::build(['filter' => ['name' => ['value']]]); + +// After: use explicit query keys for nested query shapes. +Query::build(['filter[name]' => 'value']); +``` + +Flat arrays are still supported for repeated query parameters: + +```php +Query::build(['tag' => ['a', 'b']]); +// tag=a&tag=b +``` + +`Uri::withQueryValues()` is stricter than `Query::build()` and requires `string` +or `null` values; cast numeric and boolean query values to string. + +#### Non-string Scalar Bodies + +`Utils::streamFor()` and message bodies no longer accept `int`, `float`, or +`bool` values. Cast them to strings first. + +```php +// 2.x, no longer accepted in 3.0 +$response = new Response(200, [], 404); + +// 3.0 +$response = new Response(200, [], '404'); +``` + +#### PumpStream Source Callables + +`PumpStream` source callables must now return a non-empty string when producing +data. Returning an empty string now throws `RuntimeException` instead of being +retried indefinitely. Return `false` or `null` to signal EOF. + +If your callable used `''` to mean "temporarily no data", update it to wait +until data is available, return a non-empty string, or return `false` or `null` +when the stream is complete. + +#### Iterator-backed Streams + +`Utils::streamFor()` now validates values yielded by `Iterator` instances before +passing them to the internal `PumpStream`. Strings, integers, finite floats, +booleans, `null`, and stringable objects are converted to string chunks. +Non-finite floats, arrays, resources, and non-stringable objects now throw +`UnexpectedValueException` when the stream is read. + +Iterator exhaustion is now the only EOF signal for iterator-backed streams. +Yielding `false`, `null`, or an empty string no longer ends the stream; those +values are zero-length chunks and are skipped while the iterator advances. If +your iterator yielded `false` or `null` to stop streaming, update it to finish +iteration instead. + +Avoid iterators that yield only zero-length chunks indefinitely. Such iterators +never produce bytes and never reach EOF, so they cannot satisfy stream reads. + +```php +// Before: yielding false or null could stop an iterator-backed stream early. +$stream = Utils::streamFor(new ArrayIterator([false, 'body'])); + +// After: false and null are skipped chunks. End the iterator to signal EOF. +$stream = Utils::streamFor(new ArrayIterator(['body'])); +``` + +#### Stream Behavior Changes + +All stream implementations now reject negative `read()` lengths with +`RuntimeException`. In 2.x, some decorators passed negative lengths through, +some returned sliced data, and some behavior varied by PHP version. + +`LimitStream` now rejects negative offsets and limits below `-1`. For +non-seekable streams, offsets are tracked by the number of bytes actually +skipped. Short reads are retried until the offset is reached, EOF is reached, or +the decorated stream stops making progress. + +`StreamWrapper` now translates `RuntimeException` failures from the wrapped +PSR-7 stream into PHP stream-wrapper failure values. When using a resource from +`StreamWrapper::getResource()`, functions such as `fread()`, `fwrite()`, +`fseek()`, `feof()`, and `fstat()` may now return normal PHP failure values +instead of propagating the PSR-7 stream exception. Call the PSR-7 stream directly +if you need exception-based failure handling. + +The `StreamWrapper::stream_read()` callback no longer declares a native return +type so read failures can return `false`. The `StreamWrapper::stream_tell()` +callback no longer declares a native return type so post-seek position lookup +failures can make `fseek()` fail. + +#### Stream Mode Capabilities + +`Stream::isReadable()` and `Stream::isWritable()` now follow PHP stream mode +semantics more closely. Update modes are detected by the presence of `+`, +including valid modes such as `rt+`, `wt+`, `at+`, `xt+`, and `ct+`. + +Literal `rw` metadata is now treated as read-only, matching PHP real-file +streams. If a custom stream wrapper previously exposed `rw` for a writable +resource, open it with a valid update mode such as `r+`, `w+`, or `a+` instead. + +#### Stream Copy Behavior + +Stream sizes, offsets, high-water marks, and byte counts are now validated as +non-negative PHP integers where applicable. Operations that would overflow +`PHP_INT_MAX` throw `OverflowException` instead of silently wrapping or producing +an invalid position or size. + +`Utils::copyToStream()` now returns the number of bytes copied and throws a +`RuntimeException` when the destination stream cannot make progress, for example +a `BufferStream` at its high-water mark or a full `DroppingStream`. Its +signature changed from `: void` to `: int`, but callers that ignore the return +value do not need to change anything. In 2.x, the copy stopped silently when the +destination could not make progress. For a guaranteed full copy, use a normal +writable stream such as a file or `php://temp` stream. + +#### Stream Timeout Detection + +Timed-out stream operations now throw +`GuzzleHttp\Psr7\Exception\TimeoutException`, which extends +`RuntimeException`. `Stream::read()`, `Stream::write()`, +`AppendStream::read()`, `CachingStream::read()`, `InflateStream::read()`, +`Utils::copyToStream()`, `Utils::copyToString()`, `Utils::hash()`, +`Utils::readLine()`, and `Utils::tryGetContents()` detect PHP-style stream +timeout metadata when a read or write operation cannot make progress. Timeout +detection is best-effort; custom stream implementations that do not expose +`timed_out` metadata continue to behave as before. Previously, timed-out reads +could be treated as EOF or return partial results, and timed-out writes could be +reported as generic write failures or no-progress writes. + +#### Message Body Summaries + +`Message::bodySummary()` still summarizes seekable bodies from the beginning, +even when the body was already partially read. It now restores the body cursor to +the position it had before the summary was created. In 2.x, calling +`bodySummary()` left seekable bodies rewound to the beginning. The optional +`$truncateAt` argument now accepts `null` as an explicit request for the default +summary length, matching the behavior of omitting the argument. + +Most applications do not need to change anything. Check your code only if you +called `bodySummary()` and then read the same body while relying on +`bodySummary()` to leave the body rewound. If you need to read the body from the +beginning after summarizing it, call `Message::rewindBody()` explicitly. + +#### Stream Lifecycle + +`FnStream` now treats `close()` and successful `detach()` calls as terminal +lifecycle operations. Its configured `close` callback is invoked at most once; +repeated `close()` calls are no-ops, destruction after explicit close no longer +invokes the close callback, and closed or detached streams no longer forward +read, write, seek, metadata, or stringification callbacks. `FnStream` also +suppresses exceptions thrown by destructor-triggered close callbacks. Call +`close()` explicitly if cleanup failures must be observed. + +`CachingStream::close()` is now idempotent. Calling `close()` after `detach()` +still closes the remote stream owned by the `CachingStream`, but it no longer +closes the detached cache resource returned to the caller. Repeated `close()` +calls are no-ops. + +`InflateStream::close()` now also closes the compressed source stream that was +passed to its constructor. In 2.x, closing an `InflateStream` left the source +stream open. Call `detach()` instead of `close()` if the compressed source +stream must stay open; `close()` after `detach()` no longer closes the source. + +`PumpStream::close()` and `PumpStream::detach()` now discard internally buffered +unread bytes. If a callable or iterator source returns more bytes than a read +requested, drain the stream before closing it if you need those buffered bytes. + +#### Multipart Part Headers and Metadata + +`MultipartStream` no longer adds default `Content-Length` headers to individual +`multipart/form-data` parts. RFC 7578 section 4.8 says multipart form-data +parts must not include `Content-*` headers other than the supported multipart +part headers, so 3.0 stops generating per-part `Content-Length` by default. + +If your tests compare raw multipart payloads, remove the generated +`Content-Length` lines from expected strings: + +```text +// 2.x generated: +--boundary\r\n +Content-Disposition: form-data; name="foo"\r\n +Content-Length: 3\r\n +\r\n +bar\r\n + +// 3.0 generates: +--boundary\r\n +Content-Disposition: form-data; name="foo"\r\n +\r\n +bar\r\n +``` + +Applications can still pass an explicit `Content-Length` header in a multipart +element's `headers` array if a non-standard peer requires it: + +```php +$body = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => 'bar', + 'headers' => ['Content-Length' => '3'], + ], +]); +``` + +`MultipartStream` now escapes generated `Content-Disposition` `name` and +`filename` parameters before serializing multipart part headers. Double quotes, +carriage returns, and line feeds are encoded as `%22`, `%0D`, and `%0A`. Literal +backslashes and other characters are serialized unchanged, matching browser +multipart form submission behavior. + +```php +// Before: these values were interpolated into the generated part header. +$body = new MultipartStream([ + [ + 'name' => "field\"\r\nname", + 'filename' => "avatar\"\r\n.txt", + 'contents' => 'body', + ], +]); + +// After: the generated Content-Disposition parameters contain +// field%22%0D%0Aname and avatar%22%0D%0A.txt. +``` + +Explicit custom boundaries are now validated using RFC 2046 multipart boundary +syntax. Omit the boundary or pass `null` to continue using a generated random +boundary. + +The string `'0'` is now treated as an explicit custom boundary and is serialized +literally. In 2.x, PHP truthiness caused `new MultipartStream($elements, '0')` +to use a generated random boundary. Omit the boundary or pass `null` when you +want a generated boundary. + +Custom multipart part header names and values are also validated before +serialization. Header names must be valid HTTP tokens, and header values must be +strings without CR, LF, or other invalid control bytes. + +`MultipartStream` now preserves trailing spaces and tabs in custom multipart +part header values when serializing the body. In 2.x, the final serialized part +header line was trimmed as a side effect of removing the generated header +terminator. Normal multipart parsers treat this optional whitespace as +insignificant, but tests, signatures, or snapshots that compare raw multipart +body bytes may need updated expectations. + +#### URI Userinfo Redaction + +`Utils::redactUserInfo()` now redacts all non-empty URI userinfo, including +username-only userinfo. In 2.x, it only redacted the password portion when +userinfo contained a password delimiter. + +```php +use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\Psr7\Utils; + +// 2.x: https://TOKEN@example.com +// 3.0: https://***@example.com +(string) Utils::redactUserInfo(new Uri('https://TOKEN@example.com')); + +// 2.x: https://user:***@example.com +// 3.0: https://***@example.com +(string) Utils::redactUserInfo(new Uri('https://user:pass@example.com')); +``` + +#### Header List Helpers + +The deprecated `Header::normalize()` method was removed. Use +`Header::splitList()` to split HTTP headers that are defined as comma-separated +lists. + +`Header::splitList()` now trims list elements with spaces, horizontal tabs, +carriage returns, and line feeds. 2.x also trimmed null bytes and vertical +tabs. Validated header values cannot contain those bytes, so this only affects +strings passed to `Header::splitList()` directly. + +#### Non-instantiable Utility Classes + +Static utility and constant classes such as `Header`, `Message`, `MimeType`, +`Query`, and `Utils` now have private constructors. Replace any accidental +instantiation with static method calls or constant access. + +#### Native PHP Serialization of Streams + +Guzzle PSR-7 stream implementations no longer support native PHP `serialize()` +or `unserialize()`. Persist stream contents explicitly and recreate streams with +`Utils::streamFor()` when needed. + +#### URI Normalization of Userinfo and Host + +`UriNormalizer::CAPITALIZE_PERCENT_ENCODING` and +`UriNormalizer::DECODE_UNRESERVED_CHARACTERS` now also apply to the userinfo and +host components. In 2.x, these normalizations only rewrote the path, query, and +fragment. + +Since the host is case-insensitive and PSR-7 requires it to be lowercase, octets +decoded in the host are lowercased. Reserved percent-encoded octets such as +`%3A` are never decoded, so component boundaries cannot change, and these two +flags never modify bracketed IP-literal hosts, which only the separate +`UriNormalizer::CANONICALIZE_IPV6_HOST` normalization may canonicalize. Both +flags are part of `UriNormalizer::PRESERVING_NORMALIZATIONS`, so the output of +`UriNormalizer::normalize()` and the result of `UriNormalizer::isEquivalent()` +can change for URIs whose userinfo or host contains percent-encoded octets. +Custom `UriInterface` implementations now receive `withUserInfo()` or +`withHost()` calls from the normalizer when a normalization changes those +components; unchanged components are never rewritten. The rewrite is kept only +when the value returned by the implementation matches the normalized form, and +a userinfo with an empty user segment is never rewritten. If a setter returns a +different representation, that rewrite is discarded, while other selected +normalizations still apply, and setter exceptions propagate. No percent-encoding +normalization is applied to a component with malformed percent syntax, such as a +`%` not followed by two hexadecimal digits. + +```php +use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\Psr7\UriNormalizer; + +// 2.x: http://%75ser@ex%61mple.com/ +// 3.0: http://user@example.com/ +(string) UriNormalizer::normalize(new Uri('http://%75ser@ex%61mple.com/')); +``` + +#### URI Ports and Authority Handling + +Several 3.0 changes affect how URI ports are accepted, validated, and rendered. +Each is described in its own section above: + +- `UriInterface::withPort()` now requires `int|null`; see "Native PSR-7 + Parameter Types". +- `Uri::fromParts()` validates ports instead of casting them, and `withHost()` + rejects embedded `host:port` values; see "URI Host and Scheme Validation". +- A generic `Uri` can represent ports 0 through 65535. Inbound HTTP authority + parsing is stricter: `Message::parseRequest()` rejects zero-valued ports but + accepts nonzero leading-zero ports, normalizing the reconstructed URI while + preserving the raw `Host` or request-target text; see "HTTP Start-line + Parsing" and "URI Host and Scheme Validation". +- Server globals reject a zero-valued `HTTP_HOST` and validate `SERVER_PORT` + when fallback authority reconstruction needs it. A recognized absolute-form + or CONNECT `REQUEST_URI` authority takes precedence and can still produce a + URI with port zero; see "URI Host and Scheme Validation". +- Synthesized `Host` headers now include any non-default URI port; see "Request + Modification Changes" and "Request Host Synchronization". + +`Uri` now knows the default ports of the `ws` and `wss` schemes, 80 and 443 per +RFC 6455. An explicit default port on a `ws` or `wss` URI is removed when the +URI is constructed or modified, `Uri::isDefaultPort()` returns `true` for such +URIs, and `UriNormalizer::normalize()` with the `REMOVE_DEFAULT_PORT` flag +removes the port from other `UriInterface` implementations as well. In 2.x, +these ports were preserved. + +```php +use GuzzleHttp\Psr7\Uri; + +// 2.x: ws://example.com:80/chat +// 3.0: ws://example.com/chat +(string) new Uri('ws://example.com:80/chat'); + +// 2.x: 443 +// 3.0: null +(new Uri('wss://example.com:443'))->getPort(); +``` + +Because a native `Uri` never carries a default `ws` or `wss` port, the `Host` +header synchronized from such a request URI omits the port. `Request` and +`Message` `Host` synthesis and `Utils::modifyRequest()` still append an explicit +default port that a `ws` or `wss` URI from another `UriInterface` implementation +reports. + +`UriComparator::isCrossOrigin()` now applies these default ports when comparing +effective ports, so two `ws` or `wss` URIs that differ only by an explicit +default port, such as `ws://example.com/` and `ws://example.com:80/`, are +same-origin no matter which `UriInterface` implementation supplies them. In 2.x, +such pairs were considered cross-origin. Schemes other than `http`, `https`, +`ws`, and `wss` still receive no implicit default port. + +#### Sensitive Stack Trace Arguments + +Credential-bearing URI, server-global, Authorization-header, and cookie +arguments are marked with `#[\SensitiveParameter]`. PHP 8.2 and later replace +those arguments in stack traces with `SensitiveParameterValue`. PHP 7.4 through +8.1 do not redact trace arguments. + +This does not redact logs, exception messages, object properties, wire traffic, +captured variables, return values, user callbacks, or the separate executing +object in an explicit backtrace. + 1.x to 2.0 ---------- diff --git a/libs/vendor/guzzlehttp/psr7/composer.json b/libs/vendor/guzzlehttp/psr7/composer.json index 86f2657c..135eba57 100644 --- a/libs/vendor/guzzlehttp/psr7/composer.json +++ b/libs/vendor/guzzlehttp/psr7/composer.json @@ -50,22 +50,22 @@ } ], "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/polyfill-php80": "^1.25" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.52 || ^9.6.34" + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" diff --git a/libs/vendor/guzzlehttp/psr7/docs/diagnostic-values.md b/libs/vendor/guzzlehttp/psr7/docs/diagnostic-values.md new file mode 100644 index 00000000..36f99a9f --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/diagnostic-values.md @@ -0,0 +1,30 @@ +# Diagnostic Values + +## `GuzzleHttp\Psr7\DiagnosticValue::escape` + +`public static function escape(string $value): string` + +Escapes C0, DEL, and C1 controls as uppercase `\xNN` sequences. + +ASCII bytes from 0x20 through 0x7E and valid UTF-8 characters outside those +control ranges remain unchanged. If the input is malformed UTF-8 or PCRE cannot +process it, every byte outside printable ASCII is escaped. Valid C1 characters +are rendered as `\xNN` using their Unicode code points. During bytewise +fallback, each original byte outside printable ASCII is rendered in the same +form. The result is diagnostic text, not a reversible encoding. + +This does not encode values for HTML, JSON, shells, terminals, URLs, or protocol +fields. + +Use this helper when including an untrusted value in diagnostic text: + +```php +use GuzzleHttp\Psr7\DiagnosticValue; + +$value = "bad\nvalue"; +$message = sprintf('Invalid value: %s', DiagnosticValue::escape($value)); +// Invalid value: bad\x0Avalue +``` + +Applications must still encode the completed diagnostic for its final output +context, such as HTML or JSON. diff --git a/libs/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md b/libs/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md new file mode 100644 index 00000000..f1437121 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md @@ -0,0 +1,107 @@ +# Header and Query Helpers + +This page covers helper methods for parsing structured header values, splitting +list headers, and parsing or building query strings. For basic message header +behavior, see [PSR-7 Messages](psr-7-messages.md). + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parses semicolon-separated header parameters into associative arrays, one per +comma-separated header value. Parameters without a value are appended as values +under integer keys. + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits an HTTP header defined to contain a comma-separated list into each +individual value. Empty values are removed: + +```php +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control`, and `if-none-match`. + +This method must not be used to parse headers that are not defined as a list, +such as `user-agent` or `set-cookie`. + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key-value pair +becomes an array. This function does not parse nested PHP style arrays into an +associative array. For example, `foo[a]=1&foo[b]=2` will be parsed into +`['foo[a]' => '1', 'foo[b]' => '2']`. + +## `GuzzleHttp\Psr7\Query::build` + +`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. + +This function can use the return value of `parse()` to build a query string. +This function does not modify the provided keys when an array is encountered, +unlike `http_build_query()`. + +## `GuzzleHttp\Psr7\Utils::asciiToLower` + +`public static function asciiToLower(string $string): string` + +Converts ASCII uppercase letters in a string to lowercase. + +Unlike `strtolower()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::asciiToUpper` + +`public static function asciiToUpper(string $string): string` + +Converts ASCII lowercase letters in a string to uppercase. + +Unlike `strtoupper()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::asciiUcFirst` + +`public static function asciiUcFirst(string $string): string` + +Converts the first character of a string to uppercase when it is an ASCII +lowercase letter. + +Unlike `ucfirst()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::caselessContains` + +`public static function caselessContains(string $haystack, string $needle): bool` + +Checks whether the haystack contains the needle, comparing ASCII letters +case-insensitively and without locale sensitivity. + +## `GuzzleHttp\Psr7\Utils::caselessEquals` + +`public static function caselessEquals(string $left, string $right): bool` + +Checks whether two strings are equal, comparing ASCII letters case-insensitively +and without locale sensitivity. + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(array $keys, array $data): array` + +Remove the items given by the keys from the data, case-insensitively. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Message Helpers](message-helpers.md) +- [URI Helpers](uri-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/message-helpers.md b/libs/vendor/guzzlehttp/psr7/docs/message-helpers.md new file mode 100644 index 00000000..b560ce02 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/message-helpers.md @@ -0,0 +1,112 @@ +# Message Helpers + +This page covers static helper methods for converting, parsing, summarizing, +rewinding, and cloning PSR-7 messages. For conceptual request and response +behavior, start with [PSR-7 Messages](psr-7-messages.md). + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, ?int $truncateAt = null): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + +Reads seekable bodies from the beginning and restores the original cursor +position before returning. Pass `null` for `$truncateAt` to use the default +summary length. + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` returns a +value other than `0`. + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the `start-line` key containing the start line of the +message, `headers` key containing an associative array of header array values, +and a `body` key containing the body of the message. + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + +The URI is composed from the start-line path and the `Host` header, using +`https` when the host's port is `443` and `http` otherwise. Without a `Host` +header, only the path is returned, with extra leading slashes collapsed so an +origin-form target cannot be parsed as a network-path reference with its own +authority. An `InvalidArgumentException` is thrown when the `Host` header is +invalid. + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): RequestInterface` + +Parses a request message string into a request object. + +The request-target must be in origin form, absolute form (without a userinfo +component), authority form (`CONNECT`), or asterisk form (`OPTIONS`), and any +`Host` header must be a single valid value; otherwise an +`InvalidArgumentException` is thrown. Non-origin-form targets are preserved on +the returned request via `withRequestTarget()`. + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): ResponseInterface` + +Parses a response message string into a response object. + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate a +message. + +The changes can be one of: + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. Values must be strings or + non-empty arrays of strings. +- remove_headers: (array) Remove the given headers. Values may be strings or + integers. +- body: (mixed) Sets the given body. Present non-null values are converted with + `GuzzleHttp\Psr7\Utils::streamFor()`, including resources, streams, iterators, + callable arrays, closures, invokable objects, and stringable objects. String + inputs remain literal bodies. +- uri: (UriInterface) Set the URI. When the URI contains a host, the Host header + is updated from it, and combining this with an explicit Host entry in + set_headers throws an InvalidArgumentException. Apply an intentional Host + override separately with withHeader() afterwards. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [Stream Helpers](stream-helpers.md) +- [URI and MIME Helpers](uri-and-mime-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/psr-17-factories.md b/libs/vendor/guzzlehttp/psr7/docs/psr-17-factories.md new file mode 100644 index 00000000..463e7761 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/psr-17-factories.md @@ -0,0 +1,33 @@ +# PSR-17 Factories + +This page explains `GuzzleHttp\Psr7\HttpFactory`, the PSR-17 factory implementation provided by this package. Use it when code expects PSR-17 factory interfaces and you want factories that create Guzzle PSR-7 messages, streams, uploaded files, and URIs. + +## `GuzzleHttp\Psr7\HttpFactory` + +`GuzzleHttp\Psr7\HttpFactory` implements all PSR-17 factory interfaces: `RequestFactoryInterface`, `ResponseFactoryInterface`, `ServerRequestFactoryInterface`, `StreamFactoryInterface`, `UploadedFileFactoryInterface`, and `UriFactoryInterface`. + +```php +use GuzzleHttp\Psr7\HttpFactory; + +$factory = new HttpFactory(); + +$request = $factory->createRequest('GET', 'https://example.com'); +$response = $factory->createResponse(200); +$serverRequest = $factory->createServerRequest('POST', '/submit', ['REMOTE_ADDR' => '192.0.2.1']); +$stream = $factory->createStream('body'); +$uri = $factory->createUri('https://example.com/path'); +``` + +It also creates streams from files and resources, and uploaded files from streams. + +```php +$stream = $factory->createStreamFromFile('/path/to/file.txt', 'r'); +$upload = $factory->createUploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'file.txt', 'text/plain'); +``` + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Streams and Decorators](streams-and-decorators.md) +- [URI Helpers](uri-helpers.md) +- [Message Helpers](message-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/psr-7-messages.md b/libs/vendor/guzzlehttp/psr7/docs/psr-7-messages.md new file mode 100644 index 00000000..1dd4cc27 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/psr-7-messages.md @@ -0,0 +1,402 @@ +# PSR-7 Messages + +This page covers the PSR-7 message objects provided by this package: requests, responses, server requests, uploaded files, and the message-specific header, URI, and body APIs. Use these objects when you need HTTP messages that can move between Guzzle, PSR-18 clients, PSR-15 middleware, and other PSR-7 compatible libraries. + +HTTP requests and responses are both messages. A message has a start line, headers, and an optional body stream. Message and URI objects are immutable; methods named `with*()` return changed copies. Body streams are mutable handles, so reads and writes can change their cursor or contents. For body details, see [Streams and Decorators](streams-and-decorators.md). For URI helpers, see [URI Helpers](uri-helpers.md). + +## Creating Requests + +You can create a request with `GuzzleHttp\Psr7\Request`. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', 'https://example.com/users/123'); + +// You can provide optional headers and a body. +$headers = ['Accept' => 'application/json']; +$body = 'request body'; +$request = new Request('PUT', 'https://example.com/users/123', $headers, $body); +``` + +## Creating Responses + +You can create a response with `GuzzleHttp\Psr7\Response`. + +```php +use GuzzleHttp\Psr7\Response; + +// The constructor requires no arguments. +$response = new Response(); +echo $response->getStatusCode(); +// 200 +echo $response->getProtocolVersion(); +// 1.1 + +// You can provide a status, headers, body, and protocol version. +$response = new Response(200, ['Content-Type' => 'application/json'], '{"ok":true}', '1.1'); +``` + +## Creating Server Requests + +Server requests represent incoming HTTP requests on the server side. They include the normal request method, URI, headers, and body, plus server parameters, cookies, query parameters, parsed body data, attributes, and uploaded files. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$request = new ServerRequest('POST', 'https://example.com/form', [], 'name=Guzzle', '1.1', [ + 'REMOTE_ADDR' => '192.0.2.1', +]); + +$request = $request + ->withCookieParams(['session' => 'abc']) + ->withQueryParams(['page' => '1']) + ->withParsedBody(['name' => 'Guzzle']) + ->withAttribute('route', 'profile'); + +echo $request->getServerParams()['REMOTE_ADDR']; +echo $request->getCookieParams()['session']; +echo $request->getQueryParams()['page']; +echo $request->getParsedBody()['name']; +echo $request->getAttribute('route'); +``` + +Use `ServerRequest::fromGlobals()` to create a server request from PHP superglobals. It reads `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, and `$_FILES`, and attempts to include request headers when available. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$request = ServerRequest::fromGlobals(); +``` + +Use `ServerRequest::getUriFromGlobals()` when you only need the URI derived from `$_SERVER`. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$uri = ServerRequest::getUriFromGlobals(); +``` + +For URI construction and normalization helpers, see [URI Helpers](uri-helpers.md). + +## Requests + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', 'https://example.com/users/123', [ + 'Accept' => 'application/json', +]); + +echo $request->getMethod(); +echo $request->getUri(); +``` + +PSR-7 messages are immutable. Methods such as `withHeader()` and `withUri()` return a modified copy. + +```php +$jsonRequest = $request->withHeader('Accept', 'application/json'); +``` + +## Responses + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, ['Content-Type' => 'application/json'], '{"ok":true}'); + +echo $response->getStatusCode(); +echo $response->getHeaderLine('Content-Type'); +echo $response->getBody(); +``` + +## URIs + +```php +use GuzzleHttp\Psr7\Uri; + +$uri = new Uri('https://example.com/users?active=1'); + +echo $uri->getHost(); +echo $uri->getQuery(); +``` + +For URI-specific helper methods, see [URI Helpers](uri-helpers.md). + +## Headers + +Both request and response messages contain HTTP headers. + +### Accessing Headers + +You can check if a request or response has a specific header using `hasHeader()`. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', '/', ['X-Foo' => 'bar']); + +if ($request->hasHeader('X-Foo')) { + echo 'It is there'; +} +``` + +Retrieve all header values as an array of strings with `getHeader()`. + +```php +$request->getHeader('X-Foo'); +// ['bar'] + +// Missing headers return an empty array. +$request->getHeader('X-Bar'); +// [] +``` + +Iterate over the headers of a message with `getHeaders()`. + +```php +foreach ($request->getHeaders() as $name => $values) { + echo $name . ': ' . implode(', ', $values) . "\r\n"; +} +``` + +### Complex Headers + +Some headers contain additional key-value pair information. For example, `Link` headers contain a link and additional parameters: + +```http +; rel="front"; type="image/jpeg" +``` + +Use `GuzzleHttp\Psr7\Header::parse()` to parse these headers. + +```php +use GuzzleHttp\Psr7\Header; +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', '/', [ + 'Link' => '; rel="front"; type="image/jpeg"', +]); + +$parsed = Header::parse($request->getHeader('Link')); +var_export($parsed); +``` + +This outputs: + +```php +array ( + 0 => + array ( + 0 => '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), +) +``` + +The result contains key-value pairs. Header values that have no key are indexed numerically, while header parts that form a key-value pair are added with their parameter name. + +## Body + +Request and response bodies are `Psr\Http\Message\StreamInterface` instances. Streams are used for both uploading data and downloading data. + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, [], 'response body'); + +echo $response->getBody(); +// response body +``` + +The body can be cast to a string, or you can read bytes from the stream as needed. + +```php +$body = $response->getBody(); + +echo $body->read(4); +$body->seek(0); +echo $body->getContents(); +``` + +For more stream creation and decorator examples, see [Streams and Decorators](streams-and-decorators.md). + +## Uploaded Files + +Uploaded files are represented by `Psr\Http\Message\UploadedFileInterface` instances. This package provides `GuzzleHttp\Psr7\UploadedFile`, which can wrap a local file path, PHP stream resource, or PSR-7 stream. + +```php +use GuzzleHttp\Psr7\UploadedFile; +use GuzzleHttp\Psr7\Utils; + +$stream = Utils::streamFor('file contents'); +$upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'example.txt', 'text/plain'); + +echo $upload->getClientFilename(); +echo $upload->getClientMediaType(); +echo $upload->getSize(); +``` + +Call `getStream()` to read the uploaded content, or `moveTo()` to move or copy it to a target path. After `moveTo()` succeeds, `isMoved()` returns `true`, and calls that need the active upload stream will throw. + +```php +$body = $upload->getStream(); +echo $body->getContents(); + +$upload->moveTo('/path/to/target.txt'); +var_export($upload->isMoved()); +// true +``` + +If the upload error code is not `UPLOAD_ERR_OK`, the object still exposes `getError()`, `getSize()`, `getClientFilename()`, and `getClientMediaType()`, but `getStream()` and `moveTo()` throw because no successful upload content is available. + +`ServerRequest::normalizeFiles()` converts a `$_FILES`-style array into a tree of uploaded file instances. It accepts simple file specs, nested PHP `$_FILES` shapes, existing `UploadedFileInterface` instances, and nested arrays of uploaded files. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$files = ServerRequest::normalizeFiles([ + 'avatar' => [ + 'tmp_name' => '/tmp/php123', + 'size' => 1024, + 'error' => UPLOAD_ERR_OK, + 'name' => 'avatar.png', + 'type' => 'image/png', + ], + 'photos' => [ + 'tmp_name' => [ + 'first' => '/tmp/php456', + ], + 'size' => [ + 'first' => 2048, + ], + 'error' => [ + 'first' => UPLOAD_ERR_OK, + ], + 'name' => [ + 'first' => 'photo.jpg', + ], + 'type' => [ + 'first' => 'image/jpeg', + ], + ], +]); + +$request = (new ServerRequest('POST', '/upload'))->withUploadedFiles($files); +``` + +## HTTP Method Casing + +HTTP method names are case-sensitive in PSR-7. Requests created explicitly with +`Request`, `ServerRequest`, `withMethod()`, `Message::parseRequest()`, or the +PSR-17 factories preserve the method string as provided. `ServerRequest::fromGlobals()` +normalizes `$_SERVER['REQUEST_METHOD']` to uppercase for compatibility when +hydrating requests from PHP server globals. + +## Request Methods + +When creating a request, provide the HTTP method you want to perform. You can specify any method, including custom methods that are not part of RFC 9110. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('MOVE', 'https://example.com/resource'); + +echo $request->getMethod(); +// MOVE +``` + +## Request URI + +The request URI is represented by a `Psr\Http\Message\UriInterface` object. This package provides an implementation through `GuzzleHttp\Psr7\Uri`. + +When creating a request, you can provide the URI as a string or as a `UriInterface` instance. + +```php +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Uri; + +$request = new Request('GET', new Uri('https://example.com/users?id=123')); +``` + +### Scheme + +The [scheme](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1) specifies the protocol. For HTTP requests, this is usually `http` or `https`. + +```php +$request = new Request('GET', 'https://example.com'); +echo $request->getUri()->getScheme(); +// https +``` + +### Host + +The host is accessible from the URI and is also represented by the `Host` header. + +```php +$request = new Request('GET', 'https://example.com'); +echo $request->getUri()->getHost(); +// example.com +echo $request->getHeaderLine('Host'); +// example.com +``` + +### Port + +No port is necessary for the default `http` and `https` ports. + +```php +$request = new Request('GET', 'https://example.com:8443'); +echo $request->getUri()->getPort(); +// 8443 +``` + +### Path + +The request path is accessible through the URI object. + +```php +$request = new Request('GET', 'https://example.com/users/123'); +echo $request->getUri()->getPath(); +// /users/123 +``` + +Characters that are not allowed in a URI path are percent-encoded according to [RFC 3986 section 3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3). + +### Query String + +The query string is accessible through the URI object. + +```php +$request = new Request('GET', 'https://example.com/?foo=bar'); +echo $request->getUri()->getQuery(); +// foo=bar +``` + +Characters that are not allowed in a URI query are percent-encoded according to [RFC 3986 section 3.4](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4). + +## Response Status + +Responses expose the status code, reason phrase, and protocol version. + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, [], 'OK'); + +echo $response->getStatusCode(); +// 200 +echo $response->getReasonPhrase(); +// OK +echo $response->getProtocolVersion(); +// 1.1 +``` + +## Related + +- [Streams and Decorators](streams-and-decorators.md) +- [URI Helpers](uri-helpers.md) +- [Message Helpers](message-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [PSR-17 Factories](psr-17-factories.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/stream-helpers.md b/libs/vendor/guzzlehttp/psr7/docs/stream-helpers.md new file mode 100644 index 00000000..7a2c43b0 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/stream-helpers.md @@ -0,0 +1,134 @@ +# Stream Helpers + +This page covers `GuzzleHttp\Psr7\Utils` helper methods for creating, copying, +hashing, reading, and safely opening PSR-7 streams. For stream implementations +and decorators, see [Streams and Decorators](streams-and-decorators.md). + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): int` + +Copy the contents of a stream into another stream until the given number of +bytes have been read, returning the number of bytes copied as an `int`. On +32-bit PHP, an unbounded copy larger than `PHP_INT_MAX` bytes cannot be +represented by that return type. 64-bit PHP is not affected. + +The destination must accept writes that make positive progress. Streams that +return 0 as a backpressure or drop signal (a `BufferStream` at its high water +mark, or a full `DroppingStream`) will cause this method to throw. For full +copies, use a normal writable stream such as a file or `php://temp` stream. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a source read or destination write cannot make +progress. + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of bytes have +been read. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on PHP's +`hash_init` functions. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|StreamInterface|callable|\Iterator|\Stringable $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options are provided as an associative array that can contain the following +keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Yielded strings, integers, finite floats, booleans, `null`, and stringable + objects are converted to string chunks; non-finite floats and other values + throw `UnexpectedValueException` when the stream is read. Values that + stringify to an empty string are skipped while the iterator advances. + Subsequent read calls will first read from the buffer and then call `next` on + the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, the + object will be cast to a string and then a stream will be returned that uses + the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable`: When a callable array, closure, or invokable object is passed and + no earlier resource or object rule applies, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + suggested number of bytes to read. The callable can return fewer or more bytes + than requested, but MUST return a non-empty string to provide data and MUST + return `false` or `null` when there is no more data to return. Any additional + bytes will be buffered and used in subsequent reads. String inputs are always + treated as string bodies, even when they name callable functions. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +}; + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When `fopen()` fails, PHP normally raises a warning. This function adds an error +handler that checks for errors and throws an exception instead. + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When `stream_get_contents()` fails, PHP normally raises a warning. This function +adds an error handler that checks for errors and throws an exception instead. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after the stream read cannot make progress. + +## Related + +- [Streams and Decorators](streams-and-decorators.md) +- [PSR-17 Factories](psr-17-factories.md) +- [Message Helpers](message-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md b/libs/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md new file mode 100644 index 00000000..6af5cb5c --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md @@ -0,0 +1,387 @@ +# Streams and Decorators + +PSR-7 request and response bodies are streams. This page covers stream creation, cursor and I/O behavior, built-in stream decorators, and wrapping PSR-7 streams as PHP resources. + +Streams allow HTTP messages to represent small strings, large files, generated data, remote resources, and other body sources through a common interface. + +The PSR-7 `Psr\Http\Message\StreamInterface` exposes methods that let consumers read, write, seek, and inspect body data without requiring the entire body to be loaded into memory. + +Streams expose their capabilities using `isReadable()`, `isWritable()`, and `isSeekable()`. These methods help collaborators determine whether a stream supports the operations they need. + +## Creating Streams + +Use `GuzzleHttp\Psr7\Utils::streamFor()` to create streams from common PHP values. It accepts strings, resources returned from `fopen()`, objects that implement `__toString()`, iterators, callable arrays, closures, invokable objects, and existing `Psr\Http\Message\StreamInterface` instances. + +Strings and `null` are stored in `php://temp` streams. PHP keeps `php://temp` data in memory until the stream exceeds 2 MB, then spills to a temporary file on disk. Non-string scalars such as integers, floats, and booleans are rejected; cast them to strings first. + +Callable sources receive a suggested read length, may return fewer or more bytes, and end the stream by returning `false` or `null`. Strings remain literal body contents, even when they name a callable. + +```php +use GuzzleHttp\Psr7\Utils; + +$stream = Utils::streamFor('string data'); +echo $stream; +// string data +echo $stream->read(3); +// str +echo $stream->getContents(); +// ing data +var_export($stream->eof()); +// true +var_export($stream->tell()); +// 11 +``` + +You can create streams from iterators. The iterator can yield any number of bytes per iteration. Any excess bytes returned by the iterator that were not requested by a stream consumer will be buffered until a subsequent read. + +```php +use GuzzleHttp\Psr7\Utils; + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield '.'; + } +}; + +$stream = Utils::streamFor($generator(1024)); +echo $stream->read(3); +// ... +``` + +## Metadata + +Streams expose stream metadata through `getMetadata()`. This method provides the data returned by PHP's [stream_get_meta_data()](https://www.php.net/manual/en/function.stream-get-meta-data.php), and can optionally expose custom metadata. + +```php +use GuzzleHttp\Psr7\Utils; + +$resource = Utils::tryFopen('/path/to/file', 'r'); +$stream = Utils::streamFor($resource); + +echo $stream->getMetadata('uri'); +// /path/to/file +var_export($stream->isReadable()); +// true +var_export($stream->isWritable()); +// false +var_export($stream->isSeekable()); +// true +``` + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, then read +from it to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When the buffer reaches or exceeds 1024 bytes, it will begin returning 0 to +// writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + +By default the bytes are cached in a `php://temp` stream. You can supply your own +cache target as the second constructor argument, but it is used as a random-access +byte buffer to replay the remote stream, so it must be readable, writable, and +seekable, report an accurate position and size, and store writes losslessly. Lossy +or non-seekable streams such as `BufferStream` and `DroppingStream` are not valid +targets. + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of callables. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +appends the zlib.inflate filter, and wraps the filtered resource as a stream. + +Closing an `InflateStream` also closes the compressed source stream it decorates; `detach()` leaves the source stream open. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads from or writes to a file that is opened only after an I/O operation +takes place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +A stream that returns bytes for a streaming multipart or multipart/form-data +body when read. + +Each multipart element must contain a `name` and `contents` key. `contents` may +be any non-array value accepted by `GuzzleHttp\Psr7\Utils::streamFor()`, +including closures and invokable objects. Array contents are recursively +expanded into nested form fields. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false + +try { + $noSeek->seek(0); +} catch (\RuntimeException $e) { + echo $e->getMessage(); + // Cannot seek a NoSeekStream +} +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read-only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the suggested +number of bytes to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the `read()` method of the PumpStream. The provided callable MUST return a +non-empty string to provide data, and MUST return false or null when there is +no more data to read. Returning an empty string causes a RuntimeException +because it cannot satisfy a positive-length read. + +Userland callables that declare no parameters are tolerated by PHP, but +length-aware callables remain the recommended formal shape. + + +## Implementing Stream Decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + private $stream; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read(int $length): string + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + ($this->callback)(); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Stream Helpers](stream-helpers.md) +- [PSR-17 Factories](psr-17-factories.md) +- [URI Helpers](uri-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md b/libs/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md new file mode 100644 index 00000000..01046350 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md @@ -0,0 +1,61 @@ +# URI and MIME Helpers + +This page covers small helper methods for redacting URI user info, converting +values into URI objects, and resolving MIME types from filenames or extensions. +For URI resolution, normalization, and comparison helpers, see +[URI Helpers](uri-helpers.md). + +## `GuzzleHttp\Psr7\Utils::redactUserInfo` + +`public static function redactUserInfo(UriInterface $uri): UriInterface` + +Redact the user info part of a URI. + +Returns the URI with the whole userinfo component replaced by `***` when one +is present, so neither the username nor the password survives into logs and +diagnostics. A URI without userinfo is returned unchanged. + +## `GuzzleHttp\Psr7\Utils::redactUserInfoInString` + +`public static function redactUserInfoInString(string $subject, string $uri): string` + +Redacts the userinfo of a raw URI string wherever it appears in a subject +string. + +The needle is taken verbatim from the raw URI rather than from parsed +components, so credentials that URI normalization would rewrite, such as raw +control bytes or unencoded reserved characters, are still found in text that +embeds the URI exactly as given, for example transport error messages. A URI +without `://` is treated as authority-form: a host and port with optional +userinfo. + +A URI that does not parse has no trustworthy authority boundary, so everything +between any scheme and its last `@` is redacted as a safe-side fallback. + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a `UriInterface` for the given value. + +This function accepts a string or `UriInterface` and returns a `UriInterface` +for the given value. If the value is already a `UriInterface`, it is returned +as-is. + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the MIME type of a file by looking at its extension. + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extension to a MIME type. + +## Related + +- [URI Helpers](uri-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [Stream Helpers](stream-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/docs/uri-helpers.md b/libs/vendor/guzzlehttp/psr7/docs/uri-helpers.md new file mode 100644 index 00000000..754aec14 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/docs/uri-helpers.md @@ -0,0 +1,436 @@ +# URI Helpers + +This page covers this package's `Psr\Http\Message\UriInterface` implementation +and URI helper classes for classifying, composing, resolving, normalizing, +comparing, and safely modifying URIs. + +Aside from the standard `Psr\Http\Message\UriInterface` implementation provided +by the `GuzzleHttp\Psr7\Uri` class, this library also provides additional static +methods for working with URIs. + +## URI Types + +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, the base URI. Relative references can +be divided into several forms according to +[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins +with two slash characters is termed a network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is an absolute-path reference. A relative reference that begins +with a single slash character is termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not +begin with a slash character is termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`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 fragment component, identical to the base URI. +When no base URI is given, only an empty URI reference (apart from its fragment) +is considered a same-document reference. + +## URI Syntax Validation + +`GuzzleHttp\Psr7\Rfc3986` provides static methods for validating and +canonicalizing individual URI components against the grammar defined by +[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). They operate on raw +component strings rather than on `Psr\Http\Message\UriInterface` instances. + +### `GuzzleHttp\Psr7\Rfc3986::isValidScheme` + +`public static function isValidScheme(string $scheme): bool` + +Whether the string is a valid URI scheme. Per +[RFC 3986 Section 3.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1), +a scheme must start with a letter, followed by any number of letters, digits, +`+`, `-`, or `.`. The empty string is also accepted, since a URI reference may +omit the scheme. + +### `GuzzleHttp\Psr7\Rfc3986::isValidHost` + +`public static function isValidHost(string $host): bool` + +Whether the string is a valid URI host. Per +[RFC 3986 Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2), +the host is an IP-literal, IPv4 address, or registered name. An empty host is +accepted, since the authority, and thus the host, may be empty. Bracketed values +are validated as IPv6 or IPvFuture literals; any other value is rejected if it +contains control characters, whitespace, an authority or path delimiter (`/`, +`?`, `#`, `@`, `\`), or an embedded colon denoting a port. Percent-encoding is +validated the same way: malformed sequences (a `%` not followed by two hex +digits) and percent-encoded octets that decode to one of the rejected bytes, to +a bracket (`[`, `]`), or to `%` itself are invalid, while all other +percent-encoded octets are accepted. Rejecting these percent-encoded octets is a +deliberate guzzle host policy, stricter than the RFC 3986 `reg-name` grammar, +which permits any well-formed `pct-encoded` octet; it matches the stricter +policy used throughout the library. RFC 6874 IPv6 zone identifiers (for example +`[fe80::1%25eth0]`) are not supported. + +Registered names are otherwise intentionally permissive: single-label hosts such +as `localhost`, underscores, sub-delims, and raw or percent-encoded non-ASCII +(IDN) data are accepted and preserved as given, with no punycode conversion. +IDNA is treated as a client concern. Consumers that need DNS IDNs must perform +the conversion themselves, for example via Guzzle's `idn_conversion` request +option. + +### `GuzzleHttp\Psr7\Rfc3986::isValidPort` + +`public static function isValidPort(string $port): bool` + +Whether the string is a valid port number. RFC 3986 defines the port as +`*DIGIT`, which also permits an empty port and has no upper bound; this applies +the stricter policy used throughout the library instead, accepting a non-empty +run of digits (leading zeros are accepted and normalized) that resolves to a +value in the range 0-65535. + +### `GuzzleHttp\Psr7\Rfc3986::canonicalizeIpv6` + +`public static function canonicalizeIpv6(string $address): string` + +Returns the [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952#section-4) +canonical form of a valid IPv6 address. The address must be a valid textual +IPv6 address without brackets and without a zone identifier, such as the +inside of an IP-literal accepted by `isValidHost()`. Canonicalization +lowercases the hexadecimal fields, suppresses leading zeros, and collapses the +longest run of two or more zero fields (the leftmost on a tie) with `::`. +Embedded dotted-decimal notation follows the rendering policy of BIND-derived +`inet_ntop()` implementations and curl: exactly the IPv4-mapped +(`::ffff:0:0/96`) and deprecated IPv4-compatible (`::/96`) layouts use it, +while other embedded-IPv4 forms, including translated (NAT64) well-known +prefixes such as `64:ff9b::/96` (RFC 6052), serialize in pure hexadecimal +fields. An `\InvalidArgumentException` is thrown if the address cannot be +parsed. + +Validation is strict and platform-independent: the address is checked against +the RFC 3986 `IPv6address` grammar with PHP's `FILTER_VALIDATE_IP` filter and +parsed in pure PHP, so spellings that only some platform parsers accept, such as +the zero-padded dotted octets in `::ffff:192.168.001.001`, are rejected +everywhere. + +Emitting dotted-decimal notation for these two selected layouts is the +BIND-derived `inet_ntop()` and curl compatibility policy used here. +[RFC 5952 Section 5](https://datatracker.ietf.org/doc/html/rfc5952#section-5) +permits mixed notation for recognizable embedded-IPv4 prefixes but does not +limit that category to these layouts; RFC 6052, for example, defines +`64:ff9b::/96` as a Well-Known Prefix, which this implementation renders in +pure hexadecimal. The WHATWG URL Standard always emits pure hexadecimal +fields. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. +`Psr\Http\Message\UriInterface::getPort` may return null or the standard port. +This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). +Usually this method does not need to be called manually but instead is used +indirectly via `Psr\Http\Message\UriInterface::__toString`. + +PSR-7 UriInterface treats an empty component the same as a missing component as +`getQuery()`, `getFragment()` etc. always return a string. This explains the +slight difference to RFC 3986 Section 5.3. + +Another adjustment is that the authority separator is added even when the +authority is missing/empty for the "file" scheme. This is because PHP stream +functions like `file_get_contents` only work with `file:///myfile` but not with +`file:/myfile` although they are equivalent according to RFC 3986. But +`file:///` is the more common syntax for the file scheme anyway (Chrome for +example redirects to that format). The separator is omitted when such a URI has +a rootless or empty path: adding it would turn the first path segment into the +authority of the composed URI, or compose the string `file://`, which cannot be +parsed back into a URI. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of +[`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string +values that exactly match the provided key are removed and replaced with the +given key value pair. A value of null will set the query string key without a +value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as +`withQueryValue()` but for an associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query +string values that exactly match the provided key are removed. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URI +should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URI should be considered cross-origin with respect to +an original URI. + +Two URIs are cross-origin when their scheme, host, or effective port differ. +Host comparison is case-insensitive, and bracketed IPv6 literals are +canonicalized to their RFC 5952 form from any PSR-7 implementation before +comparison, so equivalent spellings of the same address are same-origin. +IPvFuture literals and bracketed values that cannot be parsed as an IPv6 +address, such as those carrying zone identifiers, still compare as +case-insensitive text. Missing ports use the default port for `http`, `https`, +`ws`, or `wss`. Other schemes do not receive implicit default ports. + +This helper only compares URI origins. It does not implement redirect handling +or credential policy. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the +context of a base URI according to +[RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). +This is also what web browsers do when resolving a link in a document based on +the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4). + +Excess `..` segments above the root of an absolute path are dropped without +consuming the root, so the result can begin with `//` (e.g. `/..//a` becomes +`//a`). Such a path is not valid for a URI without an authority (RFC 3986 +Section 3.3); `resolve()` and `UriNormalizer::normalize()` serialize it with a +`/.` prefix in that case, like the WHATWG URL Standard. + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is +the counterpart to `resolve()`: + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use case is to use the current request URI as the base URI and then generate +relative links in your documents to reduce the document size or offer +self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'. +``` + +This method also accepts a target that is already relative and will try to +relativize it further. Only a relative-path reference will be returned as-is. + +```php +echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs +according to +[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized +to lowercase per PSR-7 UriInterface. This method adds additional normalizations +that can be configured with the `$flags` parameter, which is a bitmask of +normalizations to apply. + +PSR-7 UriInterface cannot distinguish between an empty component and a missing +component as `getQuery()`, `getFragment()` etc. always return a string. This +means the URIs `/?#` and `/` are treated equivalent which is not necessarily +true according to RFC 3986. But that difference is highly uncommon in reality. +So this potential normalization is implied in PSR-7 as well. + +The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are + case-insensitive, and should be capitalized. This applies to the userinfo, + host, path, query, and fragment components. Bracketed IP-literal hosts are + skipped as a legacy tolerance for nonstandard values other implementations + may carry; zone-identifier text was briefly valid URI syntax under RFC 6874, + which RFC 9844 obsoleted and reverted. The userinfo and host are only + rewritten when the value returned by the implementation matches the + normalized form, and a userinfo with an empty user segment is never + rewritten. No percent-encoding normalization is applied to a component that + contains malformed percent syntax, such as a `%` not followed by two + hexadecimal digits. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, + percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT + (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) + should not be created by URI producers and, when found in a URI, should be + decoded to their corresponding unreserved characters by URI normalizers. + This applies to the userinfo, host, path, query, and fragment components. + Since the host is case-insensitive and PSR-7 requires it to be lowercase, + octets decoded in the host are lowercased (e.g., "%41" becomes "a"). + Bracketed IP-literal hosts are skipped as a legacy tolerance for nonstandard + values other implementations may carry; zone-identifier text was briefly + valid URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + userinfo and host are only rewritten when the value returned by the + implementation matches the normalized form, and a userinfo with an empty + user segment is never rewritten. No percent-encoding normalization is + applied to a component that contains malformed percent syntax, such as a `%` + not followed by two hexadecimal digits. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the + "file" scheme defines the default host "localhost". All of `file:/myfile`, + `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references + are not removed as it would change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. + Webservers usually ignore duplicate slashes and treat those URIs equivalent. + But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the + order of parameters in a URI may be significant (this is not defined by the + standard). So this normalization is not safe and may change the semantics of + the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +- `UriNormalizer::CANONICALIZE_IPV6_HOST` + + Canonicalizes IPv6 hosts to their RFC 5952 form. IPv6 addresses allow + leading zeros and multiple placements of the `::` elision, so the same + address has many textual spellings. The canonical form is required for + IPv6 literals in URIs by RFC 5952 Section 6 and never changes what the URI + refers to. Native `Uri` instances already guarantee canonical output; for + other implementations, the canonical host is requested through + `withHost()` and the result is kept only when the returned `getHost()` + exactly matches the requested spelling, otherwise this step leaves the URI + unchanged while other selected normalizations still apply, and setter + exceptions propagate. + + Example: `http://[::0:0a]/` → `http://[::a]/` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized +automatically before comparison with the given `$normalizations` bitmask. The +method also accepts relative URI references and returns true when they are +equivalent. This of course assumes they will be resolved against the same base +URI. If this is not the case, determination of equivalence or difference of +relative references does not mean anything. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Streams and Decorators](streams-and-decorators.md) +- [URI and MIME Helpers](uri-and-mime-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) diff --git a/libs/vendor/guzzlehttp/psr7/src/AppendStream.php b/libs/vendor/guzzlehttp/psr7/src/AppendStream.php index a2d68c2e..2e046cda 100644 --- a/libs/vendor/guzzlehttp/psr7/src/AppendStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -13,17 +13,16 @@ use Psr\Http\Message\StreamInterface; */ final class AppendStream implements StreamInterface { + use NonSerializableStreamTrait; + /** @var StreamInterface[] Streams being decorated */ - private $streams = []; + private array $streams = []; - /** @var bool */ - private $seekable = true; + private bool $seekable = true; - /** @var int */ - private $current = 0; + private int $current = 0; - /** @var int */ - private $pos = 0; + private int $pos = 0; /** * @param StreamInterface[] $streams Streams to decorate. Each stream must @@ -38,18 +37,9 @@ final class AppendStream implements StreamInterface public function __toString(): string { - try { - $this->rewind(); + $this->rewind(); - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - - return ''; - } + return $this->getContents(); } /** @@ -132,7 +122,7 @@ final class AppendStream implements StreamInterface if ($s === null) { return null; } - $size += $s; + $size = Integers::add($size, $s); } return $size; @@ -153,26 +143,8 @@ final class AppendStream implements StreamInterface /** * Attempts to seek to the given position. Only supports SEEK_SET. */ - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - if (!$this->seekable) { throw new \RuntimeException('This AppendStream is not seekable'); } elseif ($whence !== SEEK_SET) { @@ -203,15 +175,10 @@ final class AppendStream implements StreamInterface /** * Reads from all of the appended streams until the length is met or EOF. */ - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } if ($this->streams === []) { @@ -233,7 +200,7 @@ final class AppendStream implements StreamInterface ++$this->current; } - $result = $this->streams[$this->current]->read($remaining); + $result = StreamTimeout::read($this->streams[$this->current], $remaining, 'Unable to read from stream: timed out'); if ($result === '') { $progressToNext = true; @@ -244,7 +211,7 @@ final class AppendStream implements StreamInterface $remaining = $length - strlen($buffer); } - $this->pos += strlen($buffer); + $this->pos = Integers::add($this->pos, strlen($buffer)); return $buffer; } @@ -264,34 +231,13 @@ final class AppendStream implements StreamInterface return $this->seekable; } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - throw new \RuntimeException('Cannot write to an AppendStream'); } - /** - * @return mixed - */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null): ?array { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); - } - - return $key ? null : []; + return $key === null ? [] : null; } } diff --git a/libs/vendor/guzzlehttp/psr7/src/BufferStream.php b/libs/vendor/guzzlehttp/psr7/src/BufferStream.php index 02186112..68ae060b 100644 --- a/libs/vendor/guzzlehttp/psr7/src/BufferStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -16,22 +16,22 @@ use Psr\Http\Message\StreamInterface; */ final class BufferStream implements StreamInterface { - /** @var int */ - private $hwm; + use NonSerializableStreamTrait; - /** @var string */ - private $buffer = ''; + private int $hwm; + + private string $buffer = ''; /** * @param int $hwm High water mark, representing the preferred maximum - * buffer size. If the size of the buffer exceeds the high - * water mark, then calls to write will continue to succeed - * but will return 0 to inform writers to slow down + * buffer size. If the size of the buffer reaches or exceeds + * the high water mark, then calls to write will continue to + * succeed but will return 0 to inform writers to slow down * until the buffer has been drained by reading from it. */ public function __construct(int $hwm = 16384) { - $this->hwm = $hwm; + $this->hwm = Integers::assertNonNegativeInteger($hwm, 'High water mark'); } public function __toString(): string @@ -84,26 +84,8 @@ final class BufferStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - throw new \RuntimeException('Cannot seek a BufferStream'); } @@ -120,15 +102,10 @@ final class BufferStream implements StreamInterface /** * Reads data from the buffer. */ - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } $currentLength = strlen($this->buffer); @@ -149,17 +126,8 @@ final class BufferStream implements StreamInterface /** * Writes data to the buffer. */ - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - $this->buffer .= $string; if (strlen($this->buffer) >= $this->hwm) { @@ -172,21 +140,12 @@ final class BufferStream implements StreamInterface /** * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); - } - if ($key === 'hwm') { return $this->hwm; } - return $key ? null : []; + return $key === null ? [] : null; } } diff --git a/libs/vendor/guzzlehttp/psr7/src/CachingStream.php b/libs/vendor/guzzlehttp/psr7/src/CachingStream.php index 7dd0c837..d7d6f0bc 100644 --- a/libs/vendor/guzzlehttp/psr7/src/CachingStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -13,26 +13,30 @@ use Psr\Http\Message\StreamInterface; final class CachingStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; /** @var StreamInterface Stream being wrapped */ - private $remoteStream; + private StreamInterface $remoteStream; /** @var int Number of bytes to skip reading due to a write on the buffer */ - private $skipReadBytes = 0; + private int $skipReadBytes = 0; - /** - * @var StreamInterface - */ - private $stream; + private StreamInterface $stream; - /** @var bool */ - private $detached = false; + private bool $detached = false; + + private bool $closed = false; /** * We will treat the buffer object as the body of the stream * * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream. - * @param StreamInterface $target Optionally specify where data is cached + * @param StreamInterface $target Optionally specify where data is cached. Defaults to a "php://temp" + * stream. A custom target is used as a random-access byte buffer to + * replay the remote stream, so it must be readable, writable, and + * seekable, report an accurate position and size, and store writes + * losslessly. Lossy or non-seekable streams such as BufferStream and + * DroppingStream are not valid targets. */ public function __construct( StreamInterface $stream, @@ -62,36 +66,31 @@ final class CachingStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - if ($whence === SEEK_SET) { $byte = $offset; } elseif ($whence === SEEK_CUR) { - $byte = $offset + $this->tell(); + $byte = Integers::addSigned($this->tell(), $offset); } elseif ($whence === SEEK_END) { $size = $this->remoteStream->getSize(); if ($size === null) { + // Discovering the size reads the remote stream to EOF and + // moves the cursor, so restore the cursor if the computed + // target is rejected to keep a failed seek side-effect free. + $position = $this->tell(); $size = $this->cacheEntireStream(); + + try { + $byte = Integers::addSigned($size, $offset); + } catch (\Throwable $e) { + $this->stream->seek($position); + + throw $e; + } + } else { + $byte = Integers::addSigned($size, $offset); } - $byte = $size + $offset; } else { throw new \InvalidArgumentException('Invalid whence'); } @@ -119,15 +118,10 @@ final class CachingStream implements StreamInterface } } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } // Perform a regular read on any previously read data from the buffer @@ -140,8 +134,10 @@ final class CachingStream implements StreamInterface // been filled from the remote stream, then we must skip bytes on // the remote stream to emulate overwriting bytes from that // position. This mimics the behavior of other PHP stream wrappers. - $remoteData = $this->remoteStream->read( - $remaining + $this->skipReadBytes + $remoteData = StreamTimeout::read( + $this->remoteStream, + Integers::add($remaining, $this->skipReadBytes), + 'Unable to read from stream: timed out' ); if ($this->skipReadBytes) { @@ -161,24 +157,15 @@ final class CachingStream implements StreamInterface return $data; } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - // When appending to the end of the currently read stream, you'll want // to skip bytes from being read from the remote stream to emulate // other stream wrappers. Basically replacing bytes of data of a fixed // length. - $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + $overflow = Integers::add(strlen($string), $this->tell()) - $this->remoteStream->tell(); if ($overflow > 0) { - $this->skipReadBytes += $overflow; + $this->skipReadBytes = Integers::add($this->skipReadBytes, $overflow); } return $this->stream->write($string); @@ -207,13 +194,39 @@ final class CachingStream implements StreamInterface } /** - * Close both the remote stream and buffer stream + * Close the remote stream and any attached cache stream. */ public function close(): void { - $this->remoteStream->close(); - $this->stream->close(); + if ($this->closed) { + return; + } + + $closeCache = !$this->detached; + $this->closed = true; $this->detached = true; + + $exception = null; + + try { + $this->remoteStream->close(); + } catch (\Throwable $e) { + $exception = $e; + } + + if ($closeCache) { + try { + $this->stream->close(); + } catch (\Throwable $e) { + if ($exception === null) { + $exception = $e; + } + } + } + + if ($exception !== null) { + throw $exception; + } } private function cacheEntireStream(): int diff --git a/libs/vendor/guzzlehttp/psr7/src/DiagnosticValue.php b/libs/vendor/guzzlehttp/psr7/src/DiagnosticValue.php new file mode 100644 index 00000000..151aa9f7 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/DiagnosticValue.php @@ -0,0 +1,63 @@ += 0x20 && $byte <= 0x7E) { + $escaped .= $value[$offset]; + + continue; + } + + $escaped .= \sprintf('\\x%02X', $byte); + } + + return $escaped; + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/DroppingStream.php b/libs/vendor/guzzlehttp/psr7/src/DroppingStream.php index 248eefe2..3a84ef52 100644 --- a/libs/vendor/guzzlehttp/psr7/src/DroppingStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -13,12 +13,11 @@ use Psr\Http\Message\StreamInterface; final class DroppingStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var int */ - private $maxLength; + private int $maxLength; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** * @param StreamInterface $stream Underlying stream to decorate. @@ -27,20 +26,11 @@ final class DroppingStream implements StreamInterface public function __construct(StreamInterface $stream, int $maxLength) { $this->stream = $stream; - $this->maxLength = $maxLength; + $this->maxLength = Integers::assertNonNegativeInteger($maxLength, 'Maximum length'); } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - $diff = $this->maxLength - $this->stream->getSize(); // Begin returning 0 when the underlying stream is too large. diff --git a/libs/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php b/libs/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php new file mode 100644 index 00000000..f8ed545b --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php @@ -0,0 +1,14 @@ + */ - private $methods; + private array $methods; + + private bool $detached = false; /** * @param array $methods Hash of method name to a callable. @@ -44,8 +48,7 @@ final class FnStream implements StreamInterface */ public function __get(string $name): void { - throw new \BadMethodCallException(str_replace('_fn_', '', $name) - .'() is not implemented in the FnStream'); + throw new \BadMethodCallException(\sprintf('%s() is not implemented in the FnStream', DiagnosticValue::escape(str_replace('_fn_', '', $name)))); } /** @@ -53,8 +56,14 @@ final class FnStream implements StreamInterface */ public function __destruct() { - if (isset($this->_fn_close)) { - ($this->_fn_close)(); + if ($this->detached || !isset($this->_fn_close)) { + return; + } + + try { + $this->close(); + } catch (\Throwable $e) { + // Destructors must not surface cleanup failures. } } @@ -65,7 +74,18 @@ final class FnStream implements StreamInterface */ public function __wakeup(): void { - throw new \LogicException('FnStream should never be unserialized'); + $this->methods = []; + $this->detached = true; + + throw new \LogicException(static::class.' should never be unserialized'); + } + + public function __unserialize(array $data): void + { + $this->methods = []; + $this->detached = true; + + throw new \LogicException(static::class.' should never be unserialized'); } /** @@ -74,10 +94,8 @@ final class FnStream implements StreamInterface * * @param StreamInterface $stream Stream to decorate * @param array $methods Hash of method name to a callable - * - * @return FnStream */ - public static function decorate(StreamInterface $stream, array $methods) + public static function decorate(StreamInterface $stream, array $methods): self { // If any of the required methods were not provided, then simply // proxy to the decorated stream. @@ -92,110 +110,113 @@ final class FnStream implements StreamInterface public function __toString(): string { - try { - /** @var string */ - return ($this->_fn___toString)(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + $this->assertAttached(); - return ''; - } + /** @var string */ + return ($this->_fn___toString)(); } public function close(): void { - ($this->_fn_close)(); + if ($this->detached) { + return; + } + + $close = $this->_fn_close; + $this->detached = true; + $close(); } public function detach() { - return ($this->_fn_detach)(); + if ($this->detached) { + return null; + } + + $detach = $this->_fn_detach; + $result = $detach(); + $this->detached = true; + + return $result; } public function getSize(): ?int { + if ($this->detached) { + return null; + } + return ($this->_fn_getSize)(); } public function tell(): int { + $this->assertAttached(); + return ($this->_fn_tell)(); } public function eof(): bool { + $this->assertAttached(); + return ($this->_fn_eof)(); } public function isSeekable(): bool { + if ($this->detached) { + return false; + } + return ($this->_fn_isSeekable)(); } public function rewind(): void { + $this->assertAttached(); + ($this->_fn_rewind)(); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } + $this->assertAttached(); ($this->_fn_seek)($offset, $whence); } public function isWritable(): bool { + if ($this->detached) { + return false; + } + return ($this->_fn_isWritable)(); } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } + $this->assertAttached(); return ($this->_fn_write)($string); } public function isReadable(): bool { + if ($this->detached) { + return false; + } + return ($this->_fn_isReadable)(); } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + $this->assertAttached(); + + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } return ($this->_fn_read)($length); @@ -203,23 +224,27 @@ final class FnStream implements StreamInterface public function getContents(): string { + $this->assertAttached(); + return ($this->_fn_getContents)(); } /** * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); + if ($this->detached) { + return $key === null ? [] : null; } return ($this->_fn_getMetadata)($key); } + + private function assertAttached(): void + { + if ($this->detached) { + throw new \RuntimeException('Stream is detached'); + } + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Header.php b/libs/vendor/guzzlehttp/psr7/src/Header.php index 63fc237a..52583c7b 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Header.php +++ b/libs/vendor/guzzlehttp/psr7/src/Header.php @@ -6,11 +6,14 @@ namespace GuzzleHttp\Psr7; final class Header { + private function __construct() + { + } + /** - * Parse an array of header values containing ";" separated data into an - * array of associative arrays representing the header key value pair data - * of the header. When a parameter does not contain a value, but just - * contains a key, this function will inject a key with a '' string value. + * Parses semicolon-separated header parameters into associative arrays, one + * per comma-separated header value. Parameters without a value are appended + * as values under integer keys. * * @param string|array $header Header to parse into components. */ @@ -23,7 +26,13 @@ final class Header foreach (self::splitList($value) as $val) { $part = []; foreach (self::splitParameters($val) as $kvp) { - if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $count = preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches); + + if ($count === false) { + throw new \RuntimeException('Unable to parse header parameters: '.preg_last_error_msg()); + } + + if ($count !== 0) { $m = $matches[0]; if (isset($m[1])) { $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); @@ -86,37 +95,16 @@ final class Header } /** - * Converts an array of header values that may contain comma separated - * headers into an array of headers with no comma separated values. + * Splits an HTTP header defined to contain a comma-separated list into each + * individual value. Empty values are removed. * - * @param string|array $header Header to normalize. + * Example headers include `accept`, `cache-control`, and `if-none-match`. * - * @deprecated Use self::splitList() instead. - */ - public static function normalize($header): array - { - \trigger_deprecation('guzzlehttp/psr7', '2.3', 'Header::normalize() is deprecated and will be removed in guzzlehttp/psr7 3.0. Use Header::splitList() instead.'); - - $result = []; - foreach ((array) $header as $value) { - foreach (self::splitList($value) as $parsed) { - $result[] = $parsed; - } - } - - return $result; - } - - /** - * Splits a HTTP header defined to contain a comma-separated list into - * each individual value. Empty values will be removed. + * This method must not be used to parse headers that are not defined as a + * list, such as `user-agent` or `set-cookie`. * - * Example headers include 'accept', 'cache-control' and 'if-none-match'. - * - * This method must not be used to parse headers that are not defined as - * a list, such as 'user-agent' or 'set-cookie'. - * - * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * @param string|string[] $values Header value as returned by + * MessageInterface::getHeader() * * @return string[] */ @@ -144,7 +132,7 @@ final class Header } if (!$isQuoted && $value[$i] === ',') { - $v = \trim($v, " \n\r\t\0\x0B"); + $v = \trim($v, " \t\n\r"); if ($v !== '') { $result[] = $v; } @@ -169,7 +157,7 @@ final class Header $v .= $value[$i]; } - $v = \trim($v, " \n\r\t\0\x0B"); + $v = \trim($v, " \t\n\r"); if ($v !== '') { $result[] = $v; } diff --git a/libs/vendor/guzzlehttp/psr7/src/HttpFactory.php b/libs/vendor/guzzlehttp/psr7/src/HttpFactory.php index 3ef15103..a515fc38 100644 --- a/libs/vendor/guzzlehttp/psr7/src/HttpFactory.php +++ b/libs/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -36,7 +36,13 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter $size = $stream->getSize(); } - return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + return new UploadedFile( + $stream, + Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size'), + $error, + $clientFilename, + $clientMediaType + ); } public function createStream(string $content = ''): StreamInterface @@ -50,7 +56,7 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter $resource = Utils::tryFopen($file, $mode); } catch (\RuntimeException $e) { if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { - throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + throw new \InvalidArgumentException(sprintf('Invalid file opening mode: %s', DiagnosticValue::escape($mode)), 0, $e); } throw $e; @@ -64,8 +70,12 @@ final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInter return Utils::streamFor($resource); } - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { + public function createServerRequest( + string $method, + $uri, + #[\SensitiveParameter] + array $serverParams = [] + ): ServerRequestInterface { if (empty($method)) { if (!empty($serverParams['REQUEST_METHOD'])) { $method = $serverParams['REQUEST_METHOD']; diff --git a/libs/vendor/guzzlehttp/psr7/src/InflateStream.php b/libs/vendor/guzzlehttp/psr7/src/InflateStream.php index e674c9ab..77a869e1 100644 --- a/libs/vendor/guzzlehttp/psr7/src/InflateStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\StreamInterface; /** @@ -20,12 +21,15 @@ use Psr\Http\Message\StreamInterface; final class InflateStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; + + private ?StreamInterface $source; public function __construct(StreamInterface $stream) { + $this->source = $stream; $resource = StreamWrapper::getResource($stream); // Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data // See https://www.zlib.net/manual.html#Advanced definition of inflateInit2 @@ -34,4 +38,64 @@ final class InflateStream implements StreamInterface stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]); $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); } + + public function read(int $length): string + { + if ($length <= 0 || $this->source === null) { + return $this->stream->read($length); + } + + try { + $data = $this->stream->read($length); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + if (StreamTimeout::isReadTimedOut($this->source)) { + throw new TimeoutException('Unable to read from stream: timed out', 0, $e); + } + + throw $e; + } + + if ($data === '' && StreamTimeout::isReadTimedOut($this->source)) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + + return $data; + } + + public function close(): void + { + $source = $this->source; + $this->source = null; + + $exception = null; + + try { + $this->stream->close(); + } catch (\Throwable $e) { + $exception = $e; + } + + if ($source !== null) { + try { + $source->close(); + } catch (\Throwable $e) { + if ($exception === null) { + $exception = $e; + } + } + } + + if ($exception !== null) { + throw $exception; + } + } + + public function detach() + { + $this->source = null; + + return $this->stream->detach(); + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Integers.php b/libs/vendor/guzzlehttp/psr7/src/Integers.php new file mode 100644 index 00000000..7eead646 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/Integers.php @@ -0,0 +1,104 @@ + \PHP_INT_MAX - $a) { + throw new \OverflowException('Stream byte count exceeds the maximum integer size supported on this platform'); + } + + return $a + $b; + } + + public static function addSigned(int $base, int $delta): int + { + if ($base < 0) { + throw new \InvalidArgumentException('Stream offset must be non-negative'); + } + + if ($delta > 0 && $delta > \PHP_INT_MAX - $base) { + throw new \OverflowException('Stream offset exceeds the maximum integer size supported on this platform'); + } + + $value = $base + $delta; + if ($value < 0) { + // A negative computed offset is a seek failure at runtime, so throw + // RuntimeException per PSR-7, unlike the precondition check above. + throw new \RuntimeException('Stream offset must be non-negative'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertEngineInteger($value, string $what): ?int + { + if ($value === false || $value === null) { + return null; + } + + if (!\is_int($value) || $value < 0) { + throw new \OverflowException($what.' exceeds the maximum integer size supported on this platform'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertOptionalNonNegativeSize($value, string $name): ?int + { + if ($value === null) { + return null; + } + + if (!\is_int($value) || $value < 0) { + throw new \InvalidArgumentException($name.' must be a non-negative integer or null'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertNonNegativeInteger($value, string $name): int + { + if (!\is_int($value) || $value < 0) { + throw new \InvalidArgumentException($name.' must be a non-negative integer'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertLimitInteger($value, string $name): int + { + if (!\is_int($value) || $value < -1) { + throw new \InvalidArgumentException($name.' must be -1 or a non-negative integer'); + } + + return $value; + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/libs/vendor/guzzlehttp/psr7/src/LazyOpenStream.php index f6c84904..06e0849e 100644 --- a/libs/vendor/guzzlehttp/psr7/src/LazyOpenStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -13,17 +13,13 @@ use Psr\Http\Message\StreamInterface; final class LazyOpenStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var string */ - private $filename; + private string $filename; - /** @var string */ - private $mode; + private string $mode; - /** - * @var StreamInterface - */ - private $stream; + private StreamInterface $stream; /** * @param string $filename File to lazily open @@ -39,6 +35,13 @@ final class LazyOpenStream implements StreamInterface unset($this->stream); } + public function __unserialize(array $data): void + { + $this->stream = new BufferStream(); + + throw new \LogicException(static::class.' should never be unserialized'); + } + /** * Creates the underlying stream lazily when required. */ diff --git a/libs/vendor/guzzlehttp/psr7/src/LimitStream.php b/libs/vendor/guzzlehttp/psr7/src/LimitStream.php index 3c42b3b4..03887b32 100644 --- a/libs/vendor/guzzlehttp/psr7/src/LimitStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -12,15 +12,15 @@ use Psr\Http\Message\StreamInterface; final class LimitStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; /** @var int Offset to start reading from */ - private $offset; + private int $offset; /** @var int Limit the number of bytes that can be read */ - private $limit; + private int $limit; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** * @param StreamInterface $stream Stream to wrap @@ -51,7 +51,7 @@ final class LimitStream implements StreamInterface return false; } - return $this->stream->tell() >= $this->offset + $this->limit; + return $this->stream->tell() >= Integers::add($this->offset, $this->limit); } /** @@ -75,26 +75,8 @@ final class LimitStream implements StreamInterface /** * Allow for a bounded seek on the read limited stream */ - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - if ($whence !== SEEK_SET || $offset < 0) { throw new \RuntimeException(sprintf( 'Cannot seek to offset %s with whence %s', @@ -103,11 +85,12 @@ final class LimitStream implements StreamInterface )); } - $offset += $this->offset; + $offset = Integers::add($this->offset, $offset); if ($this->limit !== -1) { - if ($offset > $this->offset + $this->limit) { - $offset = $this->offset + $this->limit; + $upperBound = Integers::add($this->offset, $this->limit); + if ($offset > $upperBound) { + $offset = $upperBound; } } @@ -131,17 +114,48 @@ final class LimitStream implements StreamInterface */ public function setOffset(int $offset): void { + $offset = Integers::assertNonNegativeInteger($offset, 'Offset'); + $current = $this->stream->tell(); - if ($current !== $offset) { - // If the stream cannot seek to the offset position, then read to it - if ($this->stream->isSeekable()) { - $this->stream->seek($offset); - } elseif ($current > $offset) { - throw new \RuntimeException("Could not seek to stream offset $offset"); - } else { - $this->stream->read($offset - $current); + if ($current === $offset) { + $this->offset = $offset; + + return; + } + + // If the stream cannot seek to the offset position, then read to it. + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + $this->offset = $offset; + + return; + } + + if ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } + + while ($current < $offset) { + if ($this->stream->eof()) { + $this->offset = $current; + + return; } + + $result = $this->stream->read($offset - $current); + + if ($result === '') { + if ($this->stream->eof()) { + $this->offset = $current; + + return; + } + + throw new \RuntimeException("Could not seek to stream offset $offset"); + } + + $current = Integers::add($current, strlen($result)); } $this->offset = $offset; @@ -156,18 +170,13 @@ final class LimitStream implements StreamInterface */ public function setLimit(int $limit): void { - $this->limit = $limit; + $this->limit = Integers::assertLimitInteger($limit, 'Limit'); } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } if ($this->limit === -1) { @@ -176,7 +185,7 @@ final class LimitStream implements StreamInterface // Check if the current position is less than the total allowed // bytes + original offset - $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + $remaining = Integers::add($this->offset, $this->limit) - $this->stream->tell(); if ($remaining > 0) { // Only return the amount of requested data, ensuring that the byte // limit is not exceeded diff --git a/libs/vendor/guzzlehttp/psr7/src/Message.php b/libs/vendor/guzzlehttp/psr7/src/Message.php index da3135d3..5158f692 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Message.php +++ b/libs/vendor/guzzlehttp/psr7/src/Message.php @@ -7,9 +7,16 @@ namespace GuzzleHttp\Psr7; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; final class Message { + private const DEFAULT_BODY_SUMMARY_TRUNCATE_AT = 120; + + private function __construct() + { + } + /** * Returns the string representation of an HTTP message. * @@ -22,7 +29,7 @@ final class Message .$message->getRequestTarget(), " \n\r\t\0\x0B") .' HTTP/'.$message->getProtocolVersion(); if (!$message->hasHeader('host')) { - $msg .= "\r\nHost: ".$message->getUri()->getHost(); + $msg .= "\r\nHost: ".self::hostHeaderFromUri($message->getUri()); } } elseif ($message instanceof ResponseInterface) { $msg = 'HTTP/'.$message->getProtocolVersion().' ' @@ -45,16 +52,39 @@ final class Message return "{$msg}\r\n\r\n".$message->getBody(); } + private static function hostHeaderFromUri(UriInterface $uri): string + { + $host = $uri->getHost(); + + if ($host === '') { + return ''; + } + + Uri::assertValidHost($host); + + if (($port = $uri->getPort()) !== null) { + $host .= ':'.$port; + } + + return $host; + } + /** * Get a short summary of the message body. * * Will return `null` if the response is not printable. * + * Reads seekable bodies from the beginning and restores the original cursor + * position before returning. Pass `null` for `$truncateAt` to use the + * default summary length. + * * @param MessageInterface $message The message to get the body summary - * @param int $truncateAt The maximum allowed size of the summary + * @param int|null $truncateAt Maximum allowed size of the summary */ - public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + public static function bodySummary(MessageInterface $message, ?int $truncateAt = null): ?string { + $truncateAt ??= self::DEFAULT_BODY_SUMMARY_TRUNCATE_AT; + $body = $message->getBody(); if (!$body->isSeekable() || !$body->isReadable()) { @@ -67,19 +97,23 @@ final class Message return null; } - $body->rewind(); - $summary = $body->read($truncateAt); + $position = $body->tell(); - if ($size > $truncateAt) { - if (preg_match('//u', $summary) !== 1) { - $summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3)); + try { + $body->rewind(); + $summary = $body->read($truncateAt); + + if ($size > $truncateAt) { + if (preg_match('//u', $summary) !== 1) { + $summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3)); + } + + $summary .= ' (truncated...)'; } - - $summary .= ' (truncated...)'; + } finally { + $body->seek($position); } - $body->rewind(); - // Matches any printable character, including unicode characters: // letters, marks, numbers, punctuation, spacing, and separators. if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) { @@ -165,184 +199,49 @@ final class Message /** * Parses an HTTP message into an associative array. * - * The array contains the "start-line" key containing the start line of - * the message, "headers" key containing an associative array of header - * array values, and a "body" key containing the body of the message. + * The array contains the `start-line` key containing the start line of the + * message, `headers` key containing an associative array of header array + * values, and a `body` key containing the body of the message. * * @param string $message HTTP request or response to parse. */ public static function parseMessage(string $message): array { - if (!$message) { - throw new \InvalidArgumentException('Invalid message'); - } - - $message = ltrim($message, "\r\n"); - - $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); - - if ($messageParts === false) { - throw new \RuntimeException('Unable to split HTTP message: '.preg_last_error_msg()); - } - - if (count($messageParts) !== 2) { - throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); - } - - [$rawHeaders, $body] = $messageParts; - $rawHeaders .= "\r\n"; // Put back the delimiter we split previously - $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); - - if ($headerParts === false) { - throw new \RuntimeException('Unable to split HTTP message headers: '.preg_last_error_msg()); - } - - if (count($headerParts) !== 2) { - throw new \InvalidArgumentException('Invalid message: Missing status line'); - } - - [$startLine, $rawHeaders] = $headerParts; - - $versionMatch = preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches); - - if ($versionMatch === false) { - throw new \RuntimeException('Unable to parse HTTP start line: '.preg_last_error_msg()); - } - - if ($versionMatch === 1 && $matches[1] === '1.0') { - // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 - $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); - - if ($rawHeaders === null) { - throw new \RuntimeException('Unable to unfold HTTP headers: '.preg_last_error_msg()); - } - } - - /** @var array[] $headerLines */ - $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); - - if ($count === false) { - throw new \RuntimeException('Unable to parse HTTP headers: '.preg_last_error_msg()); - } - - // If these aren't the same, then one line didn't match and there's an invalid header. - if ($count !== substr_count($rawHeaders, "\n")) { - // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 - $hasFoldedHeader = preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders); - - if ($hasFoldedHeader === false) { - throw new \RuntimeException('Unable to inspect HTTP header folding: '.preg_last_error_msg()); - } - - if ($hasFoldedHeader === 1) { - throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); - } - - throw new \InvalidArgumentException('Invalid header syntax'); - } - - $headers = []; - - foreach ($headerLines as $headerLine) { - $headers[$headerLine[1]][] = $headerLine[2]; - } - - return [ - 'start-line' => $startLine, - 'headers' => $headers, - 'body' => $body, - ]; + return MessageParser::parseMessage($message); } /** * Constructs a URI for an HTTP request message. * + * The URI is composed from the start-line path and the `Host` header, using + * `https` when the host's port is `443` and `http` otherwise. Without a + * `Host` header, only the path is returned, with extra leading slashes + * collapsed so an origin-form target cannot be parsed as a network-path + * reference with its own authority. An `InvalidArgumentException` is thrown + * when the `Host` header is invalid. + * * @param string $path Path from the start-line * @param array $headers Array of headers (each value an array). */ public static function parseRequestUri(string $path, array $headers): string { - $host = self::getHostFromHeaders($headers); - - // If no host is found, then a full URI cannot be constructed. - // Collapse leading slashes so an origin-form target cannot be - // parsed as a network-path reference with its own authority. - if ($host === null) { - return self::normalizePathForOriginForm($path); - } - - $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; - - return $scheme.'://'.$host.'/'.ltrim($path, '/'); - } - - private static function normalizePathForOriginForm(string $path): string - { - if (0 === strpos($path, '//')) { - return '/'.ltrim($path, '/'); - } - - return $path; - } - - /** - * @param array $headers Array of headers (each value an array). - */ - private static function getHostFromHeaders(array $headers): ?string - { - $hostKey = array_filter(array_keys($headers), function ($k) { - // Numeric array keys are converted to int by PHP. - $k = (string) $k; - - return Utils::asciiToLower($k) === 'host'; - }); - - if (!$hostKey) { - return null; - } - - $host = $headers[reset($hostKey)][0]; - if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) { - throw new \InvalidArgumentException('Invalid request string'); - } - - return $host; + return MessageParser::parseRequestUri($path, $headers); } /** * Parses a request message string into a request object. * + * The request-target must be in origin form, absolute form (without a + * userinfo component), authority form (`CONNECT`), or asterisk form + * (`OPTIONS`), and any `Host` header must be a single valid value; + * otherwise an `InvalidArgumentException` is thrown. Non-origin-form + * targets are preserved on the returned request via `withRequestTarget()`. + * * @param string $message Request message string. */ public static function parseRequest(string $message): RequestInterface { - $data = self::parseMessage($message); - if (strpbrk($data['start-line'], "\r\n") !== false) { - throw new \InvalidArgumentException('Invalid request string'); - } - - $matches = []; - $requestStartLineMatch = preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches); - - if ($requestStartLineMatch === false) { - throw new \RuntimeException('Unable to parse request start line: '.preg_last_error_msg()); - } - - if ($requestStartLineMatch === 0) { - throw new \InvalidArgumentException('Invalid request string'); - } - $parts = explode(' ', $data['start-line'], 3); - $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; - - $request = new Request( - $parts[0], - $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], - $data['headers'], - $data['body'], - $version - ); - - return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + return MessageParser::parseRequest($message); } /** @@ -352,31 +251,6 @@ final class Message */ public static function parseResponse(string $message): ResponseInterface { - $data = self::parseMessage($message); - if (strpbrk($data['start-line'], "\r\n") !== false) { - throw new \InvalidArgumentException('Invalid response string'); - } - - // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 - // the space between status-code and reason-phrase is required. But - // browsers accept responses without space and reason as well. - $responseStartLineMatch = preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/D', $data['start-line']); - - if ($responseStartLineMatch === false) { - throw new \RuntimeException('Unable to parse response start line: '.preg_last_error_msg()); - } - - if ($responseStartLineMatch === 0) { - throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']); - } - $parts = explode(' ', $data['start-line'], 3); - - return new Response( - (int) $parts[1], - $data['headers'], - $data['body'], - explode('/', $parts[0])[1], - $parts[2] ?? null - ); + return MessageParser::parseResponse($message); } } diff --git a/libs/vendor/guzzlehttp/psr7/src/MessageParser.php b/libs/vendor/guzzlehttp/psr7/src/MessageParser.php new file mode 100644 index 00000000..22fe0189 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/MessageParser.php @@ -0,0 +1,363 @@ +> $headerLines */ + if ($count === false) { + throw new \RuntimeException('Unable to parse HTTP headers: '.preg_last_error_msg()); + } + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc9112#section-5.2 + $hasFoldedHeader = preg_match(Rfc9112::HEADER_FOLD_REGEX, $rawHeaders); + + if ($hasFoldedHeader === false) { + throw new \RuntimeException('Unable to inspect HTTP header folding: '.preg_last_error_msg()); + } + + if ($hasFoldedHeader === 1) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + public static function parseRequestUri(string $path, array $headers): string + { + $host = self::getHostFromHeaders($headers); + + // If no host is found, then a full URI cannot be constructed. + // Collapse leading slashes so an origin-form target cannot be + // parsed as a network-path reference with its own authority. + if ($host === null) { + return self::normalizePathForOriginForm($path); + } + + [$authorityHost, $port] = self::parseHostHeaderAuthority($host); + $scheme = $port === 443 ? 'https' : 'http'; + + return $scheme.'://'.self::composeAuthority($authorityHost, $port).'/'.ltrim($path, '/'); + } + + private static function normalizePathForOriginForm(string $path): string + { + if (str_starts_with($path, '//')) { + return '/'.ltrim($path, '/'); + } + + return $path; + } + + /** + * @return array{0: string, 1: int|null} + */ + private static function parseHostHeaderAuthority(string $authority): array + { + $parsed = Rfc9112::parseHostHeader($authority); + if ($parsed === null) { + throw new \InvalidArgumentException('Invalid request string'); + } + + return $parsed; + } + + private static function composeAuthority(string $host, ?int $port): string + { + return $host.($port !== null ? ':'.$port : ''); + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getHostFromHeaders(array $headers): ?string + { + $host = self::getSingleHostHeader($headers); + if ($host === null) { + return null; + } + + self::parseHostHeaderAuthority($host); + + return $host; + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getSingleHostHeader(array $headers): ?string + { + $host = null; + $found = false; + + foreach ($headers as $name => $values) { + if (Utils::asciiToLower((string) $name) !== 'host') { + continue; + } + + if ($found || !is_array($values) || count($values) !== 1) { + throw new \InvalidArgumentException('Invalid request string'); + } + + $found = true; + $host = reset($values); + } + + if (!$found) { + return null; + } + + if (!is_string($host)) { + throw new \InvalidArgumentException('Invalid request string'); + } + + return $host; + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function parseRequestAuthorityUri(array $headers): string + { + $host = self::getHostFromHeaders($headers); + if ($host === null) { + return ''; + } + + [$authorityHost, $port] = self::parseHostHeaderAuthority($host); + $scheme = $port === 443 ? 'https' : 'http'; + + return $scheme.'://'.self::composeAuthority($authorityHost, $port); + } + + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + $matched = preg_match( + '/^(?P'.Rfc9110::TOKEN_PATTERN.') (?P'.Rfc9112::REQUEST_TARGET_PATTERN.') HTTP\/(?P'.Rfc9112::PROTOCOL_VERSION_PATTERN.')$/D', + $data['start-line'], + $matches + ); + + if ($matched === false) { + throw new \RuntimeException('Unable to parse request start line: '.preg_last_error_msg()); + } + + if ($matched === 0) { + throw new \InvalidArgumentException('Invalid request string'); + } + + self::getHostFromHeaders($data['headers']); + + if (str_starts_with($matches['target'], '/')) { + return new Request( + $matches['method'], + self::parseRequestUri($matches['target'], $data['headers']), + $data['headers'], + $data['body'], + $matches['version'] + ); + } + + $absoluteFormUri = self::parseAbsoluteFormRequestTarget($matches['target']); + if ($absoluteFormUri !== null) { + return (new Request( + $matches['method'], + $absoluteFormUri, + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + if (Rfc9112::isAsteriskFormRequestTarget($matches['method'], $matches['target'])) { + return (new Request( + $matches['method'], + self::parseRequestAuthorityUri($data['headers']), + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + $connectUri = self::parseConnectAuthorityFormRequestTarget($matches['method'], $matches['target']); + if ($connectUri !== null) { + return (new Request( + $matches['method'], + $connectUri, + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + throw new \InvalidArgumentException('Invalid request string'); + } + + private static function parseAbsoluteFormRequestTarget(string $target): ?Uri + { + if (!Rfc9112::isAbsoluteFormRequestTarget($target)) { + return null; + } + + $authority = substr($target, strpos($target, '//') + 2); + $authority = substr($authority, 0, strcspn($authority, '/?#')); + + // RFC 9110 deprecates userinfo in message target URIs and directs + // recipients to treat its presence as an error, since it can obscure + // the authority. Host headers and CONNECT targets already reject it. + if (str_contains($authority, '@')) { + return null; + } + + try { + $uri = new Uri($target); + } catch (\InvalidArgumentException $e) { + return null; + } + + if ($uri->getHost() === '') { + return null; + } + + try { + self::parseHostHeaderAuthority(self::composeAuthority($uri->getHost(), $uri->getPort())); + } catch (\InvalidArgumentException $e) { + return null; + } + + return $uri; + } + + private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?Uri + { + if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) { + return null; + } + + $parsed = Rfc9112::parseHostHeader($target); + if ($parsed === null) { + return null; + } + + [$host, $port] = $parsed; + if ($port === null) { + return null; + } + + try { + return new Uri('//'.self::composeAuthority($host, $port)); + } catch (\InvalidArgumentException $e) { + return null; + } + } + + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://datatracker.ietf.org/doc/html/rfc9112#section-4 + // the space between status-code and reason-phrase is required. But + // browsers accept responses without space and reason as well. + $matched = preg_match( + '/^HTTP\/(?P'.Rfc9112::PROTOCOL_VERSION_PATTERN.') (?P[1-5][0-9]{2})(?: (?P'.Rfc9110::FIELD_VALUE_PATTERN.'))?$/D', + $data['start-line'], + $matches + ); + + if ($matched === false) { + throw new \RuntimeException('Unable to parse response start line: '.preg_last_error_msg()); + } + + if ($matched === 0) { + throw new \InvalidArgumentException(\sprintf('Invalid response string: %s', DiagnosticValue::escape($data['start-line']))); + } + + return new Response( + (int) $matches['status'], + $data['headers'], + $data['body'], + $matches['version'], + $matches['reason'] ?? null + ); + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/MessageTrait.php b/libs/vendor/guzzlehttp/psr7/src/MessageTrait.php index 16337a3b..12da9f5f 100644 --- a/libs/vendor/guzzlehttp/psr7/src/MessageTrait.php +++ b/libs/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -13,16 +13,14 @@ use Psr\Http\Message\StreamInterface; trait MessageTrait { /** @var string[][] Map of all registered headers, as original name => array of values */ - private $headers = []; + private array $headers = []; /** @var string[] Map of lowercase header name => original name at registration */ - private $headerNames = []; + private array $headerNames = []; - /** @var string */ - private $protocol = '1.1'; + private string $protocol = '1.1'; - /** @var StreamInterface|null */ - private $stream; + private ?StreamInterface $stream = null; public function getProtocolVersion(): string { @@ -32,17 +30,8 @@ trait MessageTrait /** * @return static */ - public function withProtocolVersion($version): MessageInterface + public function withProtocolVersion(string $version): MessageInterface { - if (!\is_string($version)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to MessageInterface::withProtocolVersion() is deprecated; guzzlehttp/psr7 3.0 requires string.', - \get_debug_type($version) - ); - } - $this->assertProtocolVersion($version); if ($this->protocol === $version) { @@ -60,14 +49,14 @@ trait MessageTrait return $this->headers; } - public function hasHeader($header): bool + public function hasHeader(string $name): bool { - return isset($this->headerNames[Utils::asciiToLower($header)]); + return isset($this->headerNames[Utils::asciiToLower($name)]); } - public function getHeader($header): array + public function getHeader(string $name): array { - $header = Utils::asciiToLower($header); + $header = Utils::asciiToLower($name); if (!isset($this->headerNames[$header])) { return []; @@ -78,39 +67,26 @@ trait MessageTrait return $this->headers[$header]; } - public function getHeaderLine($header): string + public function getHeaderLine(string $name): string { - return implode(', ', $this->getHeader($header)); + return implode(', ', $this->getHeader($name)); } /** * @return static */ - public function withHeader($header, $value): MessageInterface + public function withHeader(string $name, $value): MessageInterface { - $this->assertHeader($header); - $values = \is_array($value) ? $value : [$value]; - foreach ($values as $item) { - if (!\is_string($item) && (\is_scalar($item) || $item === null)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to MessageInterface::withHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', - \get_debug_type($item) - ); - - break; - } - } + $this->assertHeader($name); $value = $this->normalizeHeaderValue($value); - $normalized = Utils::asciiToLower($header); + $normalized = Utils::asciiToLower($name); $new = clone $this; if (isset($new->headerNames[$normalized])) { unset($new->headers[$new->headerNames[$normalized]]); } - $new->headerNames[$normalized] = $header; - $new->headers[$header] = $value; + $new->headerNames[$normalized] = $name; + $new->headers[$name] = $value; return $new; } @@ -118,32 +94,19 @@ trait MessageTrait /** * @return static */ - public function withAddedHeader($header, $value): MessageInterface + public function withAddedHeader(string $name, $value): MessageInterface { - $this->assertHeader($header); - $values = \is_array($value) ? $value : [$value]; - foreach ($values as $item) { - if (!\is_string($item) && (\is_scalar($item) || $item === null)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to MessageInterface::withAddedHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', - \get_debug_type($item) - ); - - break; - } - } + $this->assertHeader($name); $value = $this->normalizeHeaderValue($value); - $normalized = Utils::asciiToLower($header); + $normalized = Utils::asciiToLower($name); $new = clone $this; if (isset($new->headerNames[$normalized])) { - $header = $this->headerNames[$normalized]; - $new->headers[$header] = array_merge($this->headers[$header], $value); + $name = $this->headerNames[$normalized]; + $new->headers[$name] = array_merge($this->headers[$name], $value); } else { - $new->headerNames[$normalized] = $header; - $new->headers[$header] = $value; + $new->headerNames[$normalized] = $name; + $new->headers[$name] = $value; } return $new; @@ -152,18 +115,18 @@ trait MessageTrait /** * @return static */ - public function withoutHeader($header): MessageInterface + public function withoutHeader(string $name): MessageInterface { - $normalized = Utils::asciiToLower($header); + $normalized = Utils::asciiToLower($name); if (!isset($this->headerNames[$normalized])) { return $this; } - $header = $this->headerNames[$normalized]; + $name = $this->headerNames[$normalized]; $new = clone $this; - unset($new->headers[$header], $new->headerNames[$normalized]); + unset($new->headers[$name], $new->headerNames[$normalized]); return $new; } @@ -203,20 +166,6 @@ trait MessageTrait $header = (string) $header; $this->assertHeader($header); - $values = \is_array($value) ? $value : [$value]; - foreach ($values as $item) { - if (!\is_string($item) && (\is_scalar($item) || $item === null)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to %s::__construct() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', - \get_debug_type($item), - static::class - ); - - break; - } - } $value = $this->normalizeHeaderValue($value); $normalized = Utils::asciiToLower($header); if (isset($this->headerNames[$normalized])) { @@ -237,11 +186,7 @@ trait MessageTrait private function normalizeHeaderValue($value): array { if (is_array($value) && $value === []) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing an empty array as a header value is deprecated; guzzlehttp/psr7 3.0 rejects empty header value arrays.' - ); + throw new \InvalidArgumentException('Header value must be a non-empty array or string.'); } if (!is_array($value)) { @@ -263,25 +208,19 @@ trait MessageTrait * * @return string[] Trimmed header values * - * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5 */ private function trimAndValidateHeaderValues(array $values): array { - return array_map(function ($value) { - if (!is_scalar($value) && null !== $value) { + return array_map(function ($value): string { + if (!is_string($value)) { throw new \InvalidArgumentException(sprintf( - 'Header value must be scalar or null but %s provided.', - is_object($value) ? get_class($value) : gettype($value) + 'Header value must be a string or array of strings but %s provided.', + \get_debug_type($value) )); } - // Convert non-finite floats explicitly, as implicit coercion of - // NAN emits a warning on PHP 8.5. - if (is_float($value) && !is_finite($value)) { - $value = is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); - } - - $trimmed = trim((string) $value, " \t"); + $trimmed = trim($value, " \t"); $this->assertValue($trimmed); return $trimmed; @@ -289,45 +228,24 @@ trait MessageTrait } /** - * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 - * - * @param mixed $header + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.1 */ - private function assertHeader($header): void + private function assertHeader(string $header): void { - if (!is_string($header)) { - throw new \InvalidArgumentException(sprintf( - 'Header name must be a string but %s provided.', - is_object($header) ? get_class($header) : gettype($header) - )); + if (!Rfc9110::isToken($header)) { + throw new \InvalidArgumentException(sprintf('Invalid header name: %s', DiagnosticValue::escape($header))); } + } - if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { - throw new \InvalidArgumentException( - sprintf('"%s" is not valid header name.', $header) - ); + private function assertProtocolVersion(string $version): void + { + if (!Rfc9112::isValidProtocolVersion($version)) { + throw new \InvalidArgumentException('Protocol version must be a valid HTTP version number.'); } } /** - * @param mixed $version - */ - private function assertProtocolVersion($version): void - { - if (is_string($version)) { - $this->assertNoLineSeparators($version, 'Protocol version'); - } - } - - private function assertNoLineSeparators(string $value, string $field): void - { - if (strpbrk($value, "\r\n") !== false) { - throw new \InvalidArgumentException($field.' must not contain CR or LF characters.'); - } - } - - /** - * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5 * * field-value = *( field-content / obs-fold ) * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] @@ -338,21 +256,20 @@ trait MessageTrait */ private function assertValue(string $value): void { - // The regular expression intentionally does not support the obs-fold production, because as - // per RFC 7230#3.2.4: + // The regular expression intentionally does not support the obs-fold + // production, because as per RFC 9112#5.2: // - // A sender MUST NOT generate a message that includes - // line folding (i.e., that has any field-value that contains a match to - // the obs-fold rule) unless the message is intended for packaging - // within the message/http media type. + // A sender MUST NOT generate a message that includes line folding + // (i.e., that has any field-value that contains a match to the obs-fold + // rule) unless the message is intended for packaging within the + // message/http media type. // - // Clients must not send a request with line folding and a server sending folded headers is - // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting - // folding is not likely to break any legitimate use case. - if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { - throw new \InvalidArgumentException( - sprintf('"%s" is not valid header value.', $value) - ); + // Clients must not send a request with line folding and a server + // sending folded headers is likely very rare. Line folding is a fairly + // obscure feature of HTTP/1.1 and thus not accepting folding is not + // likely to break any legitimate use case. + if (!Rfc9110::isFieldValue($value)) { + throw new \InvalidArgumentException(sprintf('Invalid header value: %s', DiagnosticValue::escape($value))); } } } diff --git a/libs/vendor/guzzlehttp/psr7/src/MimeType.php b/libs/vendor/guzzlehttp/psr7/src/MimeType.php index b812864c..0d17cd2a 100644 --- a/libs/vendor/guzzlehttp/psr7/src/MimeType.php +++ b/libs/vendor/guzzlehttp/psr7/src/MimeType.php @@ -6,6 +6,10 @@ namespace GuzzleHttp\Psr7; final class MimeType { + private function __construct() + { + } + private const MIME_TYPES = [ '123' => 'application/vnd.lotus-1-2-3', '1km' => 'application/vnd.1000minds.decision-model+xml', @@ -1284,7 +1288,7 @@ final class MimeType ]; /** - * Determines the mimetype of a file by looking at its extension. + * Determines the MIME type of a file by looking at its extension. * * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json */ @@ -1294,7 +1298,7 @@ final class MimeType } /** - * Maps a file extensions to a mimetype. + * Maps a file extension to a MIME type. * * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json */ diff --git a/libs/vendor/guzzlehttp/psr7/src/MultipartStream.php b/libs/vendor/guzzlehttp/psr7/src/MultipartStream.php index 0f454aa7..74cb9cd9 100644 --- a/libs/vendor/guzzlehttp/psr7/src/MultipartStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -13,12 +13,11 @@ use Psr\Http\Message\StreamInterface; final class MultipartStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var string */ - private $boundary; + private string $boundary; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; private const BOUNDARY_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=? "; @@ -43,15 +42,11 @@ final class MultipartStream implements StreamInterface */ public function __construct(array $elements = [], ?string $boundary = null) { - if ($boundary !== null && !self::isValidBoundary($boundary)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing an invalid multipart boundary to MultipartStream::__construct() is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart boundaries.' - ); + if ($boundary !== null) { + self::validateBoundary($boundary); } - $this->boundary = $boundary ?: bin2hex(random_bytes(20)); + $this->boundary = $boundary ?? bin2hex(random_bytes(20)); $this->stream = $this->createStream($elements); } @@ -75,10 +70,14 @@ final class MultipartStream implements StreamInterface $str = ''; foreach ($headers as $key => $value) { $key = (string) $key; + + self::validatePartHeaderName($key); + self::validatePartHeaderValue($value); + $str .= "{$key}: {$value}\r\n"; } - return "--{$this->boundary}\r\n".trim($str, " \n\r\t\0\x0B")."\r\n\r\n"; + return "--{$this->boundary}\r\n".rtrim($str, "\r\n")."\r\n\r\n"; } /** @@ -129,18 +128,10 @@ final class MultipartStream implements StreamInterface if (is_scalar($contents) && !is_string($contents)) { // Multipart field values are byte strings on the wire, so finite // numeric and boolean field values are cast to string here rather - // than tripping streamFor()'s non-string-scalar deprecation. Non-finite - // floats are deprecated and normalized here too, so the deprecation is - // reported against MultipartStream instead of transitively through - // streamFor(). + // than rejected by streamFor(). Non-finite floats cannot be + // represented and are rejected. if (is_float($contents) && !is_finite($contents)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.12', - 'Passing a non-finite float as multipart contents is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.' - ); - - $contents = is_nan($contents) ? 'NAN' : ($contents > 0 ? 'INF' : '-INF'); + throw new \InvalidArgumentException('Cannot create a stream from a non-finite float.'); } $contents = (string) $contents; @@ -149,7 +140,7 @@ final class MultipartStream implements StreamInterface if (empty($element['filename'])) { $uri = $element['contents']->getMetadata('uri'); - if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + if ($uri && \is_string($uri) && !str_starts_with($uri, 'php://') && !str_starts_with($uri, 'data://')) { $element['filename'] = $uri; } } @@ -196,21 +187,14 @@ final class MultipartStream implements StreamInterface // Set a default content-disposition header if one was no provided $disposition = self::getHeader($headers, 'content-disposition'); if (!$disposition) { + $escapedName = self::escapeContentDispositionParameter($name); $headers['Content-Disposition'] = ($filename === '0' || $filename) ? sprintf( 'form-data; name="%s"; filename="%s"', - $name, - basename($filename) + $escapedName, + self::escapeContentDispositionParameter(basename($filename)) ) - : "form-data; name=\"{$name}\""; - } - - // Set a default content-length header if one was no provided - $length = self::getHeader($headers, 'content-length'); - if (!$length) { - if ($length = $stream->getSize()) { - $headers['Content-Length'] = (string) $length; - } + : sprintf('form-data; name="%s"', $escapedName); } // Set a default Content-Type if one was not supplied @@ -237,15 +221,17 @@ final class MultipartStream implements StreamInterface return null; } - private static function isValidBoundary(string $boundary): bool + private static function validateBoundary(string $boundary): void { $length = strlen($boundary); if ($length < 1 || $length > 70 || $boundary[$length - 1] === ' ') { - return false; + throw new \InvalidArgumentException('Invalid multipart boundary.'); } - return strspn($boundary, self::BOUNDARY_CHARS) === $length; + if (strspn($boundary, self::BOUNDARY_CHARS) !== $length) { + throw new \InvalidArgumentException('Invalid multipart boundary.'); + } } /** @@ -258,27 +244,15 @@ final class MultipartStream implements StreamInterface $normalized = []; foreach ($headers as $key => $value) { - self::deprecateInvalidPartHeaderName((string) $key); + $key = (string) $key; + + self::validatePartHeaderName($key); if (!is_string($value)) { - if (!is_scalar($value) && $value !== null && !(is_object($value) && method_exists($value, '__toString'))) { - throw new \InvalidArgumentException(sprintf( - 'Multipart part header value must be a string or stringable value but %s provided.', - \get_debug_type($value) - )); - } - - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s as a multipart part header value is deprecated; guzzlehttp/psr7 3.0 requires string multipart part header values.', - \get_debug_type($value) - ); + throw new \InvalidArgumentException('Multipart part header value must be a string.'); } - $value = (string) $value; - - self::deprecateInvalidPartHeaderValue($value); + self::validatePartHeaderValue($value); $normalized[$key] = $value; } @@ -286,25 +260,23 @@ final class MultipartStream implements StreamInterface return $normalized; } - private static function deprecateInvalidPartHeaderName(string $name): void + private static function validatePartHeaderName(string $name): void { - if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing an invalid multipart part header name to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header names.' - ); + if (!Rfc9110::isToken($name)) { + throw new \InvalidArgumentException(sprintf('Invalid multipart part header name: %s', DiagnosticValue::escape($name))); } } - private static function deprecateInvalidPartHeaderValue(string $value): void + private static function validatePartHeaderValue(string $value): void { - if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing an invalid multipart part header value to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header values.' - ); + if (!Rfc9110::isFieldValue($value)) { + throw new \InvalidArgumentException(sprintf('Invalid multipart part header value: %s', DiagnosticValue::escape($value))); } } + + private static function escapeContentDispositionParameter(string $value): string + { + // Match WHATWG browser multipart/form-data behavior: escape CR, LF, and DQUOTE only. + return str_replace(["\r", "\n", '"'], ['%0D', '%0A', '%22'], $value); + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php index 3abb57bd..1611e327 100644 --- a/libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php +++ b/libs/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -12,30 +12,12 @@ use Psr\Http\Message\StreamInterface; final class NoSeekStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - throw new \RuntimeException('Cannot seek a NoSeekStream'); } diff --git a/libs/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php b/libs/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php new file mode 100644 index 00000000..cc605cc9 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php @@ -0,0 +1,21 @@ +source = $source; - $this->size = $options['size'] ?? null; + $this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size'); $this->metadata = $options['metadata'] ?? []; $this->buffer = new BufferStream(); } + public function __unserialize(array $data): void + { + $this->source = null; + $this->size = null; + $this->tellPos = 0; + $this->metadata = []; + $this->buffer = new BufferStream(); + + throw new \LogicException(static::class.' should never be unserialized'); + } + public function __toString(): string { - try { - return Utils::copyToString($this); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - - return ''; - } + return Utils::copyToString($this); } public function close(): void @@ -80,6 +81,7 @@ final class PumpStream implements StreamInterface { $this->tellPos = 0; $this->source = null; + $this->buffer->close(); return null; } @@ -109,26 +111,8 @@ final class PumpStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - throw new \RuntimeException('Cannot seek a PumpStream'); } @@ -137,17 +121,8 @@ final class PumpStream implements StreamInterface return false; } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - throw new \RuntimeException('Cannot write to a PumpStream'); } @@ -156,56 +131,35 @@ final class PumpStream implements StreamInterface return true; } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + $bufferLength = $this->buffer->getSize() ?? 0; + + if ($length > $bufferLength) { + $this->pump($length - $bufferLength); } $data = $this->buffer->read($length); - $readLen = strlen($data); - $this->tellPos += $readLen; - $remaining = $length - $readLen; - - if ($remaining) { - $this->pump($remaining); - $data .= $this->buffer->read($remaining); - $this->tellPos += strlen($data) - $readLen; - } + $this->tellPos = Integers::add($this->tellPos, strlen($data)); return $data; } public function getContents(): string { - $result = ''; - while (!$this->eof()) { - $result .= $this->read(1000000); - } - - return $result; + return Utils::copyToString($this); } /** * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); - } - - if (!$key) { + if ($key === null) { return $this->metadata; } @@ -216,12 +170,18 @@ final class PumpStream implements StreamInterface { if ($this->source !== null) { do { + /** @var string|false|null $data */ $data = ($this->source)($length); if ($data === false || $data === null) { $this->source = null; return; } + + if ($data === '') { + throw new \RuntimeException('PumpStream source returned an empty string'); + } + $this->buffer->write($data); $length -= strlen($data); } while ($length > 0); diff --git a/libs/vendor/guzzlehttp/psr7/src/Query.php b/libs/vendor/guzzlehttp/psr7/src/Query.php index a7b8997a..c54c669f 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Query.php +++ b/libs/vendor/guzzlehttp/psr7/src/Query.php @@ -6,13 +6,17 @@ namespace GuzzleHttp\Psr7; final class Query { + private function __construct() + { + } + /** * Parse a query string into an associative array. * - * If multiple values are found for the same key, the value of that key - * value pair will become an array. This function does not parse nested - * PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` - * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + * If multiple values are found for the same key, the value of that + * key-value pair becomes an array. This function does not parse nested PHP + * style arrays into an associative array. For example, `foo[a]=1&foo[b]=2` + * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2']`. * * @param string $str Query string to parse * @param int|bool $urlEncoding How the query string is encoded @@ -26,15 +30,19 @@ final class Query } if ($urlEncoding === true) { - $decoder = function ($value) { - return rawurldecode(str_replace('+', ' ', (string) $value)); + $decoder = function (string $value): string { + return \rawurldecode(str_replace('+', ' ', $value)); }; } elseif ($urlEncoding === PHP_QUERY_RFC3986) { - $decoder = 'rawurldecode'; + $decoder = static function (string $value): string { + return \rawurldecode($value); + }; } elseif ($urlEncoding === PHP_QUERY_RFC1738) { - $decoder = 'urldecode'; + $decoder = static function (string $value): string { + return \urldecode($value); + }; } else { - $decoder = function ($str) { + $decoder = function (string $str): string { return $str; }; } @@ -57,11 +65,11 @@ final class Query } /** - * Build a query string from an array of key value pairs. + * Build a query string from an array of key-value pairs. * * This function can use the return value of `parse()` to build a query * string. This function does not modify the provided keys when an array is - * encountered (like `http_build_query()` would). + * encountered, unlike `http_build_query()`. * * @param array $params Query string parameters. * @param int|false $encoding Set to false to not encode, @@ -82,31 +90,33 @@ final class Query return $str; }; } elseif ($encoding === PHP_QUERY_RFC3986) { - $encoder = 'rawurlencode'; + $encoder = static function (string $value): string { + return \rawurlencode($value); + }; } elseif ($encoding === PHP_QUERY_RFC1738) { - $encoder = 'urlencode'; + $encoder = static function (string $value): string { + return \urlencode($value); + }; } else { throw new \InvalidArgumentException('Invalid type'); } - $castBool = $treatBoolsAsInts ? static function ($v) { return (int) $v; } : static function ($v) { return $v ? 'true' : 'false'; }; - $qs = ''; foreach ($params as $k => $v) { $k = $encoder((string) $k); if (!is_array($v)) { $qs .= $k; - $v = is_bool($v) ? $castBool($v) : self::normalizeNonFiniteFloat($v); + $v = self::normalizeValue($v, $treatBoolsAsInts); if ($v !== null) { - $qs .= '='.$encoder((string) $v); + $qs .= '='.$encoder($v); } $qs .= '&'; } else { foreach ($v as $vv) { $qs .= $k; - $vv = is_bool($vv) ? $castBool($vv) : self::normalizeNonFiniteFloat($vv); + $vv = self::normalizeValue($vv, $treatBoolsAsInts); if ($vv !== null) { - $qs .= '='.$encoder((string) $vv); + $qs .= '='.$encoder($vv); } $qs .= '&'; } @@ -117,25 +127,30 @@ final class Query } /** - * Converts non-finite floats to the strings PHP coerces them to, as - * implicit coercion of NAN emits a warning on PHP 8.5. - * * @param mixed $value - * - * @return mixed */ - private static function normalizeNonFiniteFloat($value) + private static function normalizeValue($value, bool $treatBoolsAsInts): ?string { - if (is_float($value) && !is_finite($value)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.12', - 'Passing a non-finite float to Query::build() is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.' - ); - - return is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + if ($value === null) { + return null; } - return $value; + if (is_bool($value)) { + return $treatBoolsAsInts ? (string) (int) $value : ($value ? 'true' : 'false'); + } + + if (is_float($value) && !is_finite($value)) { + throw new \InvalidArgumentException('Query string values must be finite; non-finite floats are not supported.'); + } + + if (is_scalar($value)) { + return (string) $value; + } + + if (is_object($value) && method_exists($value, '__toString')) { + return $value->__toString(); + } + + throw new \InvalidArgumentException('Query string values must be scalar, null, or stringable objects'); } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Request.php b/libs/vendor/guzzlehttp/psr7/src/Request.php index ae44da4e..c49b16d6 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Request.php +++ b/libs/vendor/guzzlehttp/psr7/src/Request.php @@ -16,14 +16,11 @@ class Request implements RequestInterface { use MessageTrait; - /** @var string */ - private $method; + private string $method; - /** @var string|null */ - private $requestTarget; + private ?string $requestTarget = null; - /** @var UriInterface */ - private $uri; + private UriInterface $uri; /** * @param string $method HTTP method @@ -45,9 +42,9 @@ class Request implements RequestInterface if (!$uri instanceof UriInterface) { $uri = new Uri($uri); } + self::getRequestTargetFromUri($uri); - self::warnOnMethodCasingChange($method); - $this->method = Utils::asciiToUpper($method); + $this->method = $method; $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; @@ -67,30 +64,12 @@ class Request implements RequestInterface return $this->requestTarget; } - $target = $this->uri->getPath(); - if ($target === '') { - $target = '/'; - } - if ($this->uri->getQuery() != '') { - $target .= '?'.$this->uri->getQuery(); - } - - return $target; + return self::getRequestTargetFromUri($this->uri); } - public function withRequestTarget($requestTarget): RequestInterface + public function withRequestTarget(string $requestTarget): RequestInterface { - $hasWhitespace = preg_match('#\s#', $requestTarget); - - if ($hasWhitespace === false) { - throw new \RuntimeException('Unable to validate request target: '.preg_last_error_msg()); - } - - if ($hasWhitespace === 1) { - throw new InvalidArgumentException( - 'Invalid request target provided; cannot contain whitespace' - ); - } + self::assertRequestTarget($requestTarget); $new = clone $this; $new->requestTarget = $requestTarget; @@ -103,12 +82,11 @@ class Request implements RequestInterface return $this->method; } - public function withMethod($method): RequestInterface + public function withMethod(string $method): RequestInterface { $this->assertMethod($method); - self::warnOnMethodCasingChange($method); $new = clone $this; - $new->method = Utils::asciiToUpper($method); + $new->method = $method; return $new; } @@ -118,26 +96,30 @@ class Request implements RequestInterface return $this->uri; } - public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface { - if (!\is_bool($preserveHost)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to RequestInterface::withUri() is deprecated; guzzlehttp/psr7 3.0 requires bool for $preserveHost.', - \get_debug_type($preserveHost) - ); + $sameUri = $uri === $this->uri; + + if (!$sameUri && $this->requestTarget === null) { + self::getRequestTargetFromUri($uri); } - if ($uri === $this->uri) { + $currentHost = $this->getHeaderLine('Host'); + $host = null; + + if (!$preserveHost || $currentHost === '') { + $host = $this->getHostFromUri($uri); + } + + if ($sameUri && ($host === null || $currentHost === $host)) { return $this; } $new = clone $this; $new->uri = $uri; - if (!$preserveHost || !isset($this->headerNames['host'])) { - $new->updateHostFromUri(); + if ($host !== null) { + $new->setHostHeader($host); } return $new; @@ -145,20 +127,36 @@ class Request implements RequestInterface private function updateHostFromUri(): void { - $host = $this->uri->getHost(); + $host = $this->getHostFromUri($this->uri); - if ($host == '') { + if ($host === null) { return; } + $this->setHostHeader($host); + } + + private function getHostFromUri(UriInterface $uri): ?string + { + $host = $uri->getHost(); + + if ($host === '') { + return null; + } + Uri::assertValidHost($host); - if (($port = $this->uri->getPort()) !== null) { + if (($port = $uri->getPort()) !== null) { $host .= ':'.$port; } $this->assertValue($host); + return $host; + } + + private function setHostHeader(string $host): void + { if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else { @@ -166,30 +164,47 @@ class Request implements RequestInterface $this->headerNames['host'] = 'Host'; } // Ensure Host is the first header. - // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 + // See: https://datatracker.ietf.org/doc/html/rfc9110#section-7.2 $this->headers = [$header => [$host]] + $this->headers; } - /** - * @param mixed $method - */ - private function assertMethod($method): void + private function assertMethod(string $method): void { - if (!is_string($method) || $method === '') { - throw new InvalidArgumentException('Method must be a non-empty string.'); + if (!Rfc9110::isToken($method)) { + throw new InvalidArgumentException('Method must be a valid HTTP token.'); } - - $this->assertNoLineSeparators($method, 'Method'); } - private static function warnOnMethodCasingChange(string $method): void + private static function getRequestTargetFromUri(UriInterface $uri): string { - if ($method !== Utils::asciiToUpper($method)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing a non-uppercase HTTP method is deprecated; guzzlehttp/psr7 3.0 preserves method casing and will no longer uppercase it. Normalize the method before constructing or modifying requests if uppercase is required.' + $target = self::normalizePathForOriginForm($uri->getPath()); + if ($target === '') { + $target = '/'; + } + if ($uri->getQuery() != '') { + $target .= '?'.$uri->getQuery(); + } + + self::assertRequestTarget($target); + + return $target; + } + + private static function assertRequestTarget(string $requestTarget): void + { + if (!Rfc9112::isValidRequestTarget($requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot be empty or contain whitespace or control characters' ); } } + + private static function normalizePathForOriginForm(string $path): string + { + if (str_starts_with($path, '//')) { + return '/'.ltrim($path, '/'); + } + + return $path; + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Response.php b/libs/vendor/guzzlehttp/psr7/src/Response.php index 4bec8163..a4e8ff66 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Response.php +++ b/libs/vendor/guzzlehttp/psr7/src/Response.php @@ -78,11 +78,9 @@ class Response implements ResponseInterface 511 => 'Network Authentication Required', ]; - /** @var string */ - private $reasonPhrase; + private string $reasonPhrase; - /** @var int */ - private $statusCode; + private int $statusCode; /** * @param int $status Status code @@ -114,7 +112,7 @@ class Response implements ResponseInterface $reasonPhrase = (string) $reason; } - $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); + $this->assertReasonPhrase($reasonPhrase); $this->reasonPhrase = $reasonPhrase; $this->protocol = $version; @@ -130,56 +128,32 @@ class Response implements ResponseInterface return $this->reasonPhrase; } - public function withStatus($code, $reasonPhrase = ''): ResponseInterface + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface { - if (!\is_int($code) && \filter_var($code, \FILTER_VALIDATE_INT) !== false) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires int for $code.', - \get_debug_type($code) - ); - } - - if (!\is_string($reasonPhrase)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires string for $reasonPhrase.', - \get_debug_type($reasonPhrase) - ); - } - - $this->assertStatusCodeIsInteger($code); - $code = (int) $code; $this->assertStatusCodeRange($code); $new = clone $this; $new->statusCode = $code; - if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + if ($reasonPhrase === '' && isset(self::PHRASES[$new->statusCode])) { $reasonPhrase = self::PHRASES[$new->statusCode]; } - $reasonPhrase = (string) $reasonPhrase; - $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); + $this->assertReasonPhrase($reasonPhrase); $new->reasonPhrase = $reasonPhrase; return $new; } - /** - * @param mixed $statusCode - */ - private function assertStatusCodeIsInteger($statusCode): void - { - if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { - throw new \InvalidArgumentException('Status code must be an integer value.'); - } - } - private function assertStatusCodeRange(int $statusCode): void { if ($statusCode < 100 || $statusCode >= 600) { throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); } } + + private function assertReasonPhrase(string $reasonPhrase): void + { + if (!Rfc9112::isValidReasonPhrase($reasonPhrase)) { + throw new \InvalidArgumentException('Reason phrase must not contain invalid control characters.'); + } + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php b/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php index 6cc2ec08..d6cc92ae 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php +++ b/libs/vendor/guzzlehttp/psr7/src/Rfc3986.php @@ -5,14 +5,21 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; /** - * @internal + * Syntax predicates and canonicalization helpers for the URI grammar defined + * by RFC 3986. */ final class Rfc3986 { + private function __construct() + { + } + /** * Sub-delims for use in a regex. * * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 + * + * @internal */ public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; @@ -20,6 +27,335 @@ final class Rfc3986 * Unreserved characters for use in a regex. * * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + * + * @internal */ public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * The two hex digits of a percent-encoded octet (the "3A" in "%3A"), for use in a regex. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 + * + * @internal + */ + public const HEX_OCTET = '[A-Fa-f0-9]{2}'; + + /** + * Whether the string is a valid URI scheme. + * + * Per RFC 3986 a scheme must start with a letter, followed by letters, + * digits, `+`, `-`, or `.`. The empty string is also accepted, since a URI + * reference may omit the scheme. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.1 + */ + public static function isValidScheme(string $scheme): bool + { + return $scheme === '' || preg_match('/^[A-Za-z][A-Za-z0-9.+-]*$/D', $scheme) === 1; + } + + /** + * Whether the string is a valid URI host. + * + * Per RFC 3986 the host is `IP-literal / IPv4address / reg-name`. An empty + * host is accepted, since the authority (and thus the host) may be empty. + * Bracketed values are validated as IPv6 / IPvFuture literals; any other + * value is rejected if it contains control characters, whitespace, an + * authority or path delimiter (`/ ? # @ \`), an embedded colon denoting + * a port, a malformed percent-sequence, or a percent-encoded octet that + * decodes to one of those rejected bytes, to a bracket, or to `%` itself. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 + */ + public static function isValidHost(string $host): bool + { + if ($host === '') { + return true; + } + + $invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host); + + if ($invalidHost === false) { + return false; + } + + if ($invalidHost === 1) { + return false; + } + + if (str_contains($host, '[') || str_contains($host, ']')) { + return self::isValidIpLiteralHost($host); + } + + if (str_contains($host, ':')) { + return false; + } + + return !str_contains($host, '%') || self::hasValidHostPercentEncoding($host); + } + + /** + * Whether the string is a valid port number (0-65535). + * + * RFC 3986 defines the port as `*DIGIT`, which also permits an empty port + * and has no upper bound. This applies the stricter policy used throughout + * the library instead: the value must be a non-empty run of digits (leading + * zeros are accepted and normalized) that resolves to 0-65535. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3 + */ + public static function isValidPort(string $port): bool + { + if ($port === '' || !ctype_digit($port)) { + return false; + } + + $normalized = ltrim($port, '0'); + if ($normalized === '') { + return true; + } + + return strlen($normalized) <= 5 && (int) $normalized <= 0xFFFF; + } + + /** + * Returns the RFC 5952 canonical form of a valid IPv6 address. + * + * The address must be a valid textual IPv6 address without brackets and + * without a zone identifier, such as the inside of an IP-literal accepted + * by `isValidHost()`. Canonicalization lowercases the hexadecimal fields, + * suppresses leading zeros, and collapses the longest run of two or more + * zero fields (the leftmost on a tie) with `::`. Embedded dotted-decimal + * notation follows the rendering policy of BIND-derived `inet_ntop()` + * implementations and curl: exactly the IPv4-mapped (`::ffff:0:0/96`) and + * deprecated IPv4-compatible (`::/96`) layouts use it, while other + * embedded-IPv4 forms, including translated (NAT64) well-known prefixes + * such as `64:ff9b::/96` (RFC 6052), serialize in pure hexadecimal fields. + * + * Validation is strict and platform-independent: the address is checked + * against the RFC 3986 `IPv6address` grammar with PHP's + * `FILTER_VALIDATE_IP` filter and parsed in pure PHP, so spellings that + * only some platform parsers accept, such as the zero-padded dotted octets + * in `::ffff:192.168.001.001`, are rejected everywhere. + * + * @throws \InvalidArgumentException If the address cannot be parsed. + * + * @see https://datatracker.ietf.org/doc/html/rfc5952#section-4 + */ + public static function canonicalizeIpv6(string $address): string + { + $canonical = self::tryCanonicalizeIpv6($address); + if ($canonical === null) { + throw new \InvalidArgumentException('Invalid IPv6 address'); + } + + return $canonical; + } + + /** + * Returns the RFC 5952 canonical form of a valid IPv6 address, or null + * when the address cannot be parsed. + * + * @internal + */ + public static function tryCanonicalizeIpv6(string $address): ?string + { + // Platform parsers disagree on which spellings are valid: Apple libc + // and OpenBSD inet_pton() accept zero-padded dotted octets such as + // "::ffff:192.168.001.001", and macOS additionally accepts and silently + // strips zone IDs ("fe80::1%eth0"), while glibc, musl, and PHP's own + // filter reject both. Origin classification built on this helper must + // fail closed and must not vary by operating system, so the address is + // validated with the platform-independent FILTER_VALIDATE_IP filter and + // parsed in pure PHP; no OS parser is consulted. + if (\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) === false) { + return null; + } + + $words = self::parseIpv6Words($address); + if ($words === null) { + return null; + } + + // Find the longest run of two or more zero fields; ties keep the + // leftmost run per RFC 5952 section 4.2.3. + $bestStart = 0; + $bestLen = 0; + $start = -1; + foreach ($words as $i => $word) { + if ($word !== 0) { + $start = -1; + continue; + } + if ($start === -1) { + $start = $i; + } + if ($i - $start + 1 > $bestLen) { + $bestStart = $start; + $bestLen = $i - $start + 1; + } + } + if ($bestLen < 2) { + $bestLen = 0; + } + + // RFC 5952 section 5: embedded IPv4 notation for IPv4-mapped + // (::ffff:0:0/96) and IPv4-compatible (::/96) addresses, the same + // condition BIND-derived inet_ntop() and curl use. bestStart must be + // zero: a five or six field zero run elsewhere is not an IPv4 prefix. + $mixed = $bestStart === 0 + && ($bestLen === 6 || ($bestLen === 5 && $words[5] === 0xFFFF)); + + $groups = []; + for ($i = 0, $n = $mixed ? 6 : 8; $i < $n; ++$i) { + $groups[] = dechex($words[$i]); + } + if ($mixed) { + $groups[] = sprintf( + '%d.%d.%d.%d', + $words[6] >> 8, + $words[6] & 0xFF, + $words[7] >> 8, + $words[7] & 0xFF + ); + } + + if ($bestLen === 0) { + return implode(':', $groups); + } + + return implode(':', array_slice($groups, 0, $bestStart)).'::'.implode(':', array_slice($groups, $bestStart + $bestLen)); + } + + private static function hasValidHostPercentEncoding(string $host): bool + { + // Mirror of the raw reg-name policy above for percent-encoded octets: + // reject malformed sequences (RFC 3986 requires "%" HEXDIG HEXDIG) and + // octets that decode to bytes the raw grammar rejects - C0 controls, + // SP, DEL, the delimiters / ? # @ \ [ ], the port colon, and % itself. + // Octets decoding to any other byte (unreserved, sub-delims, and + // non-ASCII UTF-8 data) remain accepted. + $invalidEncoding = preg_match( + '/%(?!'.self::HEX_OCTET.')|%(?:[01][0-9A-Fa-f]|2[035F]|3[AF]|40|5[BCD]|7F)/i', + $host + ); + + return $invalidEncoding === 0; + } + + private static function isValidIpLiteralHost(string $host): bool + { + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return false; + } + + $address = substr($host, 1, -1); + if (\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false) { + return true; + } + + // RFC 6874 IPv6 zone identifiers are intentionally not supported here. + // Bracketed hosts are validated as IPv6 or IPvFuture only. + return preg_match('/^v[0-9a-f]+\.['.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1; + } + + /** + * Parses a textual IPv6 address into its eight 16-bit words, or null + * when the text is not a structurally valid address. + * + * The grammar enforced here is the RFC 3986 `IPv6address` rule: one to four + * hexadecimal digits per field, at most one `::` eliding one or more zero + * fields, and an optional dotted-decimal tail of four octets (0-255, no + * leading zeros) as the final 32 bits. FILTER_VALIDATE_IP accepts exactly + * this grammar, so the filter guard in tryCanonicalizeIpv6() and this + * parser always agree and the null paths here can only fail closed. + * + * @return list|null + */ + private static function parseIpv6Words(string $address): ?array + { + // A dotted-decimal tail is only valid as the final 32 bits, after the + // final colon. Rewrite it into its two hexadecimal fields so the + // remainder of the parse handles hexadecimal fields only. + $dot = strpos($address, '.'); + if ($dot !== false) { + $colon = strrpos($address, ':'); + if ($colon === false || $colon > $dot) { + return null; + } + $octets = explode('.', substr($address, $colon + 1)); + if (count($octets) !== 4) { + return null; + } + $bytes = []; + foreach ($octets as $octet) { + if ($octet === '' || strlen($octet) > 3 || !ctype_digit($octet)) { + return null; + } + if ($octet[0] === '0' && $octet !== '0') { + return null; + } + $byte = (int) $octet; + if ($byte > 255) { + return null; + } + $bytes[] = $byte; + } + $address = substr($address, 0, $colon + 1) + .dechex(($bytes[0] << 8) | $bytes[1]) + .':' + .dechex(($bytes[2] << 8) | $bytes[3]); + } + + $halves = explode('::', $address); + if (count($halves) > 2) { + return null; + } + + $head = self::parseHexFields($halves[0]); + if ($head === null) { + return null; + } + + if (count($halves) === 1) { + return count($head) === 8 ? $head : null; + } + + $tail = self::parseHexFields($halves[1]); + if ($tail === null) { + return null; + } + + // The "::" must elide at least one zero field. + $elided = 8 - count($head) - count($tail); + if ($elided < 1) { + return null; + } + + return array_merge($head, array_fill(0, $elided, 0), $tail); + } + + /** + * Parses a colon-separated run of 16-bit hexadecimal fields, or null + * when a field is empty, longer than four digits, or not hexadecimal. + * + * @return list|null + */ + private static function parseHexFields(string $fields): ?array + { + if ($fields === '') { + return []; + } + + $words = []; + foreach (explode(':', $fields) as $field) { + if ($field === '' || strlen($field) > 4 || !ctype_xdigit($field)) { + return null; + } + $words[] = intval($field, 16); + } + + return $words; + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/Rfc7230.php b/libs/vendor/guzzlehttp/psr7/src/Rfc7230.php deleted file mode 100644 index 6557f905..00000000 --- a/libs/vendor/guzzlehttp/psr7/src/Rfc7230.php +++ /dev/null @@ -1,112 +0,0 @@ -@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; - public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; - - /** - * @return array{0: string, 1: int|null}|null - */ - public static function parseHostHeader(string $authority): ?array - { - if ($authority === '') { - return null; - } - - $host = $authority; - $port = null; - - if ($authority[0] === '[') { - $closingBracket = strpos($authority, ']'); - if ($closingBracket === false) { - return null; - } - - $host = substr($authority, 0, $closingBracket + 1); - $remainder = substr($authority, $closingBracket + 1); - if ($remainder !== '') { - if ($remainder[0] !== ':') { - return null; - } - - $port = self::parseAuthorityPort(substr($remainder, 1)); - if ($port === null) { - return null; - } - } - } elseif (false !== ($colon = strpos($authority, ':'))) { - $host = substr($authority, 0, $colon); - $port = self::parseAuthorityPort(substr($authority, $colon + 1)); - if ($port === null) { - return null; - } - } - - if ($host === '' || !self::isValidHostHeaderHost($host)) { - return null; - } - - return [$host, $port]; - } - - private static function isValidHostHeaderHost(string $host): bool - { - $invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host); - - if ($invalidHost === false) { - return false; - } - - if ($invalidHost === 1) { - return false; - } - - if (strpos($host, '[') !== false || strpos($host, ']') !== false) { - if ($host[0] !== '[' || substr($host, -1) !== ']') { - return false; - } - - $address = substr($host, 1, -1); - - return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false - || preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1; - } - - return strpos($host, ':') === false; - } - - private static function parseAuthorityPort(string $port): ?int - { - if ($port === '' || !ctype_digit($port)) { - return null; - } - - $normalized = ltrim($port, '0'); - if ($normalized === '') { - return 0; - } - - if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) { - return null; - } - - return (int) $normalized; - } -} diff --git a/libs/vendor/guzzlehttp/psr7/src/Rfc9110.php b/libs/vendor/guzzlehttp/psr7/src/Rfc9110.php new file mode 100644 index 00000000..c93b1b2f --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/Rfc9110.php @@ -0,0 +1,41 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; + + public static function isValidProtocolVersion(string $version): bool + { + return preg_match('/^'.self::PROTOCOL_VERSION_PATTERN.'$/D', $version) === 1; + } + + public static function isValidRequestTarget(string $target): bool + { + return preg_match('/^'.self::REQUEST_TARGET_PATTERN.'$/D', $target) === 1; + } + + public static function isValidReasonPhrase(string $reasonPhrase): bool + { + return Rfc9110::isFieldValue($reasonPhrase); + } + + /** + * @return array{0: string, 1: int|null}|null + */ + public static function parseHostHeader(string $authority): ?array + { + if ($authority === '') { + return null; + } + + $host = $authority; + $port = null; + + if (str_starts_with($authority, '[')) { + $closingBracket = strpos($authority, ']'); + if ($closingBracket === false) { + return null; + } + + $host = substr($authority, 0, $closingBracket + 1); + $remainder = substr($authority, $closingBracket + 1); + if ($remainder !== '') { + if (!str_starts_with($remainder, ':')) { + return null; + } + + $port = self::parsePort(substr($remainder, 1)); + if ($port === null) { + return null; + } + } + } elseif (false !== ($colon = strpos($authority, ':'))) { + $host = substr($authority, 0, $colon); + $port = self::parsePort(substr($authority, $colon + 1)); + if ($port === null) { + return null; + } + } + + if ($host === '' || !Rfc3986::isValidHost($host)) { + return null; + } + + return [$host, $port]; + } + + public static function isAbsoluteFormRequestTarget(string $target): bool + { + return preg_match('/^[A-Za-z][A-Za-z0-9+.-]*:\/\//D', $target) === 1; + } + + public static function isAsteriskFormRequestTarget(string $method, string $target): bool + { + return $method === 'OPTIONS' && $target === '*'; + } + + public static function isConnectAuthorityFormRequestTarget(string $method, string $target): bool + { + return $method === 'CONNECT' && strpbrk($target, '/?#') === false; + } + + public static function parsePort(string $port): ?int + { + if (!Rfc3986::isValidPort($port)) { + return null; + } + + // A zero port is valid per RFC 3986 but meaningless for an HTTP + // authority, so reject it on top of the generic syntax check. + $parsed = (int) ltrim($port, '0'); + + return $parsed === 0 ? null : $parsed; + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php b/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php index 30b7a898..adf17627 100644 --- a/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php +++ b/libs/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -26,35 +26,20 @@ use Psr\Http\Message\UriInterface; */ class ServerRequest extends Request implements ServerRequestInterface { - /** - * @var array - */ - private $attributes = []; + private array $attributes = []; - /** - * @var array - */ - private $cookieParams = []; + private array $cookieParams = []; /** * @var array|object|null */ private $parsedBody; - /** - * @var array - */ - private $queryParams = []; + private array $queryParams = []; - /** - * @var array - */ - private $serverParams; + private array $serverParams; - /** - * @var array - */ - private $uploadedFiles = []; + private array $uploadedFiles = []; /** * @param string $method HTTP method @@ -70,6 +55,7 @@ class ServerRequest extends Request implements ServerRequestInterface array $headers = [], $body = null, string $version = '1.1', + #[\SensitiveParameter] array $serverParams = [] ) { $this->serverParams = $serverParams; @@ -86,73 +72,7 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function normalizeFiles(array $files): array { - $normalized = []; - - foreach ($files as $key => $value) { - if ($value instanceof UploadedFileInterface) { - $normalized[$key] = $value; - } elseif (is_array($value) && isset($value['tmp_name'])) { - $normalized[$key] = self::createUploadedFileFromSpec($value); - } elseif (is_array($value)) { - $normalized[$key] = self::normalizeFiles($value); - continue; - } else { - throw new InvalidArgumentException('Invalid value in files specification'); - } - } - - return $normalized; - } - - /** - * Create and return an UploadedFile instance from a $_FILES specification. - * - * If the specification represents an array of values, this method will - * delegate to normalizeNestedFileSpec() and return that return value. - * - * @param array $value $_FILES struct - * - * @return UploadedFileInterface|UploadedFileInterface[] - */ - private static function createUploadedFileFromSpec(array $value) - { - if (is_array($value['tmp_name'])) { - return self::normalizeNestedFileSpec($value); - } - - return new UploadedFile( - $value['tmp_name'], - (int) $value['size'], - (int) $value['error'], - $value['name'], - $value['type'] - ); - } - - /** - * Normalize an array of file specifications. - * - * Loops through all nested files and returns a normalized array of - * UploadedFileInterface instances. - * - * @return UploadedFileInterface[] - */ - private static function normalizeNestedFileSpec(array $files = []): array - { - $normalizedFiles = []; - - foreach (array_keys($files['tmp_name']) as $key) { - $spec = [ - 'tmp_name' => $files['tmp_name'][$key], - 'size' => $files['size'][$key] ?? null, - 'error' => $files['error'][$key] ?? null, - 'name' => $files['name'][$key] ?? null, - 'type' => $files['type'][$key] ?? null, - ]; - $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); - } - - return $normalizedFiles; + return UploadedFileNormalizer::normalize($files); } /** @@ -165,79 +85,20 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function fromGlobals(): ServerRequestInterface { - $method = Utils::asciiToUpper(self::getServerParam('REQUEST_METHOD') ?? 'GET'); - $headers = self::removeInvalidHostHeader(self::getAllHeaders()); - $uri = self::getUriFromGlobals(); - $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); - $serverProtocol = self::getServerParam('SERVER_PROTOCOL'); - $protocol = $serverProtocol !== null ? str_replace('HTTP/', '', $serverProtocol) : '1.1'; + return ServerRequestGlobalsFactory::fromArrays( + $_SERVER, + $_GET, + $_POST, + $_COOKIE, + $_FILES, + static function () { + if (!\function_exists('apache_request_headers')) { + return false; + } - $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); - - return $serverRequest - ->withCookieParams($_COOKIE) - ->withQueryParams($_GET) - ->withParsedBody($_POST) - ->withUploadedFiles(self::normalizeFiles($_FILES)); - } - - /** - * @return array - */ - private static function getAllHeaders(): array - { - return self::normalizeHeaderValues(getallheaders()); - } - - /** - * @param array $headers - * - * @return array - */ - private static function normalizeHeaderValues(array $headers): array - { - $normalized = []; - - foreach ($headers as $name => $value) { - if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { - $normalized[$name] = (string) $value; + return \apache_request_headers(); } - } - - return $normalized; - } - - private static function getServerParam(string $key): ?string - { - return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null; - } - - /** - * @param array $headers - * - * @return array - */ - private static function removeInvalidHostHeader(array $headers): array - { - foreach ($headers as $name => $value) { - if (Utils::asciiToLower((string) $name) !== 'host') { - continue; - } - - if (Rfc7230::parseHostHeader($value) === null) { - unset($headers[$name]); - } - } - - return $headers; - } - - /** - * @return array{0: string|null, 1: int|null} - */ - private static function extractHostAndPortFromAuthority(string $authority): array - { - return Rfc7230::parseHostHeader($authority) ?? [null, null]; + ); } /** @@ -245,51 +106,7 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function getUriFromGlobals(): UriInterface { - $uri = new Uri(''); - - $https = self::getServerParam('HTTPS'); - $uri = $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http'); - - $hasPort = false; - $authority = self::getServerParam('HTTP_HOST'); - if ($authority !== null) { - [$host, $port] = self::extractHostAndPortFromAuthority($authority); - if ($host !== null) { - $uri = $uri->withHost($host); - } - - if ($port !== null) { - $hasPort = true; - $uri = $uri->withPort($port); - } - } elseif (($serverName = self::getServerParam('SERVER_NAME')) !== null) { - $uri = $uri->withHost($serverName); - } elseif (($serverAddr = self::getServerParam('SERVER_ADDR')) !== null) { - $uri = $uri->withHost($serverAddr); - } - - $serverPort = self::getServerParam('SERVER_PORT'); - if (!$hasPort && $serverPort !== null && preg_match('/^[+-]?\d+$/D', $serverPort) === 1) { - $uri = $uri->withPort((int) $serverPort); - } - - $hasQuery = false; - $requestUri = self::getServerParam('REQUEST_URI'); - if ($requestUri !== null) { - $requestUriParts = explode('?', $requestUri, 2); - $uri = $uri->withPath($requestUriParts[0]); - if (isset($requestUriParts[1])) { - $hasQuery = true; - $uri = $uri->withQuery($requestUriParts[1]); - } - } - - $queryString = self::getServerParam('QUERY_STRING'); - if (!$hasQuery && $queryString !== null) { - $uri = $uri->withQuery($queryString); - } - - return $uri; + return ServerRequestGlobalsFactory::getUriFromServerParams($_SERVER); } public function getServerParams(): array @@ -304,12 +121,10 @@ class ServerRequest extends Request implements ServerRequestInterface public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { - $invalidUploadedFileFound = false; - $invalidUploadedFile = null; $stack = [$uploadedFiles]; - while ($stack !== []) { - foreach (\array_pop($stack) as $uploadedFile) { + for ($i = 0; $i < \count($stack); ++$i) { + foreach ($stack[$i] as $uploadedFile) { if ($uploadedFile instanceof UploadedFileInterface) { continue; } @@ -319,22 +134,13 @@ class ServerRequest extends Request implements ServerRequestInterface continue; } - $invalidUploadedFileFound = true; - $invalidUploadedFile = $uploadedFile; - - break 2; + throw new InvalidArgumentException(sprintf( + 'Invalid uploaded file tree; expected UploadedFileInterface instances but %s provided.', + \get_debug_type($uploadedFile) + )); } } - if ($invalidUploadedFileFound) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s inside ServerRequestInterface::withUploadedFiles() is deprecated; guzzlehttp/psr7 3.0 requires an UploadedFileInterface[] tree.', - \get_debug_type($invalidUploadedFile) - ); - } - $new = clone $this; $new->uploadedFiles = $uploadedFiles; @@ -346,8 +152,10 @@ class ServerRequest extends Request implements ServerRequestInterface return $this->cookieParams; } - public function withCookieParams(array $cookies): ServerRequestInterface - { + public function withCookieParams( + #[\SensitiveParameter] + array $cookies + ): ServerRequestInterface { $new = clone $this; $new->cookieParams = $cookies; @@ -378,12 +186,7 @@ class ServerRequest extends Request implements ServerRequestInterface public function withParsedBody($data): ServerRequestInterface { if ($data !== null && !\is_array($data) && !\is_object($data)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ServerRequestInterface::withParsedBody() is deprecated; guzzlehttp/psr7 3.0 requires array|object|null.', - \get_debug_type($data) - ); + throw new InvalidArgumentException('Parsed body must be an array, object, or null.'); } $new = clone $this; @@ -400,58 +203,31 @@ class ServerRequest extends Request implements ServerRequestInterface /** * @return mixed */ - public function getAttribute($attribute, $default = null) + public function getAttribute(string $name, $default = null) { - if (!\is_string($attribute)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ServerRequestInterface::getAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', - \get_debug_type($attribute) - ); - } - - if (false === array_key_exists($attribute, $this->attributes)) { + if (false === array_key_exists($name, $this->attributes)) { return $default; } - return $this->attributes[$attribute]; + return $this->attributes[$name]; } - public function withAttribute($attribute, $value): ServerRequestInterface + public function withAttribute(string $name, $value): ServerRequestInterface { - if (!\is_string($attribute)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ServerRequestInterface::withAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', - \get_debug_type($attribute) - ); - } - $new = clone $this; - $new->attributes[$attribute] = $value; + $new->attributes[$name] = $value; return $new; } - public function withoutAttribute($attribute): ServerRequestInterface + public function withoutAttribute(string $name): ServerRequestInterface { - if (!\is_string($attribute)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to ServerRequestInterface::withoutAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', - \get_debug_type($attribute) - ); - } - - if (false === array_key_exists($attribute, $this->attributes)) { + if (false === array_key_exists($name, $this->attributes)) { return $this; } $new = clone $this; - unset($new->attributes[$attribute]); + unset($new->attributes[$name]); return $new; } diff --git a/libs/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php b/libs/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php new file mode 100644 index 00000000..977420fc --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php @@ -0,0 +1,504 @@ + $server Typically the $_SERVER superglobal + * @param array $query Typically the $_GET superglobal + * @param array $post Typically the $_POST superglobal + * @param array $cookies Typically the $_COOKIE superglobal + * @param array $files Typically the $_FILES superglobal + * @param (callable(): mixed)|null $headerProvider + */ + public static function fromArrays( + #[\SensitiveParameter] + array $server, + array $query, + array $post, + #[\SensitiveParameter] + array $cookies, + array $files, + ?callable $headerProvider = null + ): ServerRequestInterface { + $method = self::getRequestMethodFromServer($server); + $headers = self::removeInvalidHostHeader(self::getAllHeaders($server, $headerProvider)); + [$uri, $requestTarget] = self::getUriAndRequestTargetFromServer($server, $method); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, self::getProtocolFromServer($server), $server); + if ($requestTarget !== null) { + /** @var ServerRequestInterface $serverRequest */ + $serverRequest = $serverRequest->withRequestTarget($requestTarget); + } + + return $serverRequest + ->withCookieParams($cookies) + ->withQueryParams($query) + ->withParsedBody($post) + ->withUploadedFiles(UploadedFileNormalizer::normalize($files)); + } + + /** + * @param array $server Typically the $_SERVER superglobal + */ + public static function getUriFromServerParams( + #[\SensitiveParameter] + array $server + ): UriInterface { + $method = self::getRequestMethodFromServer($server); + + return self::getUriAndRequestTargetFromServer($server, $method)[0]; + } + + /** + * @param array $server + * @param (callable(): mixed)|null $headerProvider + * + * @return array + */ + private static function getAllHeaders( + #[\SensitiveParameter] + array $server, + ?callable $headerProvider + ): array { + $headers = $headerProvider !== null ? $headerProvider() : false; + + if (!is_array($headers)) { + $headers = self::getHeadersFromServer($server); + } + + return self::normalizeHeaderValues($headers); + } + + /** + * @param array $headers + * + * @return array + */ + private static function normalizeHeaderValues( + #[\SensitiveParameter] + array $headers + ): array { + $normalized = []; + + foreach ($headers as $name => $value) { + if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { + $normalized[$name] = (string) $value; + } + } + + return $normalized; + } + + /** + * @param array $server Typically the $_SERVER superglobal + * + * @return array + */ + private static function getHeadersFromServer(array $server): array + { + $headers = []; + + $copyServer = [ + 'CONTENT_TYPE' => 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ]; + + foreach ($server as $key => $value) { + if (!is_string($key) || !is_string($value)) { + continue; + } + + if (str_starts_with($key, 'HTTP_')) { + $header = substr($key, 5); + + if (isset($copyServer[$header], $server[$header]) && is_string($server[$header])) { + continue; + } + + $parts = explode(' ', Utils::asciiToLower(str_replace('_', ' ', $header))); + foreach ($parts as $i => $part) { + $parts[$i] = Utils::asciiUcFirst($part); + } + $header = implode('-', $parts); + $headers[$header] = $value; + + continue; + } + + if (isset($copyServer[$key])) { + $headers[$copyServer[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($server['REDIRECT_HTTP_AUTHORIZATION']) && is_string($server['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $server['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($server['PHP_AUTH_USER']) && is_string($server['PHP_AUTH_USER'])) { + $password = isset($server['PHP_AUTH_PW']) && is_string($server['PHP_AUTH_PW']) + ? $server['PHP_AUTH_PW'] + : ''; + + $headers['Authorization'] = 'Basic '.base64_encode($server['PHP_AUTH_USER'].':'.$password); + } elseif (isset($server['PHP_AUTH_DIGEST']) && is_string($server['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $server['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + + /** + * @param array $headers + * + * @return array + */ + private static function removeInvalidHostHeader(array $headers): array + { + foreach ($headers as $name => $value) { + if (Utils::asciiToLower((string) $name) !== 'host') { + continue; + } + + [$host] = self::extractHostAndPortFromAuthority($value); + if ($host === null) { + unset($headers[$name]); + } + } + + return $headers; + } + + /** + * @param array $server + */ + private static function getServerParam(array $server, string $key): ?string + { + return isset($server[$key]) && is_string($server[$key]) ? $server[$key] : null; + } + + /** + * @param array $server + */ + private static function getRequestMethodFromServer(array $server): string + { + return Utils::asciiToUpper(self::getServerParam($server, 'REQUEST_METHOD') ?? 'GET'); + } + + /** + * @param array $server + */ + private static function getProtocolFromServer(array $server): string + { + $serverProtocol = self::getServerParam($server, 'SERVER_PROTOCOL'); + if ($serverProtocol === null) { + return '1.1'; + } + + return str_starts_with($serverProtocol, 'HTTP/') ? substr($serverProtocol, 5) : $serverProtocol; + } + + /** + * @return array{0: string|null, 1: int|null} + */ + private static function extractHostAndPortFromAuthority(string $authority): array + { + return Rfc9112::parseHostHeader($authority) ?? [null, null]; + } + + private static function parseServerPort(string $port): int + { + $parsed = Rfc9112::parsePort($port); + if ($parsed === null) { + throw new InvalidArgumentException('Invalid SERVER_PORT; expected an integer between 1 and 65535.'); + } + + return $parsed; + } + + private static function withHostFromServer(UriInterface $uri, ?string $host): ?UriInterface + { + if ($host === null) { + return null; + } + + try { + return $uri->withHost($host); + } catch (InvalidArgumentException $e) { + return null; + } + } + + /** + * @param array $server + */ + private static function getUriWithSchemeFromServer(array $server): UriInterface + { + $uri = new Uri(''); + + $https = self::getServerParam($server, 'HTTPS'); + + return $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http'); + } + + /** + * @param array $server + */ + private static function getAuthorityUriFromServer( + #[\SensitiveParameter] + array $server + ): UriInterface { + $uri = self::getUriWithSchemeFromServer($server); + + $hasPort = false; + $hasHost = false; + $authority = self::getServerParam($server, 'HTTP_HOST'); + if ($authority !== null) { + [$host, $port] = self::extractHostAndPortFromAuthority($authority); + if ($host !== null) { + $hostUri = self::withHostFromServer($uri, $host); + if ($hostUri !== null) { + $uri = $hostUri; + $hasHost = true; + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } + } + } + + foreach (['SERVER_NAME', 'SERVER_ADDR'] as $serverParam) { + if ($hasHost) { + continue; + } + + $hostUri = self::withHostFromServer($uri, self::getServerParam($server, $serverParam)); + if ($hostUri !== null) { + $uri = $hostUri; + $hasHost = true; + } + } + + $serverPort = self::getServerParam($server, 'SERVER_PORT'); + if (!$hasPort && $serverPort !== null) { + $uri = $uri->withPort(self::parseServerPort($serverPort)); + } + + return $uri; + } + + /** + * @param array $server + * + * @return array{0: UriInterface, 1: string|null} + */ + private static function getUriAndRequestTargetFromServer( + #[\SensitiveParameter] + array $server, + string $method + ): array { + $requestUri = self::getServerParam($server, 'REQUEST_URI'); + $queryString = self::getServerParam($server, 'QUERY_STRING'); + + if ($requestUri !== null) { + $connectAuthority = self::parseConnectAuthorityFormRequestTarget($method, $requestUri); + if ($connectAuthority !== null) { + [$host, $port] = $connectAuthority; + $uri = self::getUriWithSchemeFromServer($server); + + return [ + $uri->withHost($host)->withPort($port)->withPath('')->withQuery(''), + $requestUri, + ]; + } + + $absoluteForm = self::getAbsoluteFormUriAndRequestTarget($requestUri, $queryString); + if ($absoluteForm !== null) { + return $absoluteForm; + } + } + + $uri = self::getAuthorityUriFromServer($server); + + if ($requestUri === null) { + if ($queryString !== null) { + $uri = $uri->withQuery($queryString); + } + + return [$uri, null]; + } + + if (Rfc9112::isAsteriskFormRequestTarget($method, $requestUri)) { + return [$uri->withPath('')->withQuery(''), '*']; + } + + [$path, $query, $hasQuery] = self::splitRequestTargetQuery($requestUri); + $uri = $uri->withPath(self::normalizeOriginFormPathFromServer($path)); + + if ($hasQuery) { + $uri = $uri->withQuery($query); + } elseif ($queryString !== null) { + $uri = $uri->withQuery($queryString); + } + + return [$uri, null]; + } + + /** + * @return array{0: UriInterface, 1: string}|null + */ + private static function getAbsoluteFormUriAndRequestTarget( + #[\SensitiveParameter] + string $requestUri, + ?string $queryString + ): ?array { + if (!Rfc9112::isAbsoluteFormRequestTarget($requestUri)) { + return null; + } + + try { + $targetUri = (new Uri($requestUri))->withFragment(''); + } catch (InvalidArgumentException $e) { + return null; + } + + if ($targetUri->getHost() === '') { + return null; + } + + $requestTarget = self::removeRequestTargetFragment($requestUri); + $requestTargetWithoutUserInfo = self::removeUserInfoFromAbsoluteFormRequestTarget($requestTarget); + if ($requestTargetWithoutUserInfo !== $requestTarget) { + $targetUri = $targetUri->withUserInfo(''); + $requestTarget = $requestTargetWithoutUserInfo; + } + + if (!str_contains($requestTarget, '?') && $queryString !== null && $queryString !== '') { + $targetUri = $targetUri->withQuery($queryString); + $requestTarget .= '?'.$queryString; + } + + // Preserve the received absolute-form target unless it cannot be used as + // a PSR-7 request target without normalization. + $normalizeRequestTarget = !Rfc9112::isValidRequestTarget($requestTarget) + || self::hasEmptyPortInAbsoluteFormRequestTarget($requestTarget); + + return [$targetUri, $normalizeRequestTarget ? (string) $targetUri : $requestTarget]; + } + + private static function removeUserInfoFromAbsoluteFormRequestTarget(string $target): string + { + $authorityStart = strpos($target, '://'); + if ($authorityStart === false) { + return $target; + } + + $authorityStart += 3; + $authorityLength = strcspn($target, '/?#', $authorityStart); + $authority = substr($target, $authorityStart, $authorityLength); + if ($authority === '') { + return $target; + } + + $lastAt = strrpos($authority, '@'); + if ($lastAt === false) { + return $target; + } + + $authorityEnd = $authorityStart + $authorityLength; + + return substr($target, 0, $authorityStart) + .substr($authority, $lastAt + 1) + .substr($target, $authorityEnd); + } + + private static function hasEmptyPortInAbsoluteFormRequestTarget(string $target): bool + { + $authorityStart = strpos($target, '://'); + if ($authorityStart === false) { + return false; + } + + $authorityStart += 3; + $authority = substr($target, $authorityStart, strcspn($target, '/?#', $authorityStart)); + if ($authority === '') { + return false; + } + + $lastAt = strrpos($authority, '@'); + if ($lastAt !== false) { + $authority = substr($authority, $lastAt + 1); + } + + if ($authority === '') { + return false; + } + + if (str_starts_with($authority, '[')) { + $closingBracket = strpos($authority, ']'); + + return $closingBracket !== false && substr($authority, $closingBracket + 1) === ':'; + } + + return str_ends_with($authority, ':'); + } + + /** + * @return array{0: string, 1: int}|null + */ + private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?array + { + if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) { + return null; + } + + [$host, $port] = self::extractHostAndPortFromAuthority($target); + if ($host === null || $port === null) { + return null; + } + + return [$host, $port]; + } + + private static function removeRequestTargetFragment(string $target): string + { + return explode('#', $target, 2)[0]; + } + + /** + * @return array{0: string, 1: string, 2: bool} + */ + private static function splitRequestTargetQuery(string $target): array + { + $parts = explode('?', $target, 2); + + return [$parts[0], $parts[1] ?? '', isset($parts[1])]; + } + + private static function normalizeOriginFormPathFromServer(string $path): string + { + if ($path === '' || str_starts_with($path, '/')) { + return $path; + } + + return '/'.$path; + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/Stream.php b/libs/vendor/guzzlehttp/psr7/src/Stream.php index 7f3a109c..4235993a 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Stream.php +++ b/libs/vendor/guzzlehttp/psr7/src/Stream.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\StreamInterface; /** @@ -11,27 +12,17 @@ use Psr\Http\Message\StreamInterface; */ class Stream implements StreamInterface { - /** - * @see https://www.php.net/manual/en/function.fopen.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 WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/'; + use NonSerializableStreamTrait; /** @var resource */ private $stream; - /** @var int|null */ - private $size; - /** @var bool */ - private $seekable; - /** @var bool */ - private $readable; - /** @var bool */ - private $writable; - /** @var string|null */ - private $uri; + private ?int $size = null; + private bool $seekable; + private bool $readable; + private bool $writable; + private ?string $uri = null; /** @var mixed[] */ - private $customMetadata; + private array $customMetadata; /** * This constructor accepts an associative array of options. @@ -53,16 +44,14 @@ class Stream implements StreamInterface throw new \InvalidArgumentException('Stream must be a resource'); } - if (isset($options['size'])) { - $this->size = $options['size']; - } + $this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size'); $this->customMetadata = $options['metadata'] ?? []; $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; - $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']); - $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->readable = self::isReadableMode($meta['mode']); + $this->writable = self::isWritableMode($meta['mode']); $this->uri = $meta['uri'] ?? null; } @@ -76,20 +65,11 @@ class Stream implements StreamInterface public function __toString(): string { - try { - if ($this->isSeekable()) { - $this->seek(0); - } - - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - - return ''; + if ($this->isSeekable()) { + $this->seek(0); } + + return $this->getContents(); } public function getContents(): string @@ -145,13 +125,13 @@ class Stream implements StreamInterface } $stats = fstat($this->stream); - if (is_array($stats) && isset($stats['size'])) { - $this->size = $stats['size']; - - return $this->size; + if ($stats === false) { + return null; } - return null; + $this->size = Integers::assertEngineInteger($stats['size'], 'Stream size'); + + return $this->size; } public function isReadable(): bool @@ -185,12 +165,16 @@ class Stream implements StreamInterface } $result = ftell($this->stream); - if ($result === false) { throw new \RuntimeException('Unable to determine stream position'); } - return $result; + $position = Integers::assertEngineInteger($result, 'Stream position'); + if ($position === null) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $position; } public function rewind(): void @@ -198,28 +182,8 @@ class Stream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - - $whence = (int) $whence; - if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } @@ -232,17 +196,8 @@ class Stream implements StreamInterface } } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); - } - if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } @@ -259,28 +214,33 @@ class Stream implements StreamInterface try { $string = fread($this->stream, $length); + } catch (TimeoutException $e) { + throw $e; } catch (\Exception $e) { + if ($this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out', 0, $e); + } + throw new \RuntimeException('Unable to read from stream', 0, $e); } if (false === $string) { + if ($this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + throw new \RuntimeException('Unable to read from stream'); } + if ($string === '' && $this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + return $string; } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } @@ -288,34 +248,48 @@ class Stream implements StreamInterface throw new \RuntimeException('Cannot write to a non-writable stream'); } + if ($string === '') { + return 0; + } + // We can't know the size after writing anything $this->size = null; - $result = fwrite($this->stream, $string); + + try { + $result = fwrite($this->stream, $string); + } catch (TimeoutException $e) { + throw $e; + } catch (\Exception $e) { + if ($this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out', 0, $e); + } + + throw new \RuntimeException('Unable to write to stream', 0, $e); + } if ($result === false) { + if ($this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out'); + } + throw new \RuntimeException('Unable to write to stream'); } + if ($result === 0 && $this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out'); + } + return $result; } /** * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); - } - if (!isset($this->stream)) { - return $key ? null : []; - } elseif (!$key) { + return $key === null ? [] : null; + } elseif ($key === null) { return $this->customMetadata + stream_get_meta_data($this->stream); } elseif (isset($this->customMetadata[$key])) { return $this->customMetadata[$key]; @@ -325,4 +299,36 @@ class Stream implements StreamInterface return $meta[$key] ?? null; } + + /** + * @see https://www.php.net/manual/en/function.fopen.php + * @see https://www.php.net/manual/en/function.gzopen.php + */ + private static function isReadableMode(string $mode): bool + { + return str_starts_with($mode, 'r') || str_contains($mode, '+'); + } + + /** + * @see https://www.php.net/manual/en/function.fopen.php + * @see https://www.php.net/manual/en/function.gzopen.php + */ + private static function isWritableMode(string $mode): bool + { + return str_starts_with($mode, 'a') + || str_starts_with($mode, 'w') + || str_starts_with($mode, 'x') + || str_starts_with($mode, 'c') + || str_contains($mode, '+'); + } + + private function timedOut(): bool + { + return StreamTimeout::isResourceReadTimedOut($this->stream); + } + + private function writeTimedOut(): bool + { + return StreamTimeout::isResourceWriteTimedOut($this->stream); + } } diff --git a/libs/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/libs/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php index d31e69dc..7b649a82 100644 --- a/libs/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php +++ b/libs/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -24,10 +24,8 @@ trait StreamDecoratorTrait /** * Magic method used to create a new stream if streams are not added in * the constructor of a decorator (e.g., LazyOpenStream). - * - * @return StreamInterface */ - public function __get(string $name) + public function __get(string $name): StreamInterface { if ($name === 'stream') { $this->stream = $this->createStream(); @@ -35,25 +33,16 @@ trait StreamDecoratorTrait return $this->stream; } - throw new \UnexpectedValueException("$name not found on class"); + throw new \UnexpectedValueException(\sprintf('%s not found on class', DiagnosticValue::escape($name))); } public function __toString(): string { - try { - if ($this->isSeekable()) { - $this->seek(0); - } - - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - - return ''; + if ($this->isSeekable()) { + $this->seek(0); } + + return $this->getContents(); } public function getContents(): string @@ -84,17 +73,8 @@ trait StreamDecoratorTrait /** * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if ($key !== null && !\is_string($key)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', - \get_debug_type($key) - ); - } - return $this->stream->getMetadata($key); } @@ -138,54 +118,22 @@ trait StreamDecoratorTrait $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - if (!\is_int($offset)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', - \get_debug_type($offset) - ); - } - - if (!\is_int($whence)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', - \get_debug_type($whence) - ); - } - $this->stream->seek($offset, $whence); } - public function read($length): string + public function read(int $length): string { - if (!\is_int($length)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', - \get_debug_type($length) - ); + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } return $this->stream->read($length); } - public function write($string): int + public function write(string $string): int { - if (!\is_string($string)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', - \get_debug_type($string) - ); - } - return $this->stream->write($string); } diff --git a/libs/vendor/guzzlehttp/psr7/src/StreamTimeout.php b/libs/vendor/guzzlehttp/psr7/src/StreamTimeout.php new file mode 100644 index 00000000..84a6b364 --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/StreamTimeout.php @@ -0,0 +1,106 @@ +read($length); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + self::throwIfReadTimedOut($stream, $timeoutMessage, $e); + + throw $e; + } + + if ($buffer === '') { + self::throwIfReadTimedOut($stream, $timeoutMessage); + } + + return $buffer; + } + + public static function throwIfReadTimedOut( + StreamInterface $stream, + string $message, + ?\Throwable $previous = null + ): void { + if (self::isReadTimedOut($stream)) { + throw new TimeoutException($message, 0, $previous); + } + } + + public static function throwIfWriteTimedOut(StreamInterface $stream, ?\Throwable $previous = null): void + { + if (self::isWriteTimedOut($stream)) { + throw new TimeoutException('Unable to write to stream: timed out', 0, $previous); + } + } + + public static function isReadTimedOut(StreamInterface $stream): bool + { + try { + if ($stream->getMetadata('timed_out') !== true) { + return false; + } + + return !$stream->eof(); + } catch (\Throwable $e) { + return false; + } + } + + public static function isWriteTimedOut(StreamInterface $stream): bool + { + try { + return $stream->getMetadata('timed_out') === true; + } catch (\Throwable $e) { + return false; + } + } + + /** + * @param resource $resource + */ + public static function isResourceReadTimedOut($resource): bool + { + try { + /** @var array $metadata */ + $metadata = stream_get_meta_data($resource); + + return ($metadata['timed_out'] ?? false) === true && !feof($resource); + } catch (\Throwable $e) { + return false; + } + } + + /** + * @param resource $resource + */ + public static function isResourceWriteTimedOut($resource): bool + { + try { + /** @var array $metadata */ + $metadata = stream_get_meta_data($resource); + + return ($metadata['timed_out'] ?? false) === true; + } catch (\Throwable $e) { + return false; + } + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php index cd20764f..0d6c9e1d 100644 --- a/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php +++ b/libs/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -16,11 +16,10 @@ final class StreamWrapper /** @var resource */ public $context; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** @var string r, r+, or w */ - private $mode; + private string $mode; /** * Returns a resource representing the stream. @@ -78,40 +77,67 @@ final class StreamWrapper public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool { $options = stream_context_get_options($this->context); + $stream = $options['guzzle']['stream'] ?? null; - if (!isset($options['guzzle']['stream'])) { + if (!$stream instanceof StreamInterface) { return false; } $this->mode = $mode; - $this->stream = $options['guzzle']['stream']; + $this->stream = $stream; return true; } - public function stream_read(int $count): string + /** + * @return string|false + */ + public function stream_read(int $count) { - return $this->stream->read($count); + try { + return $this->stream->read($count); + } catch (\RuntimeException $e) { + return false; + } } public function stream_write(string $data): int { - return $this->stream->write($data); + try { + return $this->stream->write($data); + } catch (\RuntimeException $e) { + return -1; + } } - public function stream_tell(): int + /** + * @return int|false + */ + public function stream_tell() { - return $this->stream->tell(); + try { + return $this->stream->tell(); + } catch (\RuntimeException $e) { + return false; + } } public function stream_eof(): bool { - return $this->stream->eof(); + try { + return $this->stream->eof(); + } catch (\RuntimeException $e) { + return true; + } } public function stream_seek(int $offset, int $whence): bool { - $this->stream->seek($offset, $whence); + try { + $this->stream->seek($offset, $whence); + } catch (\RuntimeException $e) { + return false; + } return true; } @@ -121,8 +147,12 @@ final class StreamWrapper */ public function stream_cast(int $cast_as) { - $stream = clone $this->stream; - $resource = $stream->detach(); + try { + $stream = clone $this->stream; + $resource = $stream->detach(); + } catch (\RuntimeException $e) { + return false; + } return $resource ?? false; } @@ -146,7 +176,13 @@ final class StreamWrapper */ public function stream_stat() { - if ($this->stream->getSize() === null) { + try { + $size = $this->stream->getSize(); + } catch (\RuntimeException $e) { + return false; + } + + if ($size === null) { return false; } @@ -161,12 +197,12 @@ final class StreamWrapper return [ 'dev' => 0, 'ino' => 0, - 'mode' => $modeMap[$this->mode], + 'mode' => $modeMap[$this->mode] ?? 0, 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, - 'size' => $this->stream->getSize() ?: 0, + 'size' => $size, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, diff --git a/libs/vendor/guzzlehttp/psr7/src/UploadedFile.php b/libs/vendor/guzzlehttp/psr7/src/UploadedFile.php index d9b779f1..188bf52d 100644 --- a/libs/vendor/guzzlehttp/psr7/src/UploadedFile.php +++ b/libs/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -22,40 +22,19 @@ class UploadedFile implements UploadedFileInterface UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION', ]; - /** - * @var string|null - */ - private $clientFilename; + private ?string $clientFilename; - /** - * @var string|null - */ - private $clientMediaType; + private ?string $clientMediaType; - /** - * @var int - */ - private $error; + private int $error; - /** - * @var string|null - */ - private $file; + private ?string $file = null; - /** - * @var bool - */ - private $moved = false; + private bool $moved = false; - /** - * @var int|null - */ - private $size; + private ?int $size; - /** - * @var StreamInterface|null - */ - private $stream; + private ?StreamInterface $stream = null; /** * @param StreamInterface|string|resource $streamOrFile @@ -68,7 +47,7 @@ class UploadedFile implements UploadedFileInterface ?string $clientMediaType = null ) { $this->setError($errorStatus); - $this->size = $size; + $this->size = Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size'); $this->clientFilename = $clientFilename; $this->clientMediaType = $clientMediaType; @@ -113,11 +92,6 @@ class UploadedFile implements UploadedFileInterface $this->error = $error; } - private static function isStringNotEmpty($param): bool - { - return is_string($param) && false === empty($param); - } - /** * Return true if there is no upload error */ @@ -159,11 +133,11 @@ class UploadedFile implements UploadedFileInterface return new LazyOpenStream($file, 'r+'); } - public function moveTo($targetPath): void + public function moveTo(string $targetPath): void { $this->validateActive(); - if (false === self::isStringNotEmpty($targetPath)) { + if ($targetPath === '') { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); @@ -174,8 +148,13 @@ class UploadedFile implements UploadedFileInterface ? rename($this->file, $targetPath) : move_uploaded_file($this->file, $targetPath); } else { + $stream = $this->getStream(); + if ($stream->isSeekable()) { + $stream->rewind(); + } + Utils::copyToStream( - $this->getStream(), + $stream, new LazyOpenStream($targetPath, 'w') ); @@ -183,9 +162,7 @@ class UploadedFile implements UploadedFileInterface } if (false === $this->moved) { - throw new RuntimeException( - sprintf('Uploaded file could not be moved to %s', $targetPath) - ); + throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', DiagnosticValue::escape($targetPath))); } } diff --git a/libs/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php b/libs/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php new file mode 100644 index 00000000..968e418a --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php @@ -0,0 +1,130 @@ + + */ +final class UploadedFileNormalizer +{ + private function __construct() + { + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @return UploadedFileTree + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalize(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && array_key_exists('tmp_name', $value)) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalize($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileTree + */ + private static function createUploadedFileFromSpec(array $value) + { + self::assertFileSpec($value); + + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + Integers::assertNonNegativeInteger($value['size'], 'Uploaded file size'), + Integers::assertNonNegativeInteger($value['error'], 'Uploaded file error'), + $value['name'] ?? null, + $value['type'] ?? null + ); + } + + private static function assertFileSpec(array $value): void + { + if (!isset($value['tmp_name'], $value['size'], $value['error'])) { + throw new InvalidArgumentException('Invalid file specification; expected keys "tmp_name", "size", and "error".'); + } + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileTree + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + self::assertNestedFileSpec($files); + + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + if (!array_key_exists($key, $files['size']) || !array_key_exists($key, $files['error'])) { + throw new InvalidArgumentException('Invalid nested file specification; expected "tmp_name", "size", and "error" arrays to have matching keys.'); + } + + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key] ?? null, + 'type' => $files['type'][$key] ?? null, + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + private static function assertNestedFileSpec(array $files): void + { + foreach (['tmp_name', 'size', 'error'] as $key) { + if (!isset($files[$key]) || !is_array($files[$key])) { + throw new InvalidArgumentException('Invalid nested file specification; expected keys "tmp_name", "size", and "error" to be arrays.'); + } + } + + foreach (['name', 'type'] as $key) { + if (isset($files[$key]) && !is_array($files[$key])) { + throw new InvalidArgumentException(sprintf('Invalid nested file specification; expected key "%s" to be an array when present.', $key)); + } + } + } +} diff --git a/libs/vendor/guzzlehttp/psr7/src/Uri.php b/libs/vendor/guzzlehttp/psr7/src/Uri.php index a10f52ce..e216c817 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Uri.php +++ b/libs/vendor/guzzlehttp/psr7/src/Uri.php @@ -17,10 +17,9 @@ use Psr\Http\Message\UriInterface; class Uri implements UriInterface, \JsonSerializable { /** - * Absolute http and https URIs require a host per RFC 7230 Section 2.7 - * but in generic URIs the host can be empty. So for http(s) URIs - * we apply this default host when no host is given yet to form a - * valid URI. + * Absolute http and https URIs require a host per RFC 9110 Section 4.2.1 + * but in generic URIs the host can be empty. So for http(s) URIs we apply + * this default host when no host is given yet to form a valid URI. */ private const HTTP_DEFAULT_HOST = 'localhost'; @@ -36,37 +35,41 @@ class Uri implements UriInterface, \JsonSerializable 'imap' => 143, 'pop' => 110, 'ldap' => 389, + 'ws' => 80, + 'wss' => 443, ]; private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B']; /** @var string Uri scheme. */ - private $scheme = ''; + private string $scheme = ''; /** @var string Uri user info. */ - private $userInfo = ''; + private string $userInfo = ''; /** @var string Uri host. */ - private $host = ''; + private string $host = ''; /** @var int|null Uri port. */ - private $port; + private ?int $port = null; /** @var string Uri path. */ - private $path = ''; + private string $path = ''; /** @var string Uri query string. */ - private $query = ''; + private string $query = ''; /** @var string Uri fragment. */ - private $fragment = ''; + private string $fragment = ''; - public function __construct(string $uri = '') - { + public function __construct( + #[\SensitiveParameter] + string $uri = '' + ) { if ($uri !== '') { - $parts = self::parse($uri); + $parts = UriParser::parse($uri); if ($parts === false) { - throw new MalformedUriException("Unable to parse URI: $uri"); + throw new MalformedUriException(\sprintf('Unable to parse URI: %s', DiagnosticValue::escape($uri))); } try { $this->applyParts($parts); @@ -78,108 +81,6 @@ class Uri implements UriInterface, \JsonSerializable } } - /** - * UTF-8 aware \parse_url() replacement. - * - * The internal function produces broken output for non ASCII domain names - * (IDN) when used with locales other than "C". - * - * On the other hand, cURL understands IDN correctly only when UTF-8 locale - * is configured ("C.UTF-8", "en_US.UTF-8", etc.). - * - * @see https://bugs.php.net/bug.php?id=52923 - * @see https://www.php.net/manual/en/function.parse-url.php#114817 - * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING - * - * @return array|false - */ - private static function parse(string $url) - { - if (self::isPathNoSchemeReference($url)) { - return self::parsePathNoSchemeReference($url); - } - - // Preserve bracketed IPv6 literals before encoding, including dotted IPv4 - // tails. DEL (\x7F) is excluded so a raw-DEL host falls through to the - // general path and is rejected rather than silently mutated by parse_url(). - $prefix = ''; - $ipv6Prefix = preg_match('%\A([0-9A-Za-z+.-]+://\[[^\]\x00-\x20\x7F/?#@]+\])(.*)\z%s', $url, $matches); - - if ($ipv6Prefix === false) { - return false; - } - - if ($ipv6Prefix === 1) { - /** @var array{0:string, 1:string, 2:string} $matches */ - $suffix = $matches[2]; - - // After the bracketed host only an optional numeric port and/or a - // path, query, or fragment may follow. Anything else (for example - // `:80@evil` or `:80x`) would let parse_url() reinterpret a - // different host. - if (preg_match('%\A(?::[0-9]*)?(?:[/?#].*)?\z%s', $suffix) !== 1) { - return false; - } - - $prefix = $matches[1]; - $url = $suffix; - } - - /** @var string|null */ - $encodedUrl = preg_replace_callback( - '%[^:/@?&=#]+%usD', - static function ($matches) { - return urlencode($matches[0]); - }, - $url - ); - - if ($encodedUrl === null) { - return false; - } - - $result = parse_url($prefix.$encodedUrl); - - if ($result === false) { - return false; - } - - return array_map('urldecode', $result); - } - - private static function isPathNoSchemeReference(string $url): bool - { - if ($url === '' || $url[0] === '/' || $url[0] === '?' || $url[0] === '#') { - return false; - } - - $firstSegment = substr($url, 0, strcspn($url, '/?#')); - - return strpos($firstSegment, ':') === false; - } - - /** - * @return array{path: string, query?: string, fragment?: string} - */ - private static function parsePathNoSchemeReference(string $url): array - { - $parts = []; - - if (false !== ($fragmentPosition = strpos($url, '#'))) { - $parts['fragment'] = substr($url, $fragmentPosition + 1); - $url = substr($url, 0, $fragmentPosition); - } - - if (false !== ($queryPosition = strpos($url, '?'))) { - $parts['query'] = substr($url, $queryPosition + 1); - $url = substr($url, 0, $queryPosition); - } - - $parts['path'] = $url; - - return $parts; - } - public function __toString(): string { return self::composeComponents( @@ -192,20 +93,25 @@ class Uri implements UriInterface, \JsonSerializable } /** - * Composes a URI reference string from its various components. + * Composes a URI reference string from its various components according to + * RFC 3986 Section 5.3. * - * Usually this method does not need to be called manually but instead is used indirectly via - * `Psr\Http\Message\UriInterface::__toString`. + * Usually this method does not need to be called manually but instead is + * used indirectly via `Psr\Http\Message\UriInterface::__toString`. * - * PSR-7 UriInterface treats an empty component the same as a missing component as - * getQuery(), getFragment() etc. always return a string. This explains the slight - * difference to RFC 3986 Section 5.3. + * PSR-7 UriInterface treats an empty component the same as a missing + * component as `getQuery()`, `getFragment()` etc. always return a string. + * This explains the slight difference to RFC 3986 Section 5.3. * - * Another adjustment is that the authority separator is added even when the authority is missing/empty - * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with - * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But - * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to - * that format). + * Another adjustment is that the authority separator is added even when the + * authority is missing/empty for the "file" scheme. This is because PHP + * stream functions like `file_get_contents` only work with `file:///myfile` + * but not with `file:/myfile` although they are equivalent according to RFC + * 3986. But `file:///` is the more common syntax for the file scheme anyway + * (Chrome for example redirects to that format). The separator is omitted + * when such a URI has a rootless or empty path: adding it would turn the + * first path segment into the authority of the composed URI, or compose the + * string `file://`, which cannot be parsed back into a URI. * * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3 */ @@ -218,11 +124,11 @@ class Uri implements UriInterface, \JsonSerializable $uri .= $scheme.':'; } - if ($authority != '' || $scheme === 'file') { + if ($authority != '' || ($scheme === 'file' && str_starts_with($path, '/'))) { $uri .= '//'.$authority; } - if ($authority != '' && $path != '' && $path[0] != '/') { + if ($authority != '' && $path != '' && !str_starts_with($path, '/')) { $path = '/'.$path; } @@ -242,8 +148,8 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI has the default port of the current scheme. * - * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used - * independently of the implementation. + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard + * port. This method can be used independently of the implementation. */ public static function isDefaultPort(UriInterface $uri): bool { @@ -254,17 +160,18 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is absolute, i.e. it has a scheme. * - * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true - * if it is the former. 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: - * - network-path references, e.g. '//example.com/path' - * - absolute-path references, e.g. '/path' - * - relative-path references, e.g. 'subpath' + * An instance of 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, the base URI. Relative references + * can be divided into several forms according to RFC 3986 Section 4.2: + * - network-path references, e.g. `//example.com/path` + * - absolute-path references, e.g. `/path` + * - relative-path references, e.g. `subpath` * * @see Uri::isNetworkPathReference * @see Uri::isAbsolutePathReference * @see Uri::isRelativePathReference - * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function isAbsolute(UriInterface $uri): bool { @@ -274,7 +181,8 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is a network-path reference. * - * 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 a + * network-path reference. * * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ @@ -284,9 +192,10 @@ class Uri implements UriInterface, \JsonSerializable } /** - * Whether the URI is a absolute-path reference. + * Whether the URI is an absolute-path reference. * - * 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://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ @@ -301,7 +210,8 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is a relative-path reference. * - * 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://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ @@ -315,9 +225,10 @@ class Uri implements UriInterface, \JsonSerializable /** * 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 (apart from its fragment) is considered 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 (apart from its fragment) is considered a + * same-document reference. * * @param UriInterface $uri The URI to check * @param UriInterface|null $base An optional base URI to compare against @@ -331,7 +242,7 @@ class Uri implements UriInterface, \JsonSerializable return ($uri->getScheme() === $base->getScheme()) && ($uri->getAuthority() === $base->getAuthority()) - && ($uri->getPath() === $base->getPath()) + && (self::rawPath($uri) === self::rawPath($base)) && ($uri->getQuery() === $base->getQuery()); } @@ -358,10 +269,9 @@ class Uri implements UriInterface, \JsonSerializable * Creates a new URI with a specific query string value. * * Any existing query string values that exactly match the provided key are - * removed and replaced with the given key value pair. - * - * A value of null will set the query string key without a value, e.g. "key" - * instead of "key=value". + * removed and replaced with the given key value pair. A value of null will + * set the query string key without a value, e.g. "key" instead of + * "key=value". * * @param UriInterface $uri URI to use as a base. * @param string $key Key to set. @@ -377,9 +287,10 @@ class Uri implements UriInterface, \JsonSerializable } /** - * Creates a new URI with multiple specific query string values. + * Creates a new URI with multiple query string values. * - * 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 (string|null)[] $keyValueArray Associative array of key and values @@ -389,36 +300,24 @@ class Uri implements UriInterface, \JsonSerializable $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); foreach ($keyValueArray as $key => $value) { - $result[] = self::generateQueryString((string) $key, $value !== null ? self::stringifyQueryValue($value) : null); + self::assertStringOrNullQueryValue($value); + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); } return $uri->withQuery(implode('&', $result)); } /** - * Stringifies a non-null query value, deprecating non-string values that - * guzzlehttp/psr7 3.0 will reject. Non-finite floats are normalized to the - * strings PHP coerces them to, as implicit coercion of NAN emits a warning - * on PHP 8.5. - * * @param mixed $value */ - private static function stringifyQueryValue($value): string + private static function assertStringOrNullQueryValue($value): void { - if (!is_string($value)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.12', - 'Passing %s to Uri::withQueryValues() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string or null query values.', - \gettype($value) - ); - - if (is_float($value) && !is_finite($value)) { - return is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); - } + if ($value !== null && !is_string($value)) { + throw new \InvalidArgumentException(\sprintf( + 'Query string values must be a string or null, %s given.', + \get_debug_type($value) + )); } - - return (string) $value; } /** @@ -428,8 +327,10 @@ class Uri implements UriInterface, \JsonSerializable * * @throws MalformedUriException If the components do not form a valid URI. */ - public static function fromParts(array $parts): UriInterface - { + public static function fromParts( + #[\SensitiveParameter] + array $parts + ): UriInterface { $uri = new self(); try { $uri->applyParts($parts); @@ -450,32 +351,8 @@ class Uri implements UriInterface, \JsonSerializable */ public static function assertValidHost(string $host): void { - if ($host === '') { - return; - } - - // Reject control characters and URI authority delimiters so getHost() - // cannot disagree with the on-wire authority. - $invalidHost = preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host); - - if ($invalidHost === false) { - throw new \RuntimeException('Unable to validate URI host: '.preg_last_error_msg()); - } - - if ($invalidHost === 1) { - throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host)); - } - - if (strpos($host, '[') !== false || strpos($host, ']') !== false) { - if ($host[0] !== '[' || substr($host, -1) !== ']') { - throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host)); - } - - return; - } - - if (strpos($host, ':') !== false) { - throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host)); + if (!Rfc3986::isValidHost($host)) { + throw new \InvalidArgumentException(sprintf('Invalid host: %s', DiagnosticValue::escape($host))); } } @@ -515,9 +392,52 @@ class Uri implements UriInterface, \JsonSerializable public function getPath(): string { + if (str_starts_with($this->path, '//')) { + return '/'.ltrim($this->path, '/'); + } + return $this->path; } + /** + * Returns the path as it appears within a URI's string form. + * + * getPath() collapses multiple leading slashes so that a path used in + * isolation cannot be mistaken for a protocol-relative URL. Whole-URI + * operations like reference resolution and normalization (RFC 3986 + * Sections 5 and 6) are defined on the URI string form, where the path + * stays verbatim, so they must read the path through this method instead. + * For direct instances of this class the path is derived from the stored + * components, including the leading slash the string form adds to a + * rootless path when an authority is present; for subclasses and other + * implementations the path is split from the string form per RFC 3986 + * Appendix B, without validating or decoding any other component. + * + * @throws \RuntimeException If the path cannot be split from the string form. + * + * @internal + */ + public static function rawPath(UriInterface $uri): string + { + if (get_class($uri) === self::class) { + if ($uri->path !== '' && !str_starts_with($uri->path, '/') && $uri->getAuthority() !== '') { + // composeComponents() prepends a slash to a rootless path when + // an authority is present, so the string form uses this path. + return '/'.$uri->path; + } + + return $uri->path; + } + + $count = preg_match('%^(?:[^:/?#]+:)?(?://[^/?#]*)?([^?#]*)%', (string) $uri, $matches); + + if ($count === false) { + throw new \RuntimeException('Unable to read the URI path: '.preg_last_error_msg()); + } + + return $matches[1] ?? ''; + } + public function getQuery(): string { return $this->query; @@ -528,7 +448,7 @@ class Uri implements UriInterface, \JsonSerializable return $this->fragment; } - public function withScheme($scheme): UriInterface + public function withScheme(string $scheme): UriInterface { $scheme = $this->filterScheme($scheme); @@ -544,8 +464,11 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withUserInfo($user, $password = null): UriInterface - { + public function withUserInfo( + string $user, + #[\SensitiveParameter] + ?string $password = null + ): UriInterface { $info = $this->filterUserInfoComponent($user); if ($password !== null) { $info .= ':'.$this->filterUserInfoComponent($password); @@ -562,7 +485,7 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withHost($host): UriInterface + public function withHost(string $host): UriInterface { $host = $this->filterHost($host); @@ -577,17 +500,8 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withPort($port): UriInterface + public function withPort(?int $port): UriInterface { - if ($port !== null && !\is_int($port)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to UriInterface::withPort() is deprecated; guzzlehttp/psr7 3.0 requires int|null.', - \get_debug_type($port) - ); - } - $port = $this->filterPort($port); if ($this->port === $port) { @@ -602,7 +516,7 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withPath($path): UriInterface + public function withPath(string $path): UriInterface { $path = $this->filterPath($path); @@ -617,7 +531,7 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withQuery($query): UriInterface + public function withQuery(string $query): UriInterface { $query = $this->filterQueryAndFragment($query); @@ -631,7 +545,7 @@ class Uri implements UriInterface, \JsonSerializable return $new; } - public function withFragment($fragment): UriInterface + public function withFragment(string $fragment): UriInterface { $fragment = $this->filterQueryAndFragment($fragment); @@ -655,8 +569,10 @@ class Uri implements UriInterface, \JsonSerializable * * @param array $parts Array of parse_url parts to apply. */ - private function applyParts(array $parts): void - { + private function applyParts( + #[\SensitiveParameter] + array $parts + ): void { $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; @@ -667,7 +583,7 @@ class Uri implements UriInterface, \JsonSerializable ? $this->filterHost($parts['host']) : ''; $this->port = isset($parts['port']) - ? $this->filterPort($parts['port']) + ? $this->filterPortPart($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) @@ -686,77 +602,70 @@ class Uri implements UriInterface, \JsonSerializable } /** - * @param mixed $scheme - * * @throws \InvalidArgumentException If the scheme is invalid. */ - private function filterScheme($scheme): string + private function filterScheme(string $scheme): string { - if (!is_string($scheme)) { - throw new \InvalidArgumentException('Scheme must be a string'); - } - $scheme = Utils::asciiToLower($scheme); - if ($scheme !== '' && !preg_match('/^[a-z][a-z0-9.+-]*$/D', $scheme)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing "%s" as a URI scheme is deprecated; guzzlehttp/psr7 3.0 requires URI schemes to match RFC 3986 syntax and begin with a letter.', - $scheme - ); + if (!Rfc3986::isValidScheme($scheme)) { + throw new \InvalidArgumentException(sprintf('Invalid scheme: %s', DiagnosticValue::escape($scheme))); } return $scheme; } /** - * @param mixed $component - * * @throws \InvalidArgumentException If the user info is invalid. */ - private function filterUserInfoComponent($component): string - { - if (!is_string($component)) { - throw new \InvalidArgumentException('User info must be a string'); - } - + private function filterUserInfoComponent( + #[\SensitiveParameter] + string $component + ): string { return $this->filterComponent( - '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']++|%(?!'.Rfc3986::HEX_OCTET.'))/', $component, 'Unable to filter URI user info' ); } /** - * @param mixed $host - * * @throws \InvalidArgumentException If the host is invalid. */ - private function filterHost($host): string + private function filterHost(string $host): string { - if (!is_string($host)) { - throw new \InvalidArgumentException('Host must be a string'); + $host = Utils::asciiToLower($host); + $filtered = \preg_replace_callback('/%'.Rfc3986::HEX_OCTET.'/', static function (array $m): string { + return Utils::asciiToUpper($m[0]); + }, $host); + if ($filtered === null) { + throw new \RuntimeException('Unable to normalize URI host percent-encoding: '.\preg_last_error_msg()); + } + self::assertValidHost($filtered); + + if (str_starts_with($filtered, '[') && !str_starts_with($filtered, '[v')) { + // assertValidHost() accepted this bracketed value with the same + // filter_var() predicate tryCanonicalizeIpv6() validates with, and + // its pure-PHP parse cannot fail on filter-accepted text, so the + // null guard is defense in depth only. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($filtered, 1, -1)); + if ($canonical !== null) { + $filtered = '['.$canonical.']'; + } } - $host = Utils::asciiToLower($host); - self::assertValidHost($host); - - return $host; + return $filtered; } /** - * @param mixed $port - * * @throws \InvalidArgumentException If the port is invalid. */ - private function filterPort($port): ?int + private function filterPort(?int $port): ?int { if ($port === null) { return null; } - $port = (int) $port; if (0 > $port || 0xFFFF < $port) { throw new \InvalidArgumentException( sprintf('Invalid port: %d. Must be between 0 and 65535', $port) @@ -766,6 +675,72 @@ class Uri implements UriInterface, \JsonSerializable return $port; } + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPortPart($port): ?int + { + if (\is_int($port)) { + return $this->filterPort($port); + } + + if (\is_string($port) && \ctype_digit($port)) { + // A zero port is accepted here; only Rfc9112::parsePort() rejects + // it for HTTP Host/authority parsing. + if (Rfc3986::isValidPort($port)) { + return (int) \ltrim($port, '0'); + } + + throw new \InvalidArgumentException(sprintf( + 'Invalid port: %s. Must be between 0 and 65535', + \ltrim($port, '0') + )); + } + + throw new \InvalidArgumentException(sprintf( + 'Invalid port: %s. Must be between 0 and 65535', + self::describeInvalidPort($port) + )); + } + + /** + * @param mixed $port + */ + private static function describeInvalidPort($port): string + { + if (\is_string($port)) { + return DiagnosticValue::escape($port); + } + + if (\is_int($port)) { + return (string) $port; + } + + if (\is_bool($port)) { + return $port ? 'true' : 'false'; + } + + if ($port === null) { + return 'null'; + } + + if (\is_float($port)) { + if (\is_nan($port)) { + return 'NAN'; + } + + if (\is_infinite($port)) { + return $port > 0 ? 'INF' : '-INF'; + } + + return \sprintf('%.14G', $port); + } + + return \get_debug_type($port); + } + /** * @param (string|int)[] $keys * @@ -783,7 +758,7 @@ class Uri implements UriInterface, \JsonSerializable return rawurldecode((string) $k); }, $keys); - return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return array_filter(explode('&', $current), static function (string $part) use ($decodedKeys): bool { return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); }); } @@ -813,18 +788,12 @@ class Uri implements UriInterface, \JsonSerializable /** * Filters the path of a URI * - * @param mixed $path - * * @throws \InvalidArgumentException If the path is invalid. */ - private function filterPath($path): string + private function filterPath(string $path): string { - if (!is_string($path)) { - throw new \InvalidArgumentException('Path must be a string'); - } - return $this->filterComponent( - '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?!'.Rfc3986::HEX_OCTET.'))/', $path, 'Unable to filter URI path' ); @@ -833,25 +802,23 @@ class Uri implements UriInterface, \JsonSerializable /** * Filters the query string or fragment of a URI. * - * @param mixed $str - * * @throws \InvalidArgumentException If the query or fragment is invalid. */ - private function filterQueryAndFragment($str): string + private function filterQueryAndFragment(string $str): string { - if (!is_string($str)) { - throw new \InvalidArgumentException('Query and fragment must be a string'); - } - return $this->filterComponent( - '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?!'.Rfc3986::HEX_OCTET.'))/', $str, 'Unable to filter URI query or fragment' ); } - private function filterComponent(string $pattern, string $component, string $context): string - { + private function filterComponent( + string $pattern, + #[\SensitiveParameter] + string $component, + string $context + ): string { $filtered = preg_replace_callback($pattern, [$this, 'rawurlencodeMatchZero'], $component); if ($filtered === null) { @@ -873,10 +840,10 @@ class Uri implements UriInterface, \JsonSerializable } if ($this->getAuthority() === '') { - if (0 === strpos($this->path, '//')) { + if (str_starts_with($this->path, '//')) { throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); } - if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + if ($this->scheme === '' && str_contains(explode('/', $this->path, 2)[0], ':')) { throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); } } diff --git a/libs/vendor/guzzlehttp/psr7/src/UriComparator.php b/libs/vendor/guzzlehttp/psr7/src/UriComparator.php index ea9a07a4..8bd27bff 100644 --- a/libs/vendor/guzzlehttp/psr7/src/UriComparator.php +++ b/libs/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -7,19 +7,33 @@ namespace GuzzleHttp\Psr7; use Psr\Http\Message\UriInterface; /** - * Provides methods to determine if a modified URL should be considered cross-origin. + * Provides methods to determine if a modified URI should be considered + * cross-origin. * * @author Graham Campbell */ final class UriComparator { /** - * Determines if a modified URL should be considered cross-origin with - * respect to an original URL. + * Determines if a modified URI should be considered cross-origin with + * respect to an original URI. + * + * Two URIs are cross-origin when their scheme, host, or effective port + * differ. Host comparison is case-insensitive, and bracketed IPv6 literals + * are canonicalized to their RFC 5952 form from any PSR-7 implementation + * before comparison, so equivalent spellings of the same address are + * same-origin. IPvFuture literals and bracketed values that cannot be + * parsed as an IPv6 address, such as those carrying zone identifiers, + * still compare as case-insensitive text. Missing ports use the default + * port for `http`, `https`, `ws`, or `wss`. Other schemes do not receive + * implicit default ports. + * + * This helper only compares URI origins. It does not implement redirect + * handling or credential policy. */ public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool { - if (!Utils::caselessEquals($original->getHost(), $modified->getHost())) { + if (!Utils::caselessEquals(self::normalizeHost($original), self::normalizeHost($modified))) { return true; } @@ -34,6 +48,28 @@ final class UriComparator return false; } + private static function normalizeHost(UriInterface $uri): string + { + $host = $uri->getHost(); + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return $host; + } + + // Foreign UriInterface implementations may carry non-canonical IPv6 + // spellings; canonicalize what is unambiguously an IPv6 address so + // equivalent literals compare as same-origin, and leave IPvFuture, + // zone-identifier, and invalid text to the caseless textual + // comparison. Validation is platform-independent, so a spelling only + // some OS parsers accept, such as zero-padded dotted octets, is + // cross-origin everywhere instead of same-origin on some systems. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1)); + if ($canonical === null) { + return $host; + } + + return '['.$canonical.']'; + } + private static function computePort(UriInterface $uri): ?int { $port = $uri->getPort(); @@ -42,11 +78,11 @@ final class UriComparator return $port; } - if ('http' === $uri->getScheme()) { + if (\in_array($uri->getScheme(), ['http', 'ws'], true)) { return 80; } - if ('https' === $uri->getScheme()) { + if (\in_array($uri->getScheme(), ['https', 'wss'], true)) { return 443; } diff --git a/libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php index e5468b86..d7150c29 100644 --- a/libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php +++ b/libs/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -16,7 +16,8 @@ use Psr\Http\Message\UriInterface; final class UriNormalizer { /** - * Default normalizations which only include the ones that preserve semantics. + * Default normalizations which only include the ones that preserve + * semantics. */ public const PRESERVING_NORMALIZATIONS = self::CAPITALIZE_PERCENT_ENCODING | @@ -24,10 +25,21 @@ final class UriNormalizer self::CONVERT_EMPTY_PATH | self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | - self::REMOVE_DOT_SEGMENTS; + self::REMOVE_DOT_SEGMENTS | + self::CANONICALIZE_IPV6_HOST; /** - * All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + * All letters within a percent-encoding triplet (e.g., "%3A") are + * case-insensitive, and should be capitalized. This applies to the + * userinfo, host, path, query, and fragment components. Bracketed + * IP-literal hosts are skipped as a legacy tolerance for nonstandard values + * other implementations may carry; zone-identifier text was briefly valid + * URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + * userinfo and host are only rewritten when the value returned by the + * implementation matches the normalized form, and a userinfo with an empty + * user segment is never rewritten. No percent-encoding normalization is + * applied to a component that contains malformed percent syntax, such as a + * `%` not followed by two hexadecimal digits. * * Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b */ @@ -36,9 +48,22 @@ final class UriNormalizer /** * Decodes percent-encoded octets of unreserved characters. * - * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), - * hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and, - * when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers. + * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A + * and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore + * (%5F), or tilde (%7E) should not be created by URI producers and, when + * found in a URI, should be decoded to their corresponding unreserved + * characters by URI normalizers. This applies to the userinfo, host, path, + * query, and fragment components. Since the host is case-insensitive and + * PSR-7 requires it to be lowercase, octets decoded in the host are + * lowercased (e.g., "%41" becomes "a"). Bracketed IP-literal hosts are + * skipped as a legacy tolerance for nonstandard values other + * implementations may carry; zone-identifier text was briefly valid URI + * syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + * userinfo and host are only rewritten when the value returned by the + * implementation matches the normalized form, and a userinfo with an empty + * user segment is never rewritten. No percent-encoding normalization is + * applied to a component that contains malformed percent syntax, such as a + * `%` not followed by two hexadecimal digits. * * Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/ */ @@ -54,11 +79,12 @@ final class UriNormalizer /** * Removes the default host of the given URI scheme from the URI. * - * Only the "file" scheme defines the default host "localhost". - * All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` - * are equivalent according to RFC 3986. The first format is not accepted - * by PHPs stream functions and thus already normalized implicitly to the - * second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`. + * Only the "file" scheme defines the default host "localhost". All of + * `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are + * equivalent according to RFC 3986. The first format is not accepted by + * PHPs stream functions and thus already normalized implicitly to the + * second format in the Uri class. See + * `GuzzleHttp\Psr7\Uri::composeComponents`. * * Example: file://localhost/myfile → file:///myfile */ @@ -84,9 +110,10 @@ final class UriNormalizer /** * Paths which include two or more adjacent slashes are converted to one. * - * Webservers usually ignore duplicate slashes and treat those URIs equivalent. - * But in theory those URIs do not need to be equivalent. So this normalization - * may change the semantics. Encoded slashes (%2F) are not removed. + * Webservers usually ignore duplicate slashes and treat those URIs + * equivalent. But in theory those URIs do not need to be equivalent. So + * this normalization may change the semantics. Encoded slashes (%2F) are + * not removed. * * Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html */ @@ -95,26 +122,49 @@ final class UriNormalizer /** * Sort query parameters with their values in alphabetical order. * - * However, the order of parameters in a URI may be significant (this is not defined by the standard). - * So this normalization is not safe and may change the semantics of the URI. + * However, the order of parameters in a URI may be significant (this is not + * defined by the standard). So this normalization is not safe and may + * change the semantics of the URI. * * Example: ?lang=en&article=fred → ?article=fred&lang=en * - * Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the - * purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly. + * Note: The sorting is neither locale nor Unicode aware (the URI query does + * not get decoded at all) as the purpose is to be able to compare URIs in a + * reproducible way, not to have the params sorted perfectly. */ public const SORT_QUERY_PARAMETERS = 128; + /** + * Canonicalizes IPv6 hosts to their RFC 5952 form. + * + * IPv6 addresses allow leading zeros and multiple placements of the `::` + * elision, so the same address has many textual spellings. The canonical + * form is required for IPv6 literals in URIs by RFC 5952 Section 6 and + * never changes what the URI refers to. Native `Uri` instances already + * guarantee canonical output; for other implementations, the canonical + * host is requested through `withHost()` and the result is kept only when + * the returned `getHost()` exactly matches the requested spelling, + * otherwise this step leaves the URI unchanged while other selected + * normalizations still apply, and setter exceptions propagate. + * + * Example: http://[::0:0a]/ → http://[::a]/ + */ + public const CANONICALIZE_IPV6_HOST = 256; + /** * Returns a normalized URI. * - * The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. - * This methods adds additional normalizations that can be configured with the $flags parameter. + * The scheme and host component are already normalized to lowercase per + * PSR-7 UriInterface. This method adds additional normalizations that can + * be configured with the `$flags` parameter, which is a bitmask of + * normalizations to apply. * - * PSR-7 UriInterface cannot distinguish between an empty component and a missing component as - * getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are - * treated equivalent which is not necessarily true according to RFC 3986. But that difference - * is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well. + * PSR-7 UriInterface cannot distinguish between an empty component and a + * missing component as `getQuery()`, `getFragment()` etc. always return a + * string. This means the URIs `/?#` and `/` are treated equivalent which is + * not necessarily true according to RFC 3986. But that difference is highly + * uncommon in reality. So this potential normalization is implied in PSR-7 + * as well. * * @param UriInterface $uri The URI to normalize * @param int $flags A bitmask of normalizations to apply, see constants @@ -145,18 +195,24 @@ final class UriNormalizer $uri = $uri->withPort(null); } - if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { - $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); - } + $removeDotSegments = ($flags & self::REMOVE_DOT_SEGMENTS) && !Uri::isRelativePathReference($uri); - if ($flags & self::REMOVE_DUPLICATE_SLASHES) { - $path = preg_replace('#//++#', '/', $uri->getPath()); + if ($removeDotSegments || $flags & self::REMOVE_DUPLICATE_SLASHES) { + $path = Uri::rawPath($uri); - if ($path === null) { - throw new \RuntimeException('Unable to remove duplicate slashes from URI path: '.preg_last_error_msg()); + if ($removeDotSegments) { + $path = UriResolver::removeDotSegments($path); } - $uri = $uri->withPath($path); + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $path = preg_replace('#//++#', '/', $path); + + if ($path === null) { + throw new \RuntimeException('Unable to remove duplicate slashes from URI path: '.preg_last_error_msg()); + } + } + + $uri = $uri->withPath(UriResolver::guardedPath($uri, $path)); } if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { @@ -165,16 +221,22 @@ final class UriNormalizer $uri = $uri->withQuery(implode('&', $queryKeyValues)); } + if ($flags & self::CANONICALIZE_IPV6_HOST) { + $uri = self::canonicalizeIpv6Host($uri); + } + return $uri; } /** * Whether two URIs can be considered equivalent. * - * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also - * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be - * resolved against the same base URI. If this is not the case, determination of equivalence or difference of - * relative references does not mean anything. + * Both URIs are normalized automatically before comparison with the given + * `$normalizations` bitmask. The method also accepts relative URI + * references and returns true when they are equivalent. This of course + * assumes they will be resolved against the same base URI. If this is not + * the case, determination of equivalence or difference of relative + * references does not mean anything. * * @param UriInterface $uri1 An URI to compare * @param UriInterface $uri2 An URI to compare @@ -189,14 +251,17 @@ final class UriNormalizer private static function capitalizePercentEncoding(UriInterface $uri): UriInterface { - $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + $regex = '/(?:%'.Rfc3986::HEX_OCTET.')++/'; $callback = function (array $match): string { return Utils::asciiToUpper($match[0]); }; + $uri = self::withNormalizedUserInfo($uri, $regex, $callback); + $uri = self::withNormalizedHost($uri, $regex, $callback); + return $uri - ->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback)) + ->withPath(self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback)) ->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback)) ->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); } @@ -209,17 +274,116 @@ final class UriNormalizer return rawurldecode($match[0]); }; + // The host is case-insensitive and PSR-7 requires it to be lowercase, + // so decoded ALPHA octets (e.g. "%41") must land lowercase even for + // implementations whose withHost() does not normalize the case. + $hostCallback = function (array $match): string { + return Utils::asciiToLower(rawurldecode($match[0])); + }; + + $uri = self::withNormalizedUserInfo($uri, $regex, $callback); + $uri = self::withNormalizedHost($uri, $regex, $hostCallback); + return $uri - ->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback)) + ->withPath(self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback)) ->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback)) ->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); } + /** + * @param callable(array): string $callback + */ + private static function withNormalizedUserInfo(UriInterface $uri, string $regex, callable $callback): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (!str_contains($userInfo, '%')) { + return $uri; + } + + $normalized = self::normalizePercentEncodingInComponent($userInfo, $regex, $callback); + + if ($normalized === $userInfo) { + return $uri; + } + + // Normalization cannot create a colon: decoding is confined to + // unreserved characters and capitalization keeps octets encoded. So + // splitting on the first colon preserves the user/password boundary. + $parts = explode(':', $normalized, 2); + + // PSR-7 defines withUserInfo('') as removing the userinfo, so a + // userinfo with an empty user segment (e.g. ":pass") cannot be + // expressed through the setter and is preserved as-is instead. + if ($parts[0] === '') { + return $uri; + } + + $candidate = $uri->withUserInfo($parts[0], $parts[1] ?? null); + + // Normalization must never lose or corrupt information, so verify the + // representation the setter returned and leave the component untouched + // when the implementation cannot represent the normalized form. + if ($candidate->getUserInfo() !== $normalized) { + return $uri; + } + + return $candidate; + } + + /** + * @param callable(array): string $callback + */ + private static function withNormalizedHost(UriInterface $uri, string $regex, callable $callback): UriInterface + { + $host = $uri->getHost(); + + // Bracketed IP-literal hosts are skipped as a legacy tolerance for + // nonstandard values other implementations may carry, such as a zone + // identifier in "[fe80::1%25eth0]"; that text was briefly valid URI + // syntax under RFC 6874, which RFC 9844 obsoleted and reverted. + if (str_starts_with($host, '[') || !str_contains($host, '%')) { + return $uri; + } + + $normalized = self::normalizePercentEncodingInComponent($host, $regex, $callback); + + if ($normalized === $host) { + return $uri; + } + + $candidate = $uri->withHost($normalized); + + // Normalization must never lose or corrupt information, so verify the + // representation the setter returned and leave the component untouched + // when the implementation cannot represent the normalized form. + if ($candidate->getHost() !== $normalized) { + return $uri; + } + + return $candidate; + } + /** * @param callable(array): string $callback */ private static function normalizePercentEncodingInComponent(string $component, string $regex, callable $callback): string { + // Decoding a valid triplet that follows a dangling "%" would complete + // the malformed sequence into a new valid triplet ("example%6%31com" + // becomes "example%61com"), turning malformed text valid and breaking + // idempotence, so a component containing malformed percent syntax is + // returned unchanged. + $malformed = preg_match('/%(?!'.Rfc3986::HEX_OCTET.')/', $component); + + if ($malformed === false) { + throw new \RuntimeException('Unable to scan URI component percent-encoding: '.preg_last_error_msg()); + } + + if ($malformed === 1) { + return $component; + } + $normalized = preg_replace_callback($regex, $callback, $component); if ($normalized === null) { @@ -229,6 +393,32 @@ final class UriNormalizer return $normalized; } + private static function canonicalizeIpv6Host(UriInterface $uri): UriInterface + { + $host = $uri->getHost(); + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return $uri; + } + + // Foreign UriInterface implementations may carry IPvFuture literals, + // IPv6 zone identifiers, uppercase text, or invalid spellings; + // tryCanonicalizeIpv6() canonicalizes only what is unambiguously an + // IPv6 address and leaves everything else untouched. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1)); + if ($canonical === null || '['.$canonical.']' === $host) { + return $uri; + } + + $candidate = $uri->withHost('['.$canonical.']'); + // Normalization must never corrupt a component, so keep the original + // host when the implementation does not retain the canonical form. + if ($candidate->getHost() !== '['.$canonical.']') { + return $uri; + } + + return $candidate; + } + private function __construct() { // cannot be instantiated diff --git a/libs/vendor/guzzlehttp/psr7/src/UriParser.php b/libs/vendor/guzzlehttp/psr7/src/UriParser.php new file mode 100644 index 00000000..bedcdbff --- /dev/null +++ b/libs/vendor/guzzlehttp/psr7/src/UriParser.php @@ -0,0 +1,145 @@ + $floor) { + array_pop($results); + } } elseif ($segment !== '.') { $results[] = $segment; } @@ -38,7 +52,7 @@ final class UriResolver $newPath = implode('/', $results); - if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) { + if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) { // Re-add the leading slash if necessary for cases like "/.." $newPath = '/'.$newPath; } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) { @@ -51,7 +65,37 @@ final class UriResolver } /** - * Converts the relative URI into a new URI that is resolved against the base URI. + * Returns the path, prefixed with "/." when it would otherwise start the + * URI's string form with an authority-like "//". + * + * A URI without an authority cannot hold a path beginning with "//" (RFC + * 3986 Section 3.3), but removeDotSegments() can produce one. The "/." + * prefix serializes such a path unambiguously, the same way the WHATWG URL + * Standard does, and resolves back to the same path. Hostless http and + * https Uri instances gain the default localhost host when the path is + * written, so the path cannot be mistaken for an authority and the prefix + * is not added. + * + * @see https://url.spec.whatwg.org/#url-serializing + * + * @internal + */ + public static function guardedPath(UriInterface $uri, string $path): string + { + if (!str_starts_with($path, '//') || $uri->getAuthority() !== '') { + return $path; + } + + if ($uri instanceof Uri && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')) { + return $path; + } + + return '/.'.$path; + } + + /** + * Converts the relative URI into a new URI that is resolved against the + * base URI. * * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2 */ @@ -63,61 +107,69 @@ final class UriResolver } if ($rel->getScheme() != '') { - return $rel->withPath(self::removeDotSegments($rel->getPath())); + return $rel->withPath(self::guardedPath($rel, self::removeDotSegments(Uri::rawPath($rel)))); } if ($rel->getAuthority() != '') { return $rel ->withScheme($base->getScheme()) - ->withPath(self::removeDotSegments($rel->getPath())); + ->withPath(self::removeDotSegments(Uri::rawPath($rel))); } - if ($rel->getPath() === '') { - $targetPath = $base->getPath(); - $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + $relPath = Uri::rawPath($rel); + + if ($relPath === '') { + // the base path is used as-is per RFC 3986 Section 5.2.2, so it must not be + // rewritten through a getPath()/withPath() round-trip + return $base + ->withQuery($rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery()) + ->withFragment($rel->getFragment()); + } + + if (str_starts_with($relPath, '/')) { + $targetPath = $relPath; } else { - if ($rel->getPath()[0] === '/') { - $targetPath = $rel->getPath(); + $basePath = Uri::rawPath($base); + if ($base->getAuthority() != '' && $basePath === '') { + $targetPath = '/'.$relPath; } else { - if ($base->getAuthority() != '' && $base->getPath() === '') { - $targetPath = '/'.$rel->getPath(); + $lastSlashPos = strrpos($basePath, '/'); + if ($lastSlashPos === false) { + $targetPath = $relPath; } else { - $lastSlashPos = strrpos($base->getPath(), '/'); - if ($lastSlashPos === false) { - $targetPath = $rel->getPath(); - } else { - $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath(); - } + $targetPath = substr($basePath, 0, $lastSlashPos + 1).$relPath; } } - $targetPath = self::removeDotSegments($targetPath); - $targetQuery = $rel->getQuery(); } + $targetPath = self::removeDotSegments($targetPath); return $base - ->withPath($targetPath) - ->withQuery($targetQuery) + ->withPath(self::guardedPath($base, $targetPath)) + ->withQuery($rel->getQuery()) ->withFragment($rel->getFragment()); } /** * Returns the target URI as a relative reference from the base URI. * - * This method is the counterpart to resolve(): + * This method is the counterpart to `resolve()`: * * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) * - * One use-case is to use the current request URI as base URI and then generate relative links in your documents - * to reduce the document size or offer self-contained downloadable document archives. + * One use case is to use the current request URI as the base URI and then + * generate relative links in your documents to reduce the document size or + * offer self-contained downloadable document archives. * * $base = new Uri('http://example.com/a/b/'); * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'. * - * This method also accepts a target that is already relative and will try to relativize it further. Only a - * relative-path reference will be returned as-is. + * This method also accepts a target that is already relative and will try + * to relativize it further. Only a relative-path reference will be returned + * as-is. * * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well */ @@ -140,37 +192,68 @@ final class UriResolver return $target->withScheme(''); } + // A same-authority target with an empty path can only be expressed by a + // network-path reference (RFC 3986 Section 5.2.2). + if (self::needsNetworkPathReference($base, $target)) { + return $target->withScheme(''); + } + // We must remove the path before removing the authority because if the path starts with two slashes, the URI // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also // invalid. $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); - if ($base->getPath() !== $target->getPath()) { + if (Uri::rawPath($base) !== Uri::rawPath($target)) { return $emptyPathUri->withPath(self::getRelativePath($base, $target)); } - if ($base->getQuery() === $target->getQuery()) { + if ($base->getQuery() === $target->getQuery() && ($target->getFragment() !== '' || $base->getFragment() === '')) { // Only the target fragment is left. And it must be returned even if base and target fragment are the same. return $emptyPathUri->withQuery(''); } - // If the base URI has a query but the target has none, we cannot return an empty path reference as it would - // inherit the base query component when resolving. + // If the base URI has a query or fragment that the target lacks, we cannot return an empty path + // reference as it would inherit that base component when resolving. if ($target->getQuery() === '') { - $segments = explode('/', $target->getPath()); + $segments = explode('/', Uri::rawPath($target)); /** @var string $lastSegment */ $lastSegment = end($segments); - return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + // A reference to an empty last segment must be prefixed with "./". The same applies + // to a segment with a colon character, which would be mistaken for a scheme name. + if ($lastSegment === '' || str_contains($lastSegment, ':')) { + $lastSegment = "./$lastSegment"; + } + + return $emptyPathUri->withPath($lastSegment); } return $emptyPathUri; } + /** + * Whether relativizing to $target requires a network-path reference. + * + * A same-authority target with an empty path is expressible by a shorter + * relative reference unless resolving one would inherit a base component + * the target lacks: the base path (kept by any empty-path reference), or + * the base query or fragment (inherited by the empty reference). + */ + private static function needsNetworkPathReference(UriInterface $base, UriInterface $target): bool + { + if ($target->getAuthority() === '' || Uri::rawPath($target) !== '') { + return false; + } + + return Uri::rawPath($base) !== '' + || ($base->getQuery() !== '' && $target->getQuery() === '') + || ($base->getFragment() !== '' && $target->getFragment() === '' && $base->getQuery() === $target->getQuery()); + } + private static function getRelativePath(UriInterface $base, UriInterface $target): string { - $sourceSegments = explode('/', $base->getPath()); - $targetSegments = explode('/', $target->getPath()); + $sourceSegments = explode('/', Uri::rawPath($base)); + $targetSegments = explode('/', Uri::rawPath($target)); array_pop($sourceSegments); $targetLastSegment = array_pop($targetSegments); foreach ($sourceSegments as $i => $segment) { @@ -186,10 +269,10 @@ final class UriResolver // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. - if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + if ($relativePath === '' || str_contains(explode('/', $relativePath, 2)[0], ':')) { $relativePath = "./$relativePath"; - } elseif ('/' === $relativePath[0]) { - if ($base->getAuthority() != '' && $base->getPath() === '') { + } elseif (str_starts_with($relativePath, '/')) { + if ($base->getAuthority() != '' && Uri::rawPath($base) === '') { // In this case an extra slash is added by resolve() automatically. So we must not add one here. $relativePath = ".$relativePath"; } else { diff --git a/libs/vendor/guzzlehttp/psr7/src/Utils.php b/libs/vendor/guzzlehttp/psr7/src/Utils.php index c46991e9..35e418ff 100644 --- a/libs/vendor/guzzlehttp/psr7/src/Utils.php +++ b/libs/vendor/guzzlehttp/psr7/src/Utils.php @@ -4,12 +4,17 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; final class Utils { + private function __construct() + { + } + /** * Converts ASCII uppercase letters in a string to lowercase. * @@ -70,9 +75,9 @@ final class Utils } /** - * Remove the items given by the keys, case insensitively from the data. + * Remove the items given by the keys from the data, case-insensitively. * - * @param (string|int)[] $keys + * @param array $keys */ public static function caselessRemove(array $keys, array $data): array { @@ -93,12 +98,18 @@ final class Utils /** * Copy the contents of a stream into another stream until the given number - * of bytes have been read. + * of bytes have been read, returning the number of bytes copied as an + * `int`. On 32-bit PHP, an unbounded copy larger than `PHP_INT_MAX` bytes + * cannot be represented by that return type. 64-bit PHP is not affected. * - * The copy stops if the destination write returns 0, for example a - * BufferStream at its high water mark or a full DroppingStream. For a - * guaranteed full copy use a normal writable stream such as a file or - * php://temp stream. + * The destination must accept writes that make positive progress. Streams + * that return 0 as a backpressure or drop signal (a `BufferStream` at its + * high water mark, or a full `DroppingStream`) will cause this method to + * throw. For full copies, use a normal writable stream such as a file or + * `php://temp` stream. + * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a source read or destination write cannot make progress. * * @param StreamInterface $source Stream to read from * @param StreamInterface $dest Stream to write to @@ -107,63 +118,71 @@ final class Utils * * @throws \RuntimeException on error. */ - public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): int { $bufferSize = 8192; + $copied = 0; if ($maxLen === -1) { while (!$source->eof()) { - $buf = $source->read($bufferSize); + $buf = StreamTimeout::read($source, $bufferSize, 'Unable to read from stream: timed out'); if ($buf === '') { break; } - if (!self::writeAll($dest, $buf)) { - break; - } + self::writeAll($dest, $buf); + $copied = Integers::add($copied, strlen($buf)); } } else { $remaining = $maxLen; while ($remaining > 0 && !$source->eof()) { - $buf = $source->read(min($bufferSize, $remaining)); + $buf = StreamTimeout::read($source, min($bufferSize, $remaining), 'Unable to read from stream: timed out'); $len = strlen($buf); if (!$len) { break; } $remaining -= $len; - if (!self::writeAll($dest, $buf)) { - break; - } + self::writeAll($dest, $buf); + $copied = Integers::add($copied, $len); } } + + return $copied; } - /** - * Writes the full buffer to the destination, retrying short writes. - * - * Returns false when the destination write returns 0 or less. - */ - private static function writeAll(StreamInterface $dest, string $buf): bool + private static function writeAll(StreamInterface $dest, string $buf): void { $written = 0; $len = strlen($buf); while ($written < $len) { - $result = $dest->write(substr($buf, $written)); + try { + $result = $dest->write(substr($buf, $written)); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + StreamTimeout::throwIfWriteTimedOut($dest, $e); + + throw $e; + } + if ($result <= 0) { - return false; + StreamTimeout::throwIfWriteTimedOut($dest); + + throw new \RuntimeException('Unable to write to stream'); } $written += $result; } - - return true; } /** * Copy the contents of a stream into a string until the given number of * bytes have been read. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * * @param StreamInterface $stream Stream to read * @param int $maxLen Maximum number of bytes to read. Pass -1 * to read the entire stream. @@ -176,7 +195,7 @@ final class Utils if ($maxLen === -1) { while (!$stream->eof()) { - $buf = $stream->read(1048576); + $buf = StreamTimeout::read($stream, 1048576, 'Unable to read from stream: timed out'); if ($buf === '') { break; } @@ -188,7 +207,7 @@ final class Utils $len = 0; while (!$stream->eof() && $len < $maxLen) { - $buf = $stream->read($maxLen - $len); + $buf = StreamTimeout::read($stream, $maxLen - $len, 'Unable to read from stream: timed out'); if ($buf === '') { break; } @@ -202,8 +221,11 @@ final class Utils /** * Calculate a hash of a stream. * - * This method reads the entire stream to calculate a rolling hash, based - * on PHP's `hash_init` functions. + * This method reads the entire stream to calculate a rolling hash, based on + * PHP's `hash_init` functions. + * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. * * @param StreamInterface $stream Stream to calculate the hash for * @param string $algo Hash algorithm (e.g. md5, crc32, etc) @@ -221,7 +243,12 @@ final class Utils $ctx = hash_init($algo); while (!$stream->eof()) { - hash_update($ctx, $stream->read(1048576)); + $buf = StreamTimeout::read($stream, 1048576, 'Unable to calculate stream hash: timed out'); + if ($buf === '') { + break; + } + + hash_update($ctx, $buf); } $out = hash_final($ctx, $rawOutput); @@ -242,16 +269,27 @@ final class Utils * or non-empty arrays of strings. * - remove_headers: (array) Remove the given headers. Values may be * strings or integers. - * - body: (mixed) Sets the given body. Present non-null values are converted - * with self::streamFor(), including scalar values, resources, streams, - * iterators, callable arrays, closures, invokable objects, and objects - * with __toString(). String inputs remain literal bodies. - * - uri: (UriInterface) Set the URI. + * - body: (mixed) Sets the given body. Present non-null values are + * converted with self::streamFor(), including resources, streams, + * iterators, callable arrays, closures, invokable objects, and stringable + * objects. String inputs remain literal bodies. + * - uri: (UriInterface) Set the URI. When the URI contains a host, the + * Host header is updated from it, and combining this with an explicit + * Host entry in set_headers throws an InvalidArgumentException. Apply + * an intentional Host override separately with withHeader() afterwards. * - query: (string) Set the query string value of the URI. * - version: (string) Set the protocol version. * * @param RequestInterface $request Request to clone and modify. - * @param array $changes Changes to apply. + * @param array{ + * method?: string, + * set_headers?: array>, + * remove_headers?: array, + * body?: resource|string|StreamInterface|callable|\Iterator|\Stringable, + * uri?: UriInterface, + * query?: string, + * version?: string + * } $changes Changes to apply. */ public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface { @@ -259,16 +297,20 @@ final class Utils return $request; } - self::warnOnInvalidModifyRequestChanges($changes); + self::assertValidModifyRequestChanges($changes); $headers = $request->getHeaders(); if (!isset($changes['uri'])) { $uri = $request->getUri(); } else { - // Remove the host header if one is on the URI - $host = $changes['uri']->getHost(); + /** @var UriInterface */ + $uri = $changes['uri']; + + $host = $uri->getHost(); if ($host !== '') { + Uri::assertValidHost($host); + if (isset($changes['set_headers']) && is_array($changes['set_headers'])) { foreach (array_keys($changes['set_headers']) as $header) { if (self::asciiToLower((string) $header) === 'host') { @@ -281,15 +323,15 @@ final class Utils $changes['set_headers']['Host'] = $host; - if ($port = $changes['uri']->getPort()) { + $port = $uri->getPort(); + if ($port !== null) { $standardPorts = ['http' => 80, 'https' => 443]; - $scheme = $changes['uri']->getScheme(); - if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $scheme = $uri->getScheme(); + if (!isset($standardPorts[$scheme]) || $port != $standardPorts[$scheme]) { $changes['set_headers']['Host'] .= ':'.$port; } } } - $uri = $changes['uri']; } if (!empty($changes['remove_headers'])) { @@ -316,6 +358,7 @@ final class Utils // Match Request::__construct() by adding a Host header when one is not provided. if (!$hasHost && $uri->getHost() !== '') { $host = $uri->getHost(); + Uri::assertValidHost($host); if (($port = $uri->getPort()) !== null) { $host .= ':'.$port; @@ -372,45 +415,45 @@ final class Utils /** * @param array $changes */ - private static function warnOnInvalidModifyRequestChanges(array $changes): void + private static function assertValidModifyRequestChanges(array $changes): void { foreach (['method', 'query', 'version'] as $key) { if (\array_key_exists($key, $changes) && !\is_string($changes[$key])) { - self::warnOnInvalidModifyRequestChange($key, 'string', $changes[$key]); + self::assertValidModifyRequestChange($key, 'string', $changes[$key]); } } if (\array_key_exists('uri', $changes) && !$changes['uri'] instanceof UriInterface) { - self::warnOnInvalidModifyRequestChange('uri', 'UriInterface', $changes['uri']); + self::assertValidModifyRequestChange('uri', 'UriInterface', $changes['uri']); } if (\array_key_exists('body', $changes) && $changes['body'] === null) { - self::warnOnInvalidModifyRequestChange('body', 'resource|string|int|float|bool|StreamInterface|callable|\Iterator|\Stringable', $changes['body']); + self::assertValidModifyRequestChange('body', 'resource|string|StreamInterface|callable|\Iterator|\Stringable', $changes['body']); } if (\array_key_exists('set_headers', $changes)) { if (!\is_array($changes['set_headers'])) { - self::warnOnInvalidModifyRequestChange('set_headers', 'array>', $changes['set_headers']); + self::assertValidModifyRequestChange('set_headers', 'array>', $changes['set_headers']); } else { foreach ($changes['set_headers'] as $header => $value) { $headerPath = \sprintf('set_headers.%s', (string) $header); if (\is_array($value)) { if ($value === []) { - self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array', $value); break; } foreach ($value as $index => $item) { if (!\is_string($item)) { - self::warnOnInvalidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item); + self::assertValidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item); break 2; } } } elseif (!\is_string($value)) { - self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array', $value); break; } @@ -423,14 +466,14 @@ final class Utils } if (!\is_array($changes['remove_headers'])) { - self::warnOnInvalidModifyRequestChange('remove_headers', 'array', $changes['remove_headers']); + self::assertValidModifyRequestChange('remove_headers', 'array', $changes['remove_headers']); return; } foreach ($changes['remove_headers'] as $index => $header) { if (!\is_string($header) && !\is_int($header)) { - self::warnOnInvalidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header); + self::assertValidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header); return; } @@ -440,21 +483,17 @@ final class Utils /** * @param mixed $value */ - private static function warnOnInvalidModifyRequestChange(string $key, string $expected, $value): void + private static function assertValidModifyRequestChange(string $key, string $expected, $value): void { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.11', - 'Passing %s to Utils::modifyRequest() change "%s" is deprecated; guzzlehttp/psr7 3.0 requires %s.', - \get_debug_type($value), - $key, - $expected - ); + throw new \InvalidArgumentException(\sprintf('Utils::modifyRequest() change "%s" must be %s; %s provided.', DiagnosticValue::escape($key), $expected, \get_debug_type($value))); } /** * Read a line from the stream up to the maximum allowed buffer length. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * * @param StreamInterface $stream Stream to read from * @param int|null $maxLength Maximum buffer length */ @@ -464,7 +503,7 @@ final class Utils $size = 0; while (!$stream->eof()) { - if ('' === ($byte = $stream->read(1))) { + if ('' === ($byte = StreamTimeout::read($stream, 1, 'Unable to read line from stream: timed out'))) { return $buffer; } $buffer .= $byte; @@ -478,54 +517,113 @@ final class Utils } /** - * Redact the password in the user info part of a URI. + * Redact the user info part of a URI. + * + * Returns the URI with the whole userinfo component replaced by "***" + * when one is present, so neither the username nor the password survives + * into logs and diagnostics. A URI without userinfo is returned + * unchanged. */ - public static function redactUserInfo(UriInterface $uri): UriInterface - { - $userInfo = $uri->getUserInfo(); + public static function redactUserInfo( + #[\SensitiveParameter] + UriInterface $uri + ): UriInterface { + return $uri->getUserInfo() === '' ? $uri : $uri->withUserInfo('***'); + } - if (false !== ($pos = \strpos($userInfo, ':'))) { - return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + /** + * Redacts the userinfo of a raw URI string wherever it appears in a + * subject string. + * + * The needle is taken verbatim from the raw URI rather than from parsed + * components, so credentials that URI normalization would rewrite, such + * as raw control bytes or unencoded reserved characters, are still found + * in text that embeds the URI exactly as given, for example transport + * error messages. A URI without "://" is treated as authority-form: a + * host and port with optional userinfo. + * + * A URI that does not parse has no trustworthy authority boundary, so + * everything between any scheme and its last "@" is redacted as a safe-side + * fallback. + * + * @param string $subject Text that may embed the URI + * @param string $uri Raw URI whose userinfo is redacted in the text + */ + public static function redactUserInfoInString(string $subject, string $uri): string + { + if (\strpos($uri, '@') === false) { + return $subject; } - return $uri; + $schemePosition = \strpos($uri, '://'); + $remainder = $schemePosition === false ? $uri : \substr($uri, $schemePosition + 3); + + if (\parse_url($schemePosition === false ? 'http://'.$uri : $uri) === false) { + // Raw '/', '?', or '#' separators may sit inside the credentials + // of a URI that defeats parse_url(), so the redaction cannot stop + // at the apparent authority. + $atPosition = \strrpos($remainder, '@'); + + if ($atPosition === false || $atPosition === 0) { + return $subject; + } + + return \str_replace(\substr($remainder, 0, $atPosition).'@', '***@', $subject); + } + + $authority = \substr($remainder, 0, \strcspn($remainder, '/?#')); + $atPosition = \strrpos($authority, '@'); + + if ($atPosition === false || $atPosition === 0) { + // A parseable URI with '@' only past its authority, or with an + // empty userinfo, carries no credentials to redact. + return $subject; + } + + return \str_replace(\substr($authority, 0, $atPosition).'@', '***@', $subject); } /** * Create a new stream based on the input type. * - * Options is an associative array that can contain the following keys: + * Options are provided as an associative array that can contain the + * following keys: * - metadata: Array of custom metadata. * - size: Size of the stream. * * This method accepts the following `$resource` types: * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. - * - `string`: Creates a stream object that uses the given string as the contents. - * - `resource`: Creates a stream object that wraps the given PHP stream resource. - * - `Iterator`: If the provided value implements `Iterator`, then a read-only - * stream object will be created that wraps the given iterable. Each time the - * stream is read from, data from the iterator will fill a buffer and will be - * continuously called until the buffer is equal to the requested read size. - * Subsequent read calls will first read from the buffer and then call `next` - * on the underlying iterator until it is exhausted. - * - `object` with `__toString()`: If the object has the `__toString()` method, - * the object will be cast to a string and then a stream will be returned that - * uses the string value. + * - `string`: Creates a stream object that uses the given string as the + * contents. + * - `resource`: Creates a stream object that wraps the given PHP stream + * resource. + * - `Iterator`: If the provided value implements `Iterator`, then a + * read-only stream object will be created that wraps the given iterable. + * Each time the stream is read from, data from the iterator will fill a + * buffer and will be continuously called until the buffer is equal to the + * requested read size. Yielded strings, integers, finite floats, + * booleans, `null`, and stringable objects are converted to string + * chunks; non-finite floats and other values throw + * `UnexpectedValueException` when the stream is read. Values that + * stringify to an empty string are skipped while the iterator advances. + * Subsequent read calls will first read from the buffer and then call + * `next` on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` + * method, the object will be cast to a string and then a stream will be + * returned that uses the string value. * - `NULL`: When `null` is passed, an empty stream object is returned. - * - `callable`: When a callable array, closure, or invokable object is passed - * and no earlier resource or object rule applies, a read-only stream object - * will be created that invokes the given callable. The callable is invoked - * with the suggested number of bytes to read. The callable can return fewer - * or more bytes than requested, but MUST return `false` or `null` when there - * is no more data to return. Any additional bytes will be buffered and used - * in subsequent reads. String inputs are always treated as string bodies, - * even when they name callable functions. + * - `callable`: When a callable array, closure, or invokable object is + * passed and no earlier resource or object rule applies, a read-only + * stream object will be created that invokes the given callable. The + * callable is invoked with the suggested number of bytes to read. The + * callable can return fewer or more bytes than requested, but MUST return + * a non-empty string to provide data and MUST return `false` or `null` + * when there is no more data to return. Any additional bytes will be + * buffered and used in subsequent reads. String inputs are always treated + * as string bodies, even when they name callable functions. * - * Passing a non-string scalar (`int`, `float`, or `bool`) is deprecated; cast - * it to a string instead. guzzlehttp/psr7 3.0 will reject non-string scalars. - * - * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data - * @param array{size?: int, metadata?: array} $options Additional options + * @param resource|string|StreamInterface|callable|\Iterator|\Stringable|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options * * @throws \InvalidArgumentException if the $resource arg is not valid. */ @@ -533,24 +631,15 @@ final class Utils { if (is_scalar($resource)) { if (!is_string($resource)) { - \trigger_deprecation( - 'guzzlehttp/psr7', - '2.12', - 'Passing %s to Utils::streamFor() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string, resource, StreamInterface, Stringable, Iterator, callable, or null.', - \gettype($resource) - ); - - if (is_float($resource) && !is_finite($resource)) { - // Normalized only to avoid PHP 8.5's (string) NAN warning - // while deprecated; 3.0 rejects non-finite floats with every - // other non-string scalar. - $resource = is_nan($resource) ? 'NAN' : ($resource > 0 ? 'INF' : '-INF'); - } + throw new \InvalidArgumentException(\sprintf( + 'Cannot create a stream from %s; pass a string, resource, StreamInterface, Stringable, Iterator, callable, or null.', + \get_debug_type($resource) + )); } $stream = self::tryFopen('php://temp', 'r+'); if ($resource !== '') { - fwrite($stream, (string) $resource); + fwrite($stream, $resource); fseek($stream, 0); } @@ -578,14 +667,29 @@ final class Utils if ($resource instanceof StreamInterface) { return $resource; } elseif ($resource instanceof \Iterator) { - return new PumpStream(function () use ($resource) { - if (!$resource->valid()) { - return false; - } - $result = $resource->current(); - $resource->next(); + return new PumpStream(function (int $length) use ($resource) { + while ($resource->valid()) { + $result = $resource->current(); + $resource->next(); - return $result; + if (is_float($result) && !is_finite($result)) { + throw new \UnexpectedValueException('Iterator must not yield non-finite float values'); + } + + if ($result === null || is_scalar($result)) { + $data = (string) $result; + } elseif (is_object($result) && method_exists($result, '__toString')) { + $data = (string) $result; + } else { + throw new \UnexpectedValueException('Iterator must yield scalar, null, or stringable values'); + } + + if ($data !== '') { + return $data; + } + } + + return false; }, $options); } elseif (method_exists($resource, '__toString')) { return self::streamFor((string) $resource, $options); @@ -599,14 +703,14 @@ final class Utils return new PumpStream($resource, $options); } - throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource)); + throw new \InvalidArgumentException('Invalid resource type: '.\get_debug_type($resource)); } /** * Safely opens a PHP stream resource using a filename. * - * When fopen fails, PHP normally raises a warning. This function adds an - * error handler that checks for errors and throws an exception instead. + * When `fopen()` fails, PHP normally raises a warning. This function adds + * an error handler that checks for errors and throws an exception instead. * * @param string $filename File to open * @param string $mode Mode used to open the file @@ -619,12 +723,7 @@ final class Utils { $ex = null; set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { - $ex = new \RuntimeException(sprintf( - 'Unable to open "%s" using mode "%s": %s', - $filename, - $mode, - $errstr - )); + $ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), DiagnosticValue::escape($errstr))); return true; }); @@ -633,12 +732,7 @@ final class Utils /** @var resource $handle */ $handle = fopen($filename, $mode); } catch (\Throwable $e) { - $ex = new \RuntimeException(sprintf( - 'Unable to open "%s" using mode "%s": %s', - $filename, - $mode, - $e->getMessage() - ), 0, $e); + $ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), $e->getMessage()), 0, $e); } restore_error_handler(); @@ -654,10 +748,13 @@ final class Utils /** * Safely gets the contents of a given stream. * - * When stream_get_contents fails, PHP normally raises a warning. This + * When `stream_get_contents()` fails, PHP normally raises a warning. This * function adds an error handler that checks for errors and throws an * exception instead. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * * @param resource $stream * * @throws \RuntimeException if the stream cannot be read @@ -666,10 +763,7 @@ final class Utils { $ex = null; set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { - $ex = new \RuntimeException(sprintf( - 'Unable to read stream contents: %s', - $errstr - )); + $ex = new \RuntimeException(sprintf('Unable to read stream contents: %s', DiagnosticValue::escape($errstr))); return true; }); @@ -679,13 +773,18 @@ final class Utils $contents = stream_get_contents($stream); if ($contents === false) { - $ex = new \RuntimeException('Unable to read stream contents'); + $ex = StreamTimeout::isResourceReadTimedOut($stream) + ? new TimeoutException('Unable to read stream contents: timed out') + : new \RuntimeException('Unable to read stream contents'); + } elseif (StreamTimeout::isResourceReadTimedOut($stream)) { + $ex = new TimeoutException('Unable to read stream contents: timed out'); } + } catch (TimeoutException $e) { + $ex = $e; } catch (\Throwable $e) { - $ex = new \RuntimeException(sprintf( - 'Unable to read stream contents: %s', - $e->getMessage() - ), 0, $e); + $ex = StreamTimeout::isResourceReadTimedOut($stream) + ? new TimeoutException('Unable to read stream contents: timed out', 0, $e) + : new \RuntimeException(sprintf('Unable to read stream contents: %s', $e->getMessage()), 0, $e); } restore_error_handler(); @@ -699,11 +798,11 @@ final class Utils } /** - * Returns a UriInterface for the given value. + * Returns a `UriInterface` for the given value. * - * This function accepts a string or UriInterface and returns a - * UriInterface for the given value. If the value is already a - * UriInterface, it is returned as-is. + * This function accepts a string or `UriInterface` and returns a + * `UriInterface` for the given value. If the value is already a + * `UriInterface`, it is returned as-is. * * @param string|UriInterface $uri * diff --git a/libs/vendor/ralouphie/getallheaders/README.md b/libs/vendor/ralouphie/getallheaders/README.md deleted file mode 100644 index 9430d76b..00000000 --- a/libs/vendor/ralouphie/getallheaders/README.md +++ /dev/null @@ -1,27 +0,0 @@ -getallheaders -============= - -PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3. - -[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) -[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) -[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) -[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) -[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) - - -This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). - -## Install - -For PHP version **`>= 5.6`**: - -``` -composer require ralouphie/getallheaders -``` - -For PHP version **`< 5.6`**: - -``` -composer require ralouphie/getallheaders "^2" -``` diff --git a/libs/vendor/ralouphie/getallheaders/composer.json b/libs/vendor/ralouphie/getallheaders/composer.json deleted file mode 100644 index de8ce62e..00000000 --- a/libs/vendor/ralouphie/getallheaders/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "ralouphie/getallheaders", - "description": "A polyfill for getallheaders.", - "license": "MIT", - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^5 || ^6.5", - "php-coveralls/php-coveralls": "^2.1" - }, - "autoload": { - "files": ["src/getallheaders.php"] - }, - "autoload-dev": { - "psr-4": { - "getallheaders\\Tests\\": "tests/" - } - } -} diff --git a/libs/vendor/ralouphie/getallheaders/src/getallheaders.php b/libs/vendor/ralouphie/getallheaders/src/getallheaders.php deleted file mode 100644 index c7285a5b..00000000 --- a/libs/vendor/ralouphie/getallheaders/src/getallheaders.php +++ /dev/null @@ -1,46 +0,0 @@ - 'Content-Type', - 'CONTENT_LENGTH' => 'Content-Length', - 'CONTENT_MD5' => 'Content-Md5', - ); - - foreach ($_SERVER as $key => $value) { - if (substr($key, 0, 5) === 'HTTP_') { - $key = substr($key, 5); - if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { - $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); - $headers[$key] = $value; - } - } elseif (isset($copy_server[$key])) { - $headers[$copy_server[$key]] = $value; - } - } - - if (!isset($headers['Authorization'])) { - if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { - $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; - } elseif (isset($_SERVER['PHP_AUTH_USER'])) { - $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; - } - } - - return $headers; - } - -} diff --git a/libs/vendor/symfony/mime/Header/AbstractHeader.php b/libs/vendor/symfony/mime/Header/AbstractHeader.php index 556c0aa0..62b302f5 100644 --- a/libs/vendor/symfony/mime/Header/AbstractHeader.php +++ b/libs/vendor/symfony/mime/Header/AbstractHeader.php @@ -180,17 +180,18 @@ abstract class AbstractHeader implements HeaderInterface $tokens[] = $encodedToken; } - foreach ($tokens as $i => $token) { - // whitespace(s) between 2 encoded tokens - if ( - 0 < $i - && isset($tokens[$i + 1]) - && preg_match('~^[\t ]+$~', $token) - && $this->tokenNeedsEncoding($tokens[$i - 1]) - && $this->tokenNeedsEncoding($tokens[$i + 1]) - ) { - $tokens[$i - 1] .= $token.$tokens[$i + 1]; - array_splice($tokens, $i, 2); + $i = 1; + while (isset($tokens[$i + 1])) { + // whitespace-only token(s) between 2 encoded tokens; a gap of N spaces yields N - 1 of them + $j = $i; + while (preg_match('~^[\t ]+$~', $tokens[$j] ?? '')) { + ++$j; + } + if ($j > $i && isset($tokens[$j]) && $this->tokenNeedsEncoding($tokens[$i - 1]) && $this->tokenNeedsEncoding($tokens[$j])) { + $tokens[$i - 1] .= implode('', \array_slice($tokens, $i, 1 + $j - $i)); + array_splice($tokens, $i, 1 + $j - $i); + } else { + ++$i; } } diff --git a/libs/vendor/ralouphie/getallheaders/LICENSE b/libs/vendor/symfony/polyfill-php82/LICENSE similarity index 86% rename from libs/vendor/ralouphie/getallheaders/LICENSE rename to libs/vendor/symfony/polyfill-php82/LICENSE index be5540c2..733c826e 100644 --- a/libs/vendor/ralouphie/getallheaders/LICENSE +++ b/libs/vendor/symfony/polyfill-php82/LICENSE @@ -1,13 +1,11 @@ -The MIT License (MIT) - -Copyright (c) 2014 Ralph Khattar +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,5 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libs/vendor/symfony/polyfill-php82/NoDynamicProperties.php b/libs/vendor/symfony/polyfill-php82/NoDynamicProperties.php new file mode 100644 index 00000000..450deff4 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/NoDynamicProperties.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @internal + */ +trait NoDynamicProperties +{ + public function __set(string $name, $value): void + { + throw new \Error('Cannot create dynamic property '.self::class.'::$'.$name); + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Php82.php b/libs/vendor/symfony/polyfill-php82/Php82.php new file mode 100644 index 00000000..64b4fad9 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Php82.php @@ -0,0 +1,394 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @author Alexander M. Turek + * @author Greg Roach + * + * @internal + */ +class Php82 +{ + /** + * Determines if a string matches the ODBC quoting rules. + * + * A valid quoted string begins with a '{', ends with a '}', and has no '}' + * inside of the string that aren't repeated (as to be escaped). + * + * These rules are what .NET also follows. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L31-L57 + */ + public static function odbc_connection_string_is_quoted(string $str): bool + { + if ('' === $str || '{' !== $str[0]) { + return false; + } + + /* Check for } that aren't doubled up or at the end of the string */ + $length = \strlen($str) - 1; + for ($i = 0; $i < $length; ++$i) { + if ('}' !== $str[$i]) { + continue; + } + + if ('}' !== $str[++$i]) { + return $i === $length; + } + } + + return true; + } + + /** + * Determines if a value for a connection string should be quoted. + * + * The ODBC specification mentions: + * "Because of connection string and initialization file grammar, keywords and + * attribute values that contain the characters []{}(),;?*=!@ not enclosed + * with braces should be avoided." + * + * Note that it assumes that the string is *not* already quoted. You should + * check beforehand. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L59-L73 + */ + public static function odbc_connection_string_should_quote(string $str): bool + { + return false !== strpbrk($str, '[]{}(),;?*=!@'); + } + + public static function odbc_connection_string_quote(string $str): string + { + return '{'.str_replace('}', '}}', $str).'}'; + } + + /** + * Implementation closely based on the original C code - including the GOTOs + * and pointer-style string access. + * + * @see https://github.com/php/php-src/blob/master/Zend/zend_ini.c + */ + public static function ini_parse_quantity(string $value): int + { + // Avoid dependency on ctype_space() + $ctype_space = " \t\v\r\n\f"; + + $str = 0; + $str_end = \strlen($value); + $digits = $str; + $overflow = false; + + /* Ignore leading whitespace, but keep it for error messages. */ + while ($digits < $str_end && false !== strpos($ctype_space, $value[$digits])) { + ++$digits; + } + + /* Ignore trailing whitespace, but keep it for error messages. */ + while ($digits < $str_end && false !== strpos($ctype_space, $value[$str_end - 1])) { + --$str_end; + } + + if ($digits === $str_end) { + return 0; + } + + $is_negative = false; + + if ('+' === $value[$digits]) { + ++$digits; + } elseif ('-' === $value[$digits]) { + $is_negative = true; + ++$digits; + } + + if ($value[$digits] < '0' || $value[$digits] > 9) { + $message = \sprintf( + 'Invalid quantity "%s": no valid leading digits, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $base = 10; + $allowed_digits = '0123456789'; + + if ('0' === $value[$digits] && ($digits + 1 === $str_end || false === strpos($allowed_digits, $value[$digits + 1]))) { + if ($digits + 1 === $str_end) { + return 0; + } + + switch ($value[$digits + 1]) { + case 'g': + case 'G': + case 'm': + case 'M': + case 'k': + case 'K': + goto evaluation; + case 'x': + case 'X': + $base = 16; + $allowed_digits = '0123456789abcdefABCDEF'; + break; + case 'o': + case 'O': + $base = 8; + $allowed_digits = '01234567'; + break; + case 'b': + case 'B': + $base = 2; + $allowed_digits = '01'; + break; + default: + $message = \sprintf( + 'Invalid prefix "0%s", interpreting as "0" for backwards compatibility', + $value[$digits + 1] + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $digits += 2; + if ($digits === $str_end) { + $message = \sprintf( + 'Invalid quantity "%s": no digits after base prefix, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $digits_consumed = $digits; + /* Ignore leading whitespace. */ + while ($digits_consumed < $str_end && false !== strpos($ctype_space, $value[$digits_consumed])) { + ++$digits_consumed; + } + if ($digits_consumed !== $str_end && ('+' === $value[$digits_consumed] || '-' === $value[$digits_consumed])) { + ++$digits_consumed; + } + + if ('0' === $value[$digits_consumed]) { + /* Value is just 0 */ + if ($digits_consumed + 1 === $str_end) { + goto evaluation; + } + switch ($value[$digits_consumed + 1]) { + case 'x': + case 'X': + case 'o': + case 'O': + case 'b': + case 'B': + $digits_consumed += 2; + break; + } + } + + if ($digits !== $digits_consumed) { + $message = \sprintf( + 'Invalid quantity "%s": no digits after base prefix, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + } + + evaluation: + + if (10 === $base && '0' === $value[$digits]) { + $base = 8; + $allowed_digits = '01234567'; + } + + while ($digits < $str_end && ' ' === $value[$digits]) { + ++$digits; + } + + if ($digits < $str_end && '+' === $value[$digits]) { + ++$digits; + } elseif ($digits < $str_end && '-' === $value[$digits]) { + $is_negative = true; + $overflow = true; + ++$digits; + } + + $digits_end = $digits; + + while ($digits_end < $str_end && false !== strpos($allowed_digits, $value[$digits_end])) { + ++$digits_end; + } + + $retval = base_convert(substr($value, $digits, $digits_end - $digits), $base, 10); + + if ($is_negative && '0' === $retval) { + $is_negative = false; + $overflow = false; + } + + // Check for overflow - remember that -PHP_INT_MIN = 1 + PHP_INT_MAX + if ($is_negative) { + $signed_max = strtr((string) \PHP_INT_MIN, ['-' => '']); + } else { + $signed_max = (string) \PHP_INT_MAX; + } + + $max_length = max(\strlen($retval), \strlen($signed_max)); + + $tmp1 = str_pad($retval, $max_length, '0', \STR_PAD_LEFT); + $tmp2 = str_pad($signed_max, $max_length, '0', \STR_PAD_LEFT); + + if ($tmp1 > $tmp2) { + $retval = -1; + $overflow = true; + } elseif ($is_negative) { + $retval = '-'.$retval; + } + + $retval = (int) $retval; + + if ($digits_end === $digits) { + $message = \sprintf( + 'Invalid quantity "%s": no valid leading digits, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + /* Allow for whitespace between integer portion and any suffix character */ + while ($digits_end < $str_end && false !== strpos($ctype_space, $value[$digits_end])) { + ++$digits_end; + } + + /* No exponent suffix. */ + if ($digits_end === $str_end) { + goto end; + } + + switch ($value[$str_end - 1]) { + case 'g': + case 'G': + $shift = 30; + break; + case 'm': + case 'M': + $shift = 20; + break; + case 'k': + case 'K': + $shift = 10; + break; + default: + /* Unknown suffix */ + $invalid = self::escapeString($value); + $interpreted = self::escapeString(substr($value, $str, $digits_end - $str)); + $chr = self::escapeString($value[$str_end - 1]); + + $message = \sprintf( + 'Invalid quantity "%s": unknown multiplier "%s", interpreting as "%s" for backwards compatibility', + $invalid, + $chr, + $interpreted + ); + + trigger_error($message, \E_USER_WARNING); + + return $retval; + } + + $factor = 1 << $shift; + + if (!$overflow) { + if ($retval > 0) { + $overflow = $retval > \PHP_INT_MAX / $factor; + } else { + $overflow = $retval < \PHP_INT_MIN / $factor; + } + } + + if (\is_float($retval * $factor)) { + $overflow = true; + $retval <<= $shift; + } else { + $retval *= $factor; + } + + if ($digits_end !== $str_end - 1) { + /* More than one character in suffix */ + $message = \sprintf( + 'Invalid quantity "%s", interpreting as "%s%s" for backwards compatibility', + self::escapeString($value), + self::escapeString(substr($value, $str, $digits_end - $str)), + self::escapeString($value[$str_end - 1]) + ); + trigger_error($message, \E_USER_WARNING); + + return $retval; + } + + end: + + if ($overflow) { + /* Not specifying the resulting value here because the caller may make + * additional conversions. Not specifying the allowed range + * because the caller may do narrower range checks. */ + $message = \sprintf( + 'Invalid quantity "%s": value is out of range, using overflow result for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + } + + return $retval; + } + + /** + * Escape the string to avoid null bytes and to make non-printable chars visible. + */ + private static function escapeString(string $string): string + { + $escaped = ''; + + for ($n = 0, $len = \strlen($string); $n < $len; ++$n) { + $c = \ord($string[$n]); + + if ($c < 32 || '\\' === $string[$n] || $c > 126) { + switch ($string[$n]) { + case "\n": $escaped .= '\\n'; break; + case "\r": $escaped .= '\\r'; break; + case "\t": $escaped .= '\\t'; break; + case "\f": $escaped .= '\\f'; break; + case "\v": $escaped .= '\\v'; break; + case '\\': $escaped .= '\\\\'; break; + case "\x1B": $escaped .= '\\e'; break; + default: + $escaped .= '\\x'.strtoupper(\sprintf('%02x', $c)); + } + } else { + $escaped .= $string[$n]; + } + } + + return $escaped; + } +} diff --git a/libs/vendor/symfony/polyfill-php82/README.md b/libs/vendor/symfony/polyfill-php82/README.md new file mode 100644 index 00000000..b3191557 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/README.md @@ -0,0 +1,23 @@ +Symfony Polyfill / Php82 +======================== + +This component provides features added to PHP 8.2 core: + +- [`AllowDynamicProperties`](https://wiki.php.net/rfc/deprecate_dynamic_properties) +- [`SensitiveParameter`](https://wiki.php.net/rfc/redact_parameters_in_back_traces) +- [`SensitiveParameterValue`](https://wiki.php.net/rfc/redact_parameters_in_back_traces) +- [`Random\Engine`](https://wiki.php.net/rfc/rng_extension) +- [`Random\Engine\CryptoSafeEngine`](https://wiki.php.net/rfc/rng_extension) +- [`Random\Engine\Secure`](https://wiki.php.net/rfc/rng_extension) (check [arokettu/random-polyfill](https://packagist.org/packages/arokettu/random-polyfill) for more engines) +- [`odbc_connection_string_is_quoted()`](https://php.net/odbc_connection_string_is_quoted) +- [`odbc_connection_string_should_quote()`](https://php.net/odbc_connection_string_should_quote) +- [`odbc_connection_string_quote()`](https://php.net/odbc_connection_string_quote) +- [`ini_parse_quantity()`](https://php.net/ini_parse_quantity) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/libs/vendor/symfony/polyfill-php82/Random/Engine/Secure.php b/libs/vendor/symfony/polyfill-php82/Random/Engine/Secure.php new file mode 100644 index 00000000..5565386c --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Random/Engine/Secure.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82\Random\Engine; + +use Random\RandomException; +use Symfony\Polyfill\Php82\NoDynamicProperties; + +/** + * @author Tim Düsterhus + * @author Anton Smirnov + * + * @internal + */ +class Secure +{ + use NoDynamicProperties; + + public function generate(): string + { + try { + return random_bytes(\PHP_INT_SIZE); + } catch (\Exception $e) { + throw new RandomException($e->getMessage(), $e->getCode(), $e->getPrevious()); + } + } + + public function __sleep(): array + { + throw new \Exception("Serialization of 'Random\Engine\Secure' is not allowed"); + } + + public function __wakeup(): void + { + throw new \Exception("Unserialization of 'Random\Engine\Secure' is not allowed"); + } + + public function __clone() + { + throw new \Error('Trying to clone an uncloneable object of class Random\Engine\Secure'); + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php new file mode 100644 index 00000000..d216e0ad --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + #[Attribute(Attribute::TARGET_CLASS)] + final class AllowDynamicProperties + { + public function __construct() + { + } + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php new file mode 100644 index 00000000..971ed570 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + class BrokenRandomEngineError extends RandomError + { + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php new file mode 100644 index 00000000..fb32496f --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + interface CryptoSafeEngine extends Engine + { + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php new file mode 100644 index 00000000..4fc78c8f --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + interface Engine + { + public function generate(): string; + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php new file mode 100644 index 00000000..e779b544 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random\Engine; + +use Symfony\Polyfill\Php82 as p; + +if (\PHP_VERSION_ID < 80200) { + final class Secure extends p\Random\Engine\Secure implements \Random\CryptoSafeEngine + { + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php new file mode 100644 index 00000000..bf5e89e0 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +use Symfony\Polyfill\Php82\NoDynamicProperties; + +if (\PHP_VERSION_ID < 80200) { + class RandomError extends \Error + { + use NoDynamicProperties; + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php new file mode 100644 index 00000000..3b9aae14 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +use Symfony\Polyfill\Php82\NoDynamicProperties; + +if (\PHP_VERSION_ID < 80200) { + class RandomException extends \Exception + { + use NoDynamicProperties; + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php new file mode 100644 index 00000000..aea4dfbd --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + #[Attribute(Attribute::TARGET_PARAMETER)] + final class SensitiveParameter + { + public function __construct() + { + } + } +} diff --git a/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php b/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php new file mode 100644 index 00000000..8349170b --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + final class SensitiveParameterValue extends Symfony\Polyfill\Php82\SensitiveParameterValue + { + } +} diff --git a/libs/vendor/symfony/polyfill-php82/SensitiveParameterValue.php b/libs/vendor/symfony/polyfill-php82/SensitiveParameterValue.php new file mode 100644 index 00000000..944c0a65 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/SensitiveParameterValue.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @author Tim Düsterhus + * + * @internal + */ +class SensitiveParameterValue +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } + + public function __debugInfo(): array + { + return []; + } + + public function __sleep(): array + { + throw new \Exception("Serialization of 'SensitiveParameterValue' is not allowed"); + } + + public function __wakeup(): void + { + throw new \Exception("Unserialization of 'SensitiveParameterValue' is not allowed"); + } +} diff --git a/libs/vendor/symfony/polyfill-php82/bootstrap.php b/libs/vendor/symfony/polyfill-php82/bootstrap.php new file mode 100644 index 00000000..b8da6f08 --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/bootstrap.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php82 as p; + +if (\PHP_VERSION_ID >= 80200) { + return; +} + +if (\extension_loaded('curl') && !defined('CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256') && curl_version()['version_number'] >= 0x075000) { + define('CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256', 10311); +} + +if (extension_loaded('odbc')) { + if (!function_exists('odbc_connection_string_is_quoted')) { + function odbc_connection_string_is_quoted(string $str): bool { return p\Php82::odbc_connection_string_is_quoted($str); } + } + + if (!function_exists('odbc_connection_string_should_quote')) { + function odbc_connection_string_should_quote(string $str): bool { return p\Php82::odbc_connection_string_should_quote($str); } + } + + if (!function_exists('odbc_connection_string_quote')) { + function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); } + } +} + +if (!function_exists('ini_parse_quantity')) { + function ini_parse_quantity(string $shorthand): int { return p\Php82::ini_parse_quantity($shorthand); } +} diff --git a/libs/vendor/symfony/polyfill-php82/composer.json b/libs/vendor/symfony/polyfill-php82/composer.json new file mode 100644 index 00000000..2031ae8d --- /dev/null +++ b/libs/vendor/symfony/polyfill-php82/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php82", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php82\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/libs/vendor/symfony/polyfill-php83/bootstrap72.php b/libs/vendor/symfony/polyfill-php83/bootstrap72.php index 94a94174..3da5e401 100644 --- a/libs/vendor/symfony/polyfill-php83/bootstrap72.php +++ b/libs/vendor/symfony/polyfill-php83/bootstrap72.php @@ -11,6 +11,12 @@ use Symfony\Polyfill\Php83 as p; +if (!class_exists('ValueError', false)) { + class ValueError extends Error + { + } +} + if (extension_loaded('mbstring')) { if (!function_exists('mb_str_pad')) { /** @return string|false */ diff --git a/libs/vendor/symfony/polyfill-php85/bootstrap.php b/libs/vendor/symfony/polyfill-php85/bootstrap.php index 00c823fa..c68b01b3 100644 --- a/libs/vendor/symfony/polyfill-php85/bootstrap.php +++ b/libs/vendor/symfony/polyfill-php85/bootstrap.php @@ -41,6 +41,12 @@ if (\PHP_VERSION_ID >= 80000) { return; } +if (!class_exists('ValueError', false)) { + class ValueError extends Error + { + } +} + if (extension_loaded('intl') && !function_exists('grapheme_levenshtein')) { function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Php85::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); } } diff --git a/libs/vendor/zbateson/mail-mime-parser/.github/workflows/tests.yml b/libs/vendor/zbateson/mail-mime-parser/.github/workflows/tests.yml index 12fadbf6..f5caccde 100644 --- a/libs/vendor/zbateson/mail-mime-parser/.github/workflows/tests.yml +++ b/libs/vendor/zbateson/mail-mime-parser/.github/workflows/tests.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - php: [8.5, 8.4, 8.3, 8.2, 8.1, 8.0] + php: [8.5, 8.4, 8.3, 8.2, 8.1] stability: [prefer-stable] name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }} diff --git a/libs/vendor/zbateson/mail-mime-parser/README.md b/libs/vendor/zbateson/mail-mime-parser/README.md index 1298025f..e4e059bf 100644 --- a/libs/vendor/zbateson/mail-mime-parser/README.md +++ b/libs/vendor/zbateson/mail-mime-parser/README.md @@ -3,8 +3,6 @@ Testable and PSR-compliant mail mime parser alternative to PHP's imap* functions and Pear libraries for reading messages in _Internet Message Format_ [RFC 822](http://tools.ietf.org/html/rfc822) (and later revisions [RFC 2822](http://tools.ietf.org/html/rfc2822), [RFC 5322](http://tools.ietf.org/html/rfc5322)). [![Build Status](https://github.com/zbateson/mail-mime-parser/actions/workflows/tests.yml/badge.svg)](https://github.com/zbateson/mail-mime-parser/actions/workflows/tests.yml) -[![Code Coverage](https://scrutinizer-ci.com/g/zbateson/mail-mime-parser/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/zbateson/mail-mime-parser/?branch=master) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zbateson/mail-mime-parser/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/zbateson/mail-mime-parser/?branch=master) [![Total Downloads](https://poser.pugx.org/zbateson/mail-mime-parser/downloads)](//packagist.org/packages/zbateson/mail-mime-parser) [![Latest Stable Version](https://poser.pugx.org/zbateson/mail-mime-parser/v)](//packagist.org/packages/zbateson/mail-mime-parser) @@ -28,23 +26,13 @@ A huge thank you to [all my sponsors](https://github.com/sponsors/zbateson). <3 If this project's helped you, please consider [sponsoring me](https://github.com/sponsors/zbateson). -## Php 7 Support Dropped +## New in 4.0 -As of mail-mime-parser 3.0, support for php 7 has been dropped. - -## New in 3.0 - -Most changes in 3.0 are 'backend' changes, for example switching to PHP-DI for dependency injection, and basic usage should not be affected. - -The header class method 'getAllParts' includes comment parts in 3.0. - -Error, validation, and logging support has been added. - -For a more complete list of changes, please visit the [3.0 Upgrade Guide](https://mail-mime-parser.org/upgrade-3.0) and the [Usage Guide](https://mail-mime-parser.org/). +Version 4.0 requires PHP 8.1+ and focuses on API cleanup and improved configurability. For details, see the [4.0 Upgrade Guide](https://mail-mime-parser.org/upgrade-4.0). ## Requirements -MailMimeParser requires PHP 8.0 or newer. Tested on PHP 8.0, 8.1, 8.2, 8.3 and 8.4. +MailMimeParser requires PHP 8.1 or newer. Tested on PHP 8.1, 8.2, 8.3, 8.4 and 8.5. ## Usage @@ -109,16 +97,29 @@ fclose($handle); ``` +## Encryption and Signing Plugins + +Optional companion packages add S/MIME and PGP/MIME support for decrypting, +encrypting, signing and verifying messages: + +* [zbateson/mmp-crypt-smime](https://github.com/zbateson/mmp-crypt-smime) -- S/MIME via PHP's OpenSSL extension +* [zbateson/mmp-crypt-gpg](https://github.com/zbateson/mmp-crypt-gpg) -- PGP/MIME via PEAR's Crypt_GPG + +Install either package and encrypted/signed messages are automatically detected +and decrypted during parsing. See the [Usage Guide](https://mail-mime-parser.org/#encryption-and-signing) +for examples of reading encrypted messages and composing signed/encrypted ones. + ## Documentation * [Usage Guide](https://mail-mime-parser.org/) -* [API Reference](https://mail-mime-parser.org/api/3.0) +* [API Reference](https://mail-mime-parser.org/api/4.0) ## Upgrade guides * [1.x Upgrade Guide](https://mail-mime-parser.org/upgrade-1.0) * [2.x Upgrade Guide](https://mail-mime-parser.org/upgrade-2.0) * [3.x Upgrade Guide](https://mail-mime-parser.org/upgrade-3.0) +* [4.x Upgrade Guide](https://mail-mime-parser.org/upgrade-4.0) ## License diff --git a/libs/vendor/zbateson/mail-mime-parser/composer.json b/libs/vendor/zbateson/mail-mime-parser/composer.json index f6ffe335..84e5497c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/composer.json +++ b/libs/vendor/zbateson/mail-mime-parser/composer.json @@ -19,17 +19,17 @@ "docs": "https://mail-mime-parser.org/#usage-guide" }, "require": { - "php": ">=8.0", - "guzzlehttp/psr7": "^2.5", - "zbateson/mb-wrapper": "^2.0", - "zbateson/stream-decorators": "^2.1", + "php": ">=8.1", + "guzzlehttp/psr7": "^2.5 || ^3.0", + "zbateson/mb-wrapper": "^2.0 || ^3.0", + "zbateson/stream-decorators": "^2.1 || ^3.0", "php-di/php-di": "^6.0|^7.0", "psr/log": "^1|^2|^3" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", + "phpunit/phpunit": "^10.5", + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^2.0", "monolog/monolog": "^2|^3" }, "suggest": { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Error.php b/libs/vendor/zbateson/mail-mime-parser/src/Error.php index 544cda20..2f17953e 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Error.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Error.php @@ -19,26 +19,6 @@ use Throwable; */ class Error { - /** - * @var string The error message. - */ - protected string $message; - - /** - * @var string The PSR log level for this error. - */ - protected string $psrLevel; - - /** - * @var ErrorBag The object the error/notice occurred on. - */ - protected ErrorBag $object; - - /** - * @var ?Throwable An Exception object if one happened, or null if not - */ - protected ?Throwable $exception; - /** * @var array */ @@ -55,18 +35,18 @@ class Error /** * - * @throws InvalidArgumentException if the passed $psrLogLevelAsErrorLevel + * @throws InvalidArgumentException if the passed $psrLevel * is not a known PSR log level (see \Psr\Log\LogLevel) */ - public function __construct(string $message, string $psrLogLevelAsErrorLevel, ErrorBag $object, ?Throwable $exception = null) - { - if (!isset($this->levelMap[$psrLogLevelAsErrorLevel])) { - throw new InvalidArgumentException($psrLogLevelAsErrorLevel . ' is not a known PSR Log Level'); + public function __construct( + protected readonly string $message, + protected readonly string $psrLevel, + protected readonly ErrorBag $object, + protected readonly ?Throwable $exception = null + ) { + if (!isset($this->levelMap[$psrLevel])) { + throw new InvalidArgumentException($psrLevel . ' is not a known PSR Log Level'); } - $this->message = $message; - $this->psrLevel = $psrLogLevelAsErrorLevel; - $this->object = $object; - $this->exception = $exception; } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/ErrorBag.php b/libs/vendor/zbateson/mail-mime-parser/src/ErrorBag.php index 030a98e1..3a9cfae8 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/ErrorBag.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/ErrorBag.php @@ -18,8 +18,6 @@ use Throwable; */ abstract class ErrorBag implements IErrorBag { - protected LoggerInterface $logger; - /** * @var Error[] array of Error objects belonging to this object. */ @@ -30,9 +28,8 @@ abstract class ErrorBag implements IErrorBag */ private bool $validated = false; - public function __construct(LoggerInterface $logger) + public function __construct(protected LoggerInterface $logger) { - $this->logger = $logger; } /** @@ -95,9 +92,7 @@ abstract class ErrorBag implements IErrorBag } return \array_values(\array_filter( $this->errors, - function($e) use ($minPsrLevel) { - return $e->isPsrLevelGreaterOrEqualTo($minPsrLevel); - } + fn($e) => $e->isPsrLevelGreaterOrEqualTo($minPsrLevel) )); } @@ -109,9 +104,7 @@ abstract class ErrorBag implements IErrorBag public function getAllErrors(bool $validate = false, string $minPsrLevel = LogLevel::ERROR) : array { $arr = \array_values(\array_map( - function($e) use ($validate, $minPsrLevel) { - return $e->getAllErrors($validate, $minPsrLevel); - }, + fn($e) => $e->getAllErrors($validate, $minPsrLevel), $this->getErrorBagChildren() )); return \array_merge($this->getErrors($validate, $minPsrLevel), ...$arr); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/AbstractHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/AbstractHeader.php index 7ee333bf..9a2de7bc 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/AbstractHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/AbstractHeader.php @@ -50,6 +50,21 @@ abstract class AbstractHeader extends ErrorBag implements IHeader */ private ?array $comments = null; + /** + * Resolves a nullable service from the global DI container, returning the + * provided instance if non-null. + * + * @template T of object + * @param T|null $service + * @param class-string $class + * @return T + */ + protected static function resolveService(?object $service, string $class): object + { + /** @var T */ + return $service ?? MailMimeParser::getGlobalContainer()->get($class); + } + /** * Assigns the header's name and raw value, then calls parseHeaderValue to * extract a parsed value. @@ -78,9 +93,7 @@ abstract class AbstractHeader extends ErrorBag implements IHeader */ protected function filterAndAssignToParts() : void { - $this->parts = \array_values(\array_filter($this->allParts, function($p) { - return !($p instanceof CommentPart); - })); + $this->parts = \array_values(\array_filter($this->allParts, fn($p) => !($p instanceof CommentPart))); } /** @@ -139,6 +152,11 @@ abstract class AbstractHeader extends ErrorBag implements IHeader return $this->rawValue; } + public function getDecodedValue() : string + { + return \implode('', \array_map(fn(IHeaderPart $p) => $p->getValue(), $this->parts)); + } + public function getName() : string { return $this->name; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/AddressHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/AddressHeader.php index a6c08115..c5838d2c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/AddressHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/AddressHeader.php @@ -11,7 +11,6 @@ use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\AddressBaseConsumerService; use ZBateson\MailMimeParser\Header\Part\AddressGroupPart; use ZBateson\MailMimeParser\Header\Part\AddressPart; -use ZBateson\MailMimeParser\MailMimeParser; /** * A header containing one or more email addresses and/or groups of addresses. @@ -44,10 +43,9 @@ class AddressHeader extends AbstractHeader ?LoggerInterface $logger = null, ?AddressBaseConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(AddressBaseConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, AddressBaseConsumerService::class), $name, $value ); @@ -55,7 +53,7 @@ class AddressHeader extends AbstractHeader /** * Filters $this->allParts into the parts required by $this->parts - * and assignes it. + * and assigns it. * * The AbstractHeader::filterAndAssignToParts method filters out CommentParts. */ @@ -109,6 +107,30 @@ class AddressHeader extends AbstractHeader return false; } + public function getDecodedValue() : string + { + $parts = []; + foreach ($this->parts as $part) { + if ($part instanceof AddressGroupPart) { + $addrs = \array_map(fn(AddressPart $a) => $this->formatAddress($a), $part->getAddresses()); + $parts[] = $part->getName() . ': ' . \implode(', ', $addrs) . ';'; + } elseif ($part instanceof AddressPart) { + $parts[] = $this->formatAddress($part); + } + } + return \implode(', ', $parts); + } + + private function formatAddress(AddressPart $address) : string + { + $name = $address->getName(); + $email = $address->getEmail(); + if ($name !== '') { + return $name . ' <' . $email . '>'; + } + return $email; + } + /** * Returns the first email address in the header. * diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AbstractConsumerService.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AbstractConsumerService.php index f324555b..195888ac 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AbstractConsumerService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AbstractConsumerService.php @@ -25,19 +25,6 @@ use ZBateson\MailMimeParser\Header\Part\MimeToken; */ abstract class AbstractConsumerService implements IConsumerService { - protected LoggerInterface $logger; - - /** - * @var HeaderPartFactory used to construct IHeaderPart objects - */ - protected HeaderPartFactory $partFactory; - - /** - * @var AbstractConsumerService[] array of sub-consumers used by this - * consumer if any, or an empty array if none exist. - */ - protected array $subConsumers = []; - /** * @var ?string the generated token split pattern on first run, so it doesn't * need to be regenerated every time. @@ -47,11 +34,11 @@ abstract class AbstractConsumerService implements IConsumerService /** * @param AbstractConsumerService[] $subConsumers */ - public function __construct(LoggerInterface $logger, HeaderPartFactory $partFactory, array $subConsumers = []) - { - $this->logger = $logger; - $this->partFactory = $partFactory; - $this->subConsumers = $subConsumers; + public function __construct( + protected readonly LoggerInterface $logger, + protected readonly HeaderPartFactory $partFactory, + protected array $subConsumers = [] + ) { } public function __invoke(string $value) : array diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressBaseConsumerService.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressBaseConsumerService.php index 8a312c3d..38a72c59 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressBaseConsumerService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressBaseConsumerService.php @@ -61,7 +61,7 @@ class AddressBaseConsumerService extends AbstractConsumerService * AddressBaseConsumerService doesn't have start/end tokens, and so always * returns false. * - * @return false + * @return bool */ protected function isEndToken(string $token) : bool { @@ -73,7 +73,7 @@ class AddressBaseConsumerService extends AbstractConsumerService * returns false. * * @codeCoverageIgnore - * @return false + * @return bool */ protected function isStartToken(string $token) : bool { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressGroupConsumerService.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressGroupConsumerService.php index e613ff42..f1b6f704 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressGroupConsumerService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/AddressGroupConsumerService.php @@ -9,6 +9,7 @@ namespace ZBateson\MailMimeParser\Header\Consumer; use Psr\Log\LoggerInterface; use Iterator; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MailMimeParser\Header\Part\AddressGroupPart; use ZBateson\MailMimeParser\Header\Part\HeaderPartFactory; @@ -78,7 +79,7 @@ class AddressGroupConsumerService extends AddressBaseConsumerService * addresses, since a group could be empty. * * @param Iterator $tokens - * @return IHeaderPart[] + * @return \ZBateson\MailMimeParser\Header\IHeaderPart[] */ protected function parseTokensIntoParts(Iterator $tokens) : array { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/Received/GenericReceivedConsumerService.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/Received/GenericReceivedConsumerService.php index 4e623677..54583af8 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/Received/GenericReceivedConsumerService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Consumer/Received/GenericReceivedConsumerService.php @@ -39,14 +39,6 @@ use ZBateson\MailMimeParser\Header\Part\HeaderPartFactory; */ class GenericReceivedConsumerService extends AbstractGenericConsumerService { - /** - * @var string the current part name being parsed. - * - * This is always the lower-case name provided to the constructor, not the - * actual string that started the consumer, which could be in any case. - */ - protected $partName; - /** * Constructor overridden to include $partName parameter. * @@ -55,10 +47,9 @@ class GenericReceivedConsumerService extends AbstractGenericConsumerService LoggerInterface $logger, HeaderPartFactory $partFactory, CommentConsumerService $commentConsumerService, - string $partName + protected readonly string $partName ) { parent::__construct($logger, $partFactory, [$commentConsumerService]); - $this->partName = $partName; } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/DateHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/DateHeader.php index 6b6a9c85..10998983 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/DateHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/DateHeader.php @@ -12,7 +12,6 @@ use DateTimeImmutable; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\DateConsumerService; use ZBateson\MailMimeParser\Header\Part\DatePart; -use ZBateson\MailMimeParser\MailMimeParser; /** * Reads a DatePart value header in either RFC 2822 or RFC 822 format. @@ -27,10 +26,9 @@ class DateHeader extends AbstractHeader ?LoggerInterface $logger = null, ?DateConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(DateConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, DateConsumerService::class), $name, $value ); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/GenericHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/GenericHeader.php index ac0d3007..f91c9dfa 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/GenericHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/GenericHeader.php @@ -9,7 +9,6 @@ namespace ZBateson\MailMimeParser\Header; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\GenericConsumerMimeLiteralPartService; -use ZBateson\MailMimeParser\MailMimeParser; /** * Reads a generic header. @@ -27,20 +26,18 @@ class GenericHeader extends AbstractHeader ?LoggerInterface $logger = null, ?GenericConsumerMimeLiteralPartService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(DateConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, GenericConsumerMimeLiteralPartService::class), $name, $value ); - parent::__construct($logger, $consumerService, $name, $value); } public function getValue() : ?string { if (!empty($this->parts)) { - return \implode('', \array_map(function($p) { return $p->getValue(); }, $this->parts)); + return \implode('', \array_map(fn($p) => $p->getValue(), $this->parts)); } return null; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/HeaderFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/HeaderFactory.php index d5324a67..65afa130 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/HeaderFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/HeaderFactory.php @@ -9,6 +9,7 @@ namespace ZBateson\MailMimeParser\Header; use Psr\Log\LoggerInterface; use ReflectionClass; +use ReflectionNamedType; use ZBateson\MailMimeParser\Header\Consumer\AddressBaseConsumerService; use ZBateson\MailMimeParser\Header\Consumer\DateConsumerService; use ZBateson\MailMimeParser\Header\Consumer\GenericConsumerMimeLiteralPartService; @@ -41,22 +42,15 @@ use ZBateson\MailMimeParser\Header\Part\MimeTokenPartFactory; */ class HeaderFactory { - protected LoggerInterface $logger; - /** * @var IConsumerService[] array of available consumer service classes */ protected array $consumerServices; - /** - * @var MimeTokenPartFactory for mime decoding. - */ - protected MimeTokenPartFactory $mimeTokenPartFactory; - /** * @var string[][] maps IHeader types to headers. */ - protected $types = [ + protected array $types = [ AddressHeader::class => [ 'from', 'to', @@ -106,11 +100,11 @@ class HeaderFactory * @var string Defines the generic IHeader type to use for headers that * aren't mapped in $types */ - protected $genericType = GenericHeader::class; + protected string $genericType = GenericHeader::class; public function __construct( - LoggerInterface $logger, - MimeTokenPartFactory $mimeTokenPartFactory, + protected readonly LoggerInterface $logger, + protected readonly MimeTokenPartFactory $mimeTokenPartFactory, AddressBaseConsumerService $addressBaseConsumerService, DateConsumerService $dateConsumerService, GenericConsumerMimeLiteralPartService $genericConsumerMimeLiteralPartService, @@ -119,8 +113,6 @@ class HeaderFactory ReceivedConsumerService $receivedConsumerService, SubjectConsumerService $subjectConsumerService ) { - $this->logger = $logger; - $this->mimeTokenPartFactory = $mimeTokenPartFactory; $this->consumerServices = [ AddressBaseConsumerService::class => $addressBaseConsumerService, DateConsumerService::class => $dateConsumerService, @@ -195,19 +187,23 @@ class HeaderFactory $ref = new ReflectionClass($iHeaderClass); $params = $ref->getConstructor()->getParameters(); if ($ref->isSubclassOf(MimeEncodedHeader::class)) { + $type = $params[4]->getType(); + \assert($type instanceof ReflectionNamedType); return new $iHeaderClass( $name, $value, $this->logger, $this->mimeTokenPartFactory, - $this->consumerServices[$params[4]->getType()->getName()] + $this->consumerServices[$type->getName()] ); } + $type = $params[3]->getType(); + \assert($type instanceof ReflectionNamedType); return new $iHeaderClass( $name, $value, $this->logger, - $this->consumerServices[$params[3]->getType()->getName()] + $this->consumerServices[$type->getName()] ); } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeader.php index 97c48e5c..fa099c91 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeader.php @@ -55,7 +55,7 @@ interface IHeader extends IErrorBag * or parameter headers (Content-Type), the 'value' is the value of the * first parsed part that isn't a comment. * - * @return string The value + * @return ?string The value, or null if the header has no parts. */ public function getValue() : ?string; @@ -66,6 +66,19 @@ interface IHeader extends IErrorBag */ public function getRawValue() : string; + /** + * Returns the full decoded and unfolded value of the header, reconstructed + * from the parsed parts (excluding comments). + * + * Unlike getValue() which returns only the value of the first part, this + * returns the full decoded representation. For example, for an + * AddressHeader with `=?UTF-8?Q?J=C3=B6hn?= `, this + * would return `Jöhn `. + * + * @return string The full decoded value. + */ + public function getDecodedValue() : string; + /** * Returns the name of the header. * diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeaderPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeaderPart.php index e439e2e8..1f45a8b9 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeaderPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/IHeaderPart.php @@ -30,7 +30,7 @@ interface IHeaderPart extends IErrorBag, Stringable /** * Returns any CommentParts under this part container. * - * @return CommentPart[] + * @return Part\CommentPart[] */ public function getComments() : array; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/IdHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/IdHeader.php index ddb525dc..672c8a6c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/IdHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/IdHeader.php @@ -11,7 +11,6 @@ use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\IdBaseConsumerService; use ZBateson\MailMimeParser\Header\Part\CommentPart; use ZBateson\MailMimeParser\Header\Part\MimeTokenPartFactory; -use ZBateson\MailMimeParser\MailMimeParser; /** * Represents a Content-ID, Message-ID, In-Reply-To or References header. @@ -31,11 +30,10 @@ class IdHeader extends MimeEncodedHeader ?MimeTokenPartFactory $mimeTokenPartFactory = null, ?IdBaseConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $mimeTokenPartFactory ?? $di->get(MimeTokenPartFactory::class), - $consumerService ?? $di->get(IdBaseConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($mimeTokenPartFactory, MimeTokenPartFactory::class), + self::resolveService($consumerService, IdBaseConsumerService::class), $name, $value ); @@ -60,12 +58,8 @@ class IdHeader extends MimeEncodedHeader public function getIds() : array { return \array_values(\array_map( - function($p) { - return $p->getValue(); - }, - \array_filter($this->parts, function($p) { - return !($p instanceof CommentPart); - }) + fn($p) => $p->getValue(), + \array_filter($this->parts, fn($p) => !($p instanceof CommentPart)) )); } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/MimeEncodedHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/MimeEncodedHeader.php index 176a158a..9b3c8966 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/MimeEncodedHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/MimeEncodedHeader.php @@ -21,24 +21,18 @@ use ZBateson\MailMimeParser\Header\Part\MimeTokenPartFactory; abstract class MimeEncodedHeader extends AbstractHeader { /** - * @var MimeTokenPartFactory for mime decoding. - */ - protected MimeTokenPartFactory $mimeTokenPartFactory; - - /** - * @var MimeLiteralPart[] the mime encoded parsed parts contained in this + * @var IHeaderPart[] the mime encoded parsed parts contained in this * header */ - protected $mimeEncodedParsedParts = []; + protected array $mimeEncodedParsedParts = []; public function __construct( LoggerInterface $logger, - MimeTokenPartFactory $mimeTokenPartFactory, + protected readonly MimeTokenPartFactory $mimeTokenPartFactory, IConsumerService $consumerService, string $name, string $value ) { - $this->mimeTokenPartFactory = $mimeTokenPartFactory; parent::__construct($logger, $consumerService, $name, $value); } @@ -52,7 +46,7 @@ abstract class MimeEncodedHeader extends AbstractHeader // whitespace between parts, etc... $matchp = '~(' . MimeToken::MIME_PART_PATTERN . ')~'; $aMimeParts = \preg_split($matchp, $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - $this->mimeEncodedParsedParts = \array_map([$this->mimeTokenPartFactory, 'newInstance'], $aMimeParts); + $this->mimeEncodedParsedParts = \array_map($this->mimeTokenPartFactory->newInstance(...), $aMimeParts); parent::parseHeaderValue( $consumer, \implode('', \array_map(fn ($part) => $part->getValue(), $this->mimeEncodedParsedParts)) @@ -61,6 +55,6 @@ abstract class MimeEncodedHeader extends AbstractHeader protected function getErrorBagChildren() : array { - return \array_values(\array_filter(\array_merge($this->getAllParts(), $this->mimeEncodedParsedParts))); + return \array_values(\array_merge($this->getAllParts(), $this->mimeEncodedParsedParts)); } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/ParameterHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/ParameterHeader.php index 5a88f6b1..0f552e20 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/ParameterHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/ParameterHeader.php @@ -11,7 +11,6 @@ use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\IConsumerService; use ZBateson\MailMimeParser\Header\Consumer\ParameterConsumerService; use ZBateson\MailMimeParser\Header\Part\NameValuePart; -use ZBateson\MailMimeParser\MailMimeParser; /** * Represents a header containing an optional main value part and subsequent @@ -36,8 +35,8 @@ use ZBateson\MailMimeParser\MailMimeParser; class ParameterHeader extends AbstractHeader { /** - * @var ParameterPart[] key map of lower-case parameter names and associated - * ParameterParts. + * @var array key map of lower-case parameter names and associated + * NameValueParts. */ protected array $parameters = []; @@ -47,10 +46,9 @@ class ParameterHeader extends AbstractHeader ?LoggerInterface $logger = null, ?ParameterConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(ParameterConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, ParameterConsumerService::class), $name, $value ); @@ -70,6 +68,17 @@ class ParameterHeader extends AbstractHeader } } + public function getDecodedValue() : string + { + $value = $this->getValue() ?? ''; + foreach ($this->parameters as $param) { + if ($param->getName() !== '') { + $value .= '; ' . $param->getName() . '=' . $param->getValue(); + } + } + return $value; + } + /** * Returns true if a parameter exists with the passed name. * diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/AddressGroupPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/AddressGroupPart.php index 02aabe5d..c75be091 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/AddressGroupPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/AddressGroupPart.php @@ -9,6 +9,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -27,8 +28,8 @@ class AddressGroupPart extends NameValuePart * Creates an AddressGroupPart out of the passed array of AddressParts/ * AddressGroupParts and name. * - * @param HeaderPart[] $nameParts - * @param AddressPart[]|AddressGroupPart[] $addressesAndGroupParts + * @param IHeaderPart[] $nameParts + * @param IHeaderPart[] $addressesAndGroupParts */ public function __construct( LoggerInterface $logger, @@ -76,7 +77,7 @@ class AddressGroupPart extends NameValuePart protected function validate() : void { - if ($this->name === null || \mb_strlen($this->name) === 0) { + if (\mb_strlen($this->name) === 0) { $this->addError('Address group doesn\'t have a name', LogLevel::ERROR); } if (empty($this->addresses)) { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/CommentPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/CommentPart.php index 92b1f4f8..08999ba9 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/CommentPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/CommentPart.php @@ -8,6 +8,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -28,6 +29,9 @@ class CommentPart extends ContainerPart */ protected string $comment; + /** + * @param array $children + */ public function __construct( LoggerInterface $logger, MbWrapper $charsetConverter, diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ContainerPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ContainerPart.php index b0189080..4a18f00e 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ContainerPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ContainerPart.php @@ -9,6 +9,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\ErrorBag; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -27,11 +28,14 @@ use ZBateson\MbWrapper\MbWrapper; class ContainerPart extends HeaderPart { /** - * @var HeaderPart[] parts that were used to create this part, collected for + * @var IHeaderPart[] parts that were used to create this part, collected for * proper error reporting and validation. */ - protected $children = []; + protected array $children = []; + /** + * @param array $children + */ public function __construct( LoggerInterface $logger, MbWrapper $charsetConverter, @@ -54,8 +58,8 @@ class ContainerPart extends HeaderPart * Spaces are removed if parts on either side of it have their * canIgnoreSpaceAfter/canIgnoreSpaceBefore properties set to true. * - * @param HeaderPart[] $parts - * @return HeaderPart[] + * @param IHeaderPart[] $parts + * @return IHeaderPart[] */ protected function filterIgnoredSpaces(array $parts) : array { @@ -91,7 +95,7 @@ class ContainerPart extends HeaderPart * The default implementation filters out ignorable whitespace between * parts, and concatenates parts calling 'getValue'. * - * @param HeaderParts[] $parts + * @param IHeaderPart[] $parts */ protected function getValueFromParts(array $parts) : string { @@ -119,10 +123,11 @@ class ContainerPart extends HeaderPart /** * Returns this part's children, same as getChildParts(). * - * @return ErrorBag + * @return array */ protected function getErrorBagChildren() : array { + /** @var array */ return $this->children; } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/DatePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/DatePart.php index 09f72ae5..a3c568b4 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/DatePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/DatePart.php @@ -11,6 +11,7 @@ use DateTime; use Exception; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -31,7 +32,7 @@ class DatePart extends ContainerPart * an RFC 822 date, and failing that, tries to parse it by calling * new DateTime($value). * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function __construct( LoggerInterface $logger, diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPart.php index 8455b38a..7c9aced9 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPart.php @@ -21,19 +21,6 @@ use ZBateson\MbWrapper\UnsupportedCharsetException; */ abstract class HeaderPart extends ErrorBag implements IHeaderPart { - /** - * @var string the representative value of the part after any conversion or - * processing has been done on it (e.g. removing new lines, converting, - * whatever else). - */ - protected string $value; - - /** - * @var MbWrapper $charsetConverter the charset converter used for - * converting strings in HeaderPart::convertEncoding - */ - protected MbWrapper $charsetConverter; - /** * @var bool set to true to ignore spaces before this part */ @@ -49,11 +36,12 @@ abstract class HeaderPart extends ErrorBag implements IHeaderPart */ protected bool $isSpace = false; - public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter, string $value) - { + public function __construct( + LoggerInterface $logger, + protected MbWrapper $charsetConverter, + protected string $value + ) { parent::__construct($logger); - $this->charsetConverter = $charsetConverter; - $this->value = $value; } /** @@ -101,6 +89,9 @@ abstract class HeaderPart extends ErrorBag implements IHeaderPart return $str; } + /** + * @return CommentPart[] + */ public function getComments() : array { return []; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPartFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPartFactory.php index 864cad01..81ca4a0f 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPartFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/HeaderPartFactory.php @@ -18,18 +18,10 @@ use ZBateson\MbWrapper\MbWrapper; */ class HeaderPartFactory { - /** - * @var MbWrapper $charsetConverter passed to IHeaderPart constructors - * for converting strings in IHeaderPart::convertEncoding - */ - protected MbWrapper $charsetConverter; - - protected LoggerInterface $logger; - - public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter) - { - $this->logger = $logger; - $this->charsetConverter = $charsetConverter; + public function __construct( + protected readonly LoggerInterface $logger, + protected readonly MbWrapper $charsetConverter + ) { } /** @@ -70,7 +62,7 @@ class HeaderPartFactory /** * Initializes and returns a new ContainerPart. * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function newContainerPart(array $children) : ContainerPart { @@ -90,7 +82,7 @@ class HeaderPartFactory /** * Initializes and returns a new QuotedLiteralPart. * - * @param HeaderPart[] $parts + * @param IHeaderPart[] $parts */ public function newQuotedLiteralPart(array $parts) : QuotedLiteralPart { @@ -100,7 +92,7 @@ class HeaderPartFactory /** * Initializes and returns a new CommentPart. * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function newCommentPart(array $children) : CommentPart { @@ -110,8 +102,8 @@ class HeaderPartFactory /** * Initializes and returns a new AddressPart. * - * @param HeaderPart[] $nameParts - * @param HeaderPart[] $emailParts + * @param IHeaderPart[] $nameParts + * @param IHeaderPart[] $emailParts */ public function newAddress(array $nameParts, array $emailParts) : AddressPart { @@ -121,8 +113,8 @@ class HeaderPartFactory /** * Initializes and returns a new AddressGroupPart * - * @param HeaderPart[] $nameParts - * @param AddressPart[]|AddressGroupPart[] $addressesAndGroups + * @param IHeaderPart[] $nameParts + * @param IHeaderPart[] $addressesAndGroups */ public function newAddressGroupPart(array $nameParts, array $addressesAndGroups) : AddressGroupPart { @@ -132,7 +124,7 @@ class HeaderPartFactory /** * Initializes and returns a new DatePart * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function newDatePart(array $children) : DatePart { @@ -142,7 +134,7 @@ class HeaderPartFactory /** * Initializes and returns a new ParameterPart. * - * @param HeaderPart[] $nameParts + * @param IHeaderPart[] $nameParts */ public function newParameterPart(array $nameParts, ContainerPart $valuePart) : ParameterPart { @@ -152,7 +144,7 @@ class HeaderPartFactory /** * Initializes and returns a new ReceivedPart. * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function newReceivedPart(string $name, array $children) : ReceivedPart { @@ -162,7 +154,7 @@ class HeaderPartFactory /** * Initializes and returns a new ReceivedDomainPart. * - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function newReceivedDomainPart(string $name, array $children) : ReceivedDomainPart { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/MimeToken.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/MimeToken.php index 8e1002ad..81a14bfe 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/MimeToken.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/MimeToken.php @@ -81,10 +81,7 @@ class MimeToken extends Token } $this->charset = $matches[1]; $this->language = (!empty($matches[2])) ? $matches[2] : null; - if ($this->charset !== null) { - return $this->convertEncoding($body, $this->charset, true); - } - return $this->convertEncoding($body, 'ISO-8859-1', true); + return $this->convertEncoding($body, $this->charset, true); } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/NameValuePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/NameValuePart.php index 618d1e67..f80990eb 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/NameValuePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/NameValuePart.php @@ -10,6 +10,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use ZBateson\MailMimeParser\ErrorBag; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -24,6 +25,10 @@ class NameValuePart extends ContainerPart */ protected string $name; + /** + * @param array $nameParts + * @param array $valueParts + */ public function __construct( LoggerInterface $logger, MbWrapper $charsetConverter, @@ -41,7 +46,7 @@ class NameValuePart extends ContainerPart * Creates the string 'name' representation of this part constructed from * the child name parts passed to it. * - * @param HeaderParts[] $parts + * @param IHeaderPart[] $parts */ protected function getNameFromParts(array $parts) : string { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ParameterPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ParameterPart.php index ed5b4b8c..c46ebfeb 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ParameterPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ParameterPart.php @@ -8,6 +8,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -40,7 +41,7 @@ class ParameterPart extends NameValuePart protected bool $encoded = false; /** - * @param HeaderPart[] $nameParts + * @param IHeaderPart[] $nameParts */ public function __construct( LoggerInterface $logger, diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedDomainPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedDomainPart.php index c514141d..71c22580 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedDomainPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedDomainPart.php @@ -8,6 +8,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -48,7 +49,7 @@ class ReceivedDomainPart extends ReceivedPart protected ?string $address = null; /** - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function __construct( LoggerInterface $logger, diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedPart.php index e960f96f..23b86efc 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/ReceivedPart.php @@ -8,6 +8,7 @@ namespace ZBateson\MailMimeParser\Header\Part; use Psr\Log\LoggerInterface; +use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MbWrapper\MbWrapper; /** @@ -23,7 +24,7 @@ use ZBateson\MbWrapper\MbWrapper; class ReceivedPart extends NameValuePart { /** - * @param HeaderPart[] $children + * @param IHeaderPart[] $children */ public function __construct( LoggerInterface $logger, diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/SplitParameterPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/SplitParameterPart.php index 6c3ff178..2d23efde 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/SplitParameterPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/Part/SplitParameterPart.php @@ -47,6 +47,9 @@ class SplitParameterPart extends ParameterPart return $parts[0]->getName(); } + /** + * @return array + */ private function getMimeTokens(string $value) : array { $pattern = MimeToken::MIME_PART_PATTERN; @@ -60,6 +63,10 @@ class SplitParameterPart extends ParameterPart ); } + /** + * @param array $parts + * @return array + */ private function combineAdjacentUnencodedParts(array $parts) : array { $runningValue = ''; @@ -81,6 +88,9 @@ class SplitParameterPart extends ParameterPart return $returnedParts; } + /** + * @param array $parts + */ protected function getValueFromParts(array $parts) : string { $sorted = $parts; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/ReceivedHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/ReceivedHeader.php index ec8f974c..0a580728 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/ReceivedHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/ReceivedHeader.php @@ -11,7 +11,7 @@ use DateTime; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\ReceivedConsumerService; use ZBateson\MailMimeParser\Header\Part\DatePart; -use ZBateson\MailMimeParser\MailMimeParser; +use ZBateson\MailMimeParser\Header\Part\ReceivedDomainPart; /** * Represents a Received header. @@ -80,6 +80,12 @@ use ZBateson\MailMimeParser\MailMimeParser; */ class ReceivedHeader extends ParameterHeader { + /** + * @var array key map of lower-case parameter names and associated + * ReceivedDomainParts. + */ + protected array $parameters = []; + /** * @var DateTime the date/time stamp in the header. */ @@ -96,10 +102,9 @@ class ReceivedHeader extends ParameterHeader ?LoggerInterface $logger = null, ?ReceivedConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); AbstractHeader::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(ReceivedConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, ReceivedConsumerService::class), $name, $value ); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Header/SubjectHeader.php b/libs/vendor/zbateson/mail-mime-parser/src/Header/SubjectHeader.php index bbadc5f8..45146b95 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Header/SubjectHeader.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Header/SubjectHeader.php @@ -9,7 +9,6 @@ namespace ZBateson\MailMimeParser\Header; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Header\Consumer\SubjectConsumerService; -use ZBateson\MailMimeParser\MailMimeParser; /** * Reads a subject header. @@ -27,10 +26,9 @@ class SubjectHeader extends AbstractHeader ?LoggerInterface $logger = null, ?SubjectConsumerService $consumerService = null ) { - $di = MailMimeParser::getGlobalContainer(); parent::__construct( - $logger ?? $di->get(LoggerInterface::class), - $consumerService ?? $di->get(SubjectConsumerService::class), + self::resolveService($logger, LoggerInterface::class), + self::resolveService($consumerService, SubjectConsumerService::class), $name, $value ); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/IMessage.php b/libs/vendor/zbateson/mail-mime-parser/src/IMessage.php index 9be1372b..1219de4c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/IMessage.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/IMessage.php @@ -66,7 +66,7 @@ interface IMessage extends IMimePart * returned. * * @see IMessage::getHtmlStream() to get the html content stream directly. - * @see IMessage::getHtmlStream() to get the html content in a string. + * @see IMessage::getHtmlContent() to get the html content in a string. * @see IMessage::getTextPart() to get the text part(s). * @see IMessage::getTextPartCount() to get a count of text parts. * @see IMessage::getHtmlPartCount() to get a count of html parts. @@ -191,15 +191,12 @@ interface IMessage extends IMimePart * Removes the text/plain part of the message at the passed index if one * exists (defaults to first part if an index isn't passed). * - * Returns true if a part exists at the passed index and has been removed. - * * @see IMessage::setTextPart() to set the text part * @see IMessage::removeHtmlPart() to remove an html part * @see IMessage::removeAllTextParts() to remove all text parts * @param int $index Optional 0-based index of inline text part to remove. - * @return bool true on success */ - public function removeTextPart(int $index = 0) : bool; + public function removeTextPart(int $index = 0) : static; /** * Removes all text/plain inline parts in this message. @@ -225,23 +222,19 @@ interface IMessage extends IMimePart * @see IMessage::removeAllHtmlParts() to remove all html parts * @param bool $moveRelatedPartsBelowMessage Optionally pass false to remove * related parts. - * @return bool true on success */ - public function removeAllTextParts(bool $moveRelatedPartsBelowMessage = true) : bool; + public function removeAllTextParts(bool $moveRelatedPartsBelowMessage = true) : static; /** * Removes the text/html part of the message at the passed index if one * exists (defaults to first part if an index isn't passed). * - * Returns true if a part exists at the passed index and has been removed. - * * @see IMessage::setHtmlPart() to set the html part * @see IMessage::removeTextPart() to remove a text part * @see IMessage::removeAllHtmlParts() to remove all html parts * @param int $index Optional 0-based index of inline html part to remove. - * @return bool true on success */ - public function removeHtmlPart(int $index = 0) : bool; + public function removeHtmlPart(int $index = 0) : static; /** * Removes all text/html inline parts in this message. @@ -264,12 +257,11 @@ interface IMessage extends IMimePart * * @see IMessage::setHtmlPart() to set the html part * @see IMessage::removeHtmlPart() to remove an html part - * @see IMessage::removeAllTextParts() to remove all html parts + * @see IMessage::removeAllTextParts() to remove all text parts * @param bool $moveRelatedPartsBelowMessage Optionally pass false to remove * related parts. - * @return bool true on success */ - public function removeAllHtmlParts(bool $moveRelatedPartsBelowMessage = true) : bool; + public function removeAllHtmlParts(bool $moveRelatedPartsBelowMessage = true) : static; /** * Returns the attachment part at the given 0-based index, or null if none diff --git a/libs/vendor/zbateson/mail-mime-parser/src/MailMimeParser.php b/libs/vendor/zbateson/mail-mime-parser/src/MailMimeParser.php index ad7f9392..c03163ca 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/MailMimeParser.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/MailMimeParser.php @@ -1,4 +1,5 @@ an array of global definitions + * @var array|string|DefinitionSource> an array of global definitions * being used. */ private static array $globalDefinitions = [self::DEFAULT_DEFINITIONS_FILE]; /** * Returns the default ContainerBuilder with default loaded definitions. + * + * @return ContainerBuilder */ private static function getGlobalContainerBuilder() : ContainerBuilder { @@ -91,15 +101,77 @@ class MailMimeParser foreach (self::$globalDefinitions as $def) { $builder->addDefinitions($def); } + foreach (self::discoverPluginConfigs() as $configFile) { + $builder->addDefinitions($configFile); + } return $builder; } + /** + * Discovers plugin DI config files from installed Composer packages. + * + * Uses Composer's InstalledVersions runtime API to enumerate installed + * packages and reads each package's composer.json for the + * "extra.mail-mime-parser.di_config" entry. + * + * Returns an empty array in non-Composer environments. + * + * @return string[] Absolute paths to discovered config files + */ + private static function discoverPluginConfigs() : array + { + if (!\class_exists(InstalledVersions::class)) { + return []; + } + + $configs = []; + foreach (InstalledVersions::getInstalledPackages() as $packageName) { + $installPath = InstalledVersions::getInstallPath($packageName); + if ($installPath === null) { + continue; + } + $configFile = self::readPluginConfigPath($installPath); + if ($configFile !== null) { + $configs[] = $configFile; + } + } + return $configs; + } + + /** + * Reads a package's composer.json and returns the absolute path to its + * MMP DI config file, or null if the package is not an MMP plugin. + * + * Looks for "extra.mail-mime-parser.di_config" in the package's + * composer.json. + */ + public static function readPluginConfigPath(string $packageInstallPath) : ?string + { + $composerJsonPath = $packageInstallPath . '/composer.json'; + if (!\file_exists($composerJsonPath)) { + return null; + } + $data = \json_decode(\file_get_contents($composerJsonPath), true); + if (!\is_array($data)) { + return null; + } + $extra = $data['extra'][self::PLUGIN_EXTRA_KEY] ?? null; + if (!\is_array($extra) || !isset($extra['di_config'])) { + return null; + } + $configFile = \realpath($packageInstallPath . '/' . $extra['di_config']); + if ($configFile === false || !\file_exists($configFile)) { + return null; + } + return $configFile; + } + /** * Sets global configuration for php-di. Overrides all previously set * definitions. You can optionally not use the default MMP definitions file * by passing 'false' to the $useDefaultDefinitionsFile argument. * - * @var array array of definitions + * @param array|string|DefinitionSource> $phpDiConfigs array of definitions */ public static function setGlobalPhpDiConfigurations(array $phpDiConfigs, bool $useDefaultDefinitionsFile = true) : void { @@ -110,6 +182,9 @@ class MailMimeParser self::$globalContainer = null; } + /** + * @param array|string|DefinitionSource $phpDiConfig + */ public static function addGlobalPhpDiContainerDefinition(array|string|DefinitionSource $phpDiConfig) : void { self::$globalDefinitions[] = $phpDiConfig; @@ -135,6 +210,19 @@ class MailMimeParser return self::$globalContainer; } + /** + * Sets the fallback charset used for text/* content parts that don't + * declare a charset. Defaults to 'ISO-8859-1' per RFC 2045. + * + * Many modern messages omit the charset and are actually UTF-8, so you + * may want to set this to 'UTF-8'. + */ + public static function setFallbackCharset(string $charset) : void + { + self::$globalDefinitions[] = ['defaultFallbackCharset' => $charset]; + self::$globalContainer = null; + } + /** * Registers the provided logger globally. */ @@ -162,6 +250,8 @@ class MailMimeParser * @see MailMimeParser::setGlobalPhpDiConfiguration() to register * configuration globally. * @see MailMimeParser::setGlobalLogger() to set a global logger + * + * @param array|string|DefinitionSource|null $phpDiContainerConfig */ public function __construct( ?LoggerInterface $logger = null, @@ -194,22 +284,21 @@ class MailMimeParser * * If the passed $resource is a resource handle or StreamInterface, the * resource must remain open while the returned IMessage object exists. - * Pass true as the second argument to have the resource attached to the - * IMessage and closed for you when it's destroyed, or pass false to - * manually close it if it should remain open after the IMessage object is - * destroyed. + * Pass true as the second argument to have the resource automatically + * closed when the returned IMessage is destroyed, or pass false to + * manage the resource lifecycle yourself. * * @param resource|StreamInterface|string $resource The resource handle to * the input stream of the mime message, or a string containing a * mime message. - * @param bool $attached pass true to have it attached to the returned - * IMessage and destroyed with it. + * @param bool $autoClose pass true to have the resource closed + * automatically when the returned IMessage is destroyed. */ - public function parse(mixed $resource, bool $attached) : IMessage + public function parse(mixed $resource, bool $autoClose) : IMessage { $stream = Utils::streamFor( $resource, - ['metadata' => ['mmp-detached-stream' => ($attached !== true)]] + ['metadata' => ['mmp-detached-stream' => ($autoClose !== true)]] ); if (!$stream->isSeekable()) { $stream = new CachingStream($stream); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message.php b/libs/vendor/zbateson/mail-mime-parser/src/Message.php index 43285b13..7d0273b4 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message.php @@ -47,14 +47,16 @@ class Message extends MimePart implements IMessage ?PartHeaderContainer $headerContainer = null, ?PartChildrenContainer $partChildrenContainer = null, ?MultipartHelper $multipartHelper = null, - ?PrivacyHelper $privacyHelper = null + ?PrivacyHelper $privacyHelper = null, + string $defaultFallbackCharset = 'ISO-8859-1' ) { parent::__construct( null, $logger, $streamContainer, $headerContainer, - $partChildrenContainer + $partChildrenContainer, + $defaultFallbackCharset ); $di = MailMimeParser::getGlobalContainer(); $this->multipartHelper = $multipartHelper ?? $di->get(MultipartHelper::class); @@ -67,24 +69,23 @@ class Message extends MimePart implements IMessage * * If the passed $resource is a resource handle or StreamInterface, the * resource must remain open while the returned IMessage object exists. - * Pass true as the second argument to have the resource attached to the - * IMessage and closed for you when it's destroyed, or pass false to - * manually close it if it should remain open after the IMessage object is - * destroyed. + * Pass true as the second argument to have the resource automatically + * closed when the returned IMessage is destroyed, or pass false to + * manage the resource lifecycle yourself. * * @param resource|StreamInterface|string $resource The resource handle to * the input stream of the mime message, or a string containing a * mime message. - * @param bool $attached pass true to have it attached to the returned - * IMessage and destroyed with it. + * @param bool $autoClose pass true to have the resource closed + * automatically when the returned IMessage is destroyed. */ - public static function from(mixed $resource, bool $attached) : IMessage + public static function from(mixed $resource, bool $autoClose) : IMessage { static $mmp = null; if ($mmp === null) { $mmp = new MailMimeParser(); } - return $mmp->parse($resource, $attached); + return $mmp->parse($resource, $autoClose); } /** @@ -196,44 +197,48 @@ class Message extends MimePart implements IMessage return $this; } - public function removeTextPart(int $index = 0) : bool + public function removeTextPart(int $index = 0) : static { - return $this->multipartHelper + $this->multipartHelper ->removePartByMimeType( $this, 'text/plain', $index ); + return $this; } - public function removeAllTextParts(bool $moveRelatedPartsBelowMessage = true) : bool + public function removeAllTextParts(bool $moveRelatedPartsBelowMessage = true) : static { - return $this->multipartHelper + $this->multipartHelper ->removeAllContentPartsByMimeType( $this, 'text/plain', $moveRelatedPartsBelowMessage ); + return $this; } - public function removeHtmlPart(int $index = 0) : bool + public function removeHtmlPart(int $index = 0) : static { - return $this->multipartHelper + $this->multipartHelper ->removePartByMimeType( $this, 'text/html', $index ); + return $this; } - public function removeAllHtmlParts(bool $moveRelatedPartsBelowMessage = true) : bool + public function removeAllHtmlParts(bool $moveRelatedPartsBelowMessage = true) : static { - return $this->multipartHelper + $this->multipartHelper ->removeAllContentPartsByMimeType( $this, 'text/html', $moveRelatedPartsBelowMessage ); + return $this; } public function getAttachmentPart(int $index) : ?IMessagePart @@ -332,11 +337,12 @@ class Message extends MimePart implements IMessage { $params = ''; if (!empty($this->getMessageId())) { - $params .= ', message-id=' . $this->getContentId(); + $params .= ', message-id=' . $this->getMessageId(); } $params .= ', content-type=' . $this->getContentType(); $nsClass = static::class; - $class = \substr($nsClass, (\strrpos($nsClass, '\\') ?? -1) + 1); + $pos = \strrpos($nsClass, '\\'); + $class = ($pos !== false) ? \substr($nsClass, $pos + 1) : $nsClass; return $class . '(' . \spl_object_id($this) . $params . ')'; } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMessagePartFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMessagePartFactory.php index 036b92d9..e7be670c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMessagePartFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMessagePartFactory.php @@ -19,20 +19,12 @@ use ZBateson\MailMimeParser\Stream\StreamFactory; */ abstract class IMessagePartFactory { - protected LoggerInterface $logger; - - protected StreamFactory $streamFactory; - - protected PartStreamContainerFactory $partStreamContainerFactory; - public function __construct( - LoggerInterface $logger, - StreamFactory $streamFactory, - PartStreamContainerFactory $partStreamContainerFactory + protected readonly LoggerInterface $logger, + protected readonly StreamFactory $streamFactory, + protected readonly PartStreamContainerFactory $partStreamContainerFactory, + protected readonly string $defaultFallbackCharset = 'ISO-8859-1' ) { - $this->logger = $logger; - $this->streamFactory = $streamFactory; - $this->partStreamContainerFactory = $partStreamContainerFactory; } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMimePartFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMimePartFactory.php index d76fd579..f05f99ae 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMimePartFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IMimePartFactory.php @@ -19,20 +19,15 @@ use ZBateson\MailMimeParser\Stream\StreamFactory; */ class IMimePartFactory extends IMessagePartFactory { - protected PartHeaderContainerFactory $partHeaderContainerFactory; - - protected PartChildrenContainerFactory $partChildrenContainerFactory; - public function __construct( LoggerInterface $logger, StreamFactory $streamFactory, PartStreamContainerFactory $partStreamContainerFactory, - PartHeaderContainerFactory $partHeaderContainerFactory, - PartChildrenContainerFactory $partChildrenContainerFactory + protected readonly PartHeaderContainerFactory $partHeaderContainerFactory, + protected readonly PartChildrenContainerFactory $partChildrenContainerFactory, + string $defaultFallbackCharset = 'ISO-8859-1' ) { - parent::__construct($logger, $streamFactory, $partStreamContainerFactory); - $this->partHeaderContainerFactory = $partHeaderContainerFactory; - $this->partChildrenContainerFactory = $partChildrenContainerFactory; + parent::__construct($logger, $streamFactory, $partStreamContainerFactory, $defaultFallbackCharset); } /** @@ -47,7 +42,8 @@ class IMimePartFactory extends IMessagePartFactory $this->logger, $streamContainer, $headerContainer, - $this->partChildrenContainerFactory->newInstance() + $this->partChildrenContainerFactory->newInstance(), + $this->defaultFallbackCharset ); $streamContainer->setStream($this->streamFactory->newMessagePartStream($part)); return $part; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IUUEncodedPartFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IUUEncodedPartFactory.php index 999e4d35..3a2b0584 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IUUEncodedPartFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/IUUEncodedPartFactory.php @@ -29,7 +29,8 @@ class IUUEncodedPartFactory extends IMessagePartFactory null, $parent, $this->logger, - $streamContainer + $streamContainer, + $this->defaultFallbackCharset ); $streamContainer->setStream($this->streamFactory->newMessagePartStream($part)); return $part; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartHeaderContainerFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartHeaderContainerFactory.php index a0234d5c..38b29510 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartHeaderContainerFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartHeaderContainerFactory.php @@ -18,21 +18,10 @@ use ZBateson\MailMimeParser\Message\PartHeaderContainer; */ class PartHeaderContainerFactory { - protected LoggerInterface $logger; - - /** - * @var HeaderFactory the HeaderFactory passed to HeaderContainer instances. - */ - protected HeaderFactory $headerFactory; - - /** - * Constructor - * - */ - public function __construct(LoggerInterface $logger, HeaderFactory $headerFactory) - { - $this->logger = $logger; - $this->headerFactory = $headerFactory; + public function __construct( + protected readonly LoggerInterface $logger, + protected readonly HeaderFactory $headerFactory + ) { } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartStreamContainerFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartStreamContainerFactory.php index 2a45ca95..f82ea0aa 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartStreamContainerFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Factory/PartStreamContainerFactory.php @@ -19,24 +19,12 @@ use ZBateson\MbWrapper\MbWrapper; */ class PartStreamContainerFactory { - protected LoggerInterface $logger; - - protected StreamFactory $streamFactory; - - protected MbWrapper $mbWrapper; - - protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets; - public function __construct( - LoggerInterface $logger, - StreamFactory $streamFactory, - MbWrapper $mbWrapper, - bool $throwExceptionReadingPartContentFromUnsupportedCharsets + protected readonly LoggerInterface $logger, + protected readonly StreamFactory $streamFactory, + protected readonly MbWrapper $mbWrapper, + protected readonly bool $throwExceptionReadingPartContentFromUnsupportedCharsets ) { - $this->logger = $logger; - $this->streamFactory = $streamFactory; - $this->mbWrapper = $mbWrapper; - $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; } public function newInstance() : PartStreamContainer diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/AbstractHelper.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/AbstractHelper.php index ede79c77..e8b13910 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/AbstractHelper.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/AbstractHelper.php @@ -17,21 +17,9 @@ use ZBateson\MailMimeParser\Message\Factory\IUUEncodedPartFactory; */ abstract class AbstractHelper { - /** - * @var IMimePartFactory to create parts for attachments/content - */ - protected IMimePartFactory $mimePartFactory; - - /** - * @var IUUEncodedPartFactory to create parts for attachments - */ - protected IUUEncodedPartFactory $uuEncodedPartFactory; - public function __construct( - IMimePartFactory $mimePartFactory, - IUUEncodedPartFactory $uuEncodedPartFactory + protected readonly IMimePartFactory $mimePartFactory, + protected readonly IUUEncodedPartFactory $uuEncodedPartFactory ) { - $this->mimePartFactory = $mimePartFactory; - $this->uuEncodedPartFactory = $uuEncodedPartFactory; } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/GenericHelper.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/GenericHelper.php index 25856b31..d2b181b3 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/GenericHelper.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/GenericHelper.php @@ -11,6 +11,7 @@ use ZBateson\MailMimeParser\Header\HeaderConsts; use ZBateson\MailMimeParser\Header\IHeader; use ZBateson\MailMimeParser\IMessage; use ZBateson\MailMimeParser\MailMimeParser; +use ZBateson\MailMimeParser\Message\IMessagePart; use ZBateson\MailMimeParser\Message\IMimePart; /** @@ -31,6 +32,9 @@ class GenericHelper extends AbstractHelper * one defined in the static $nonMimeContentFields * */ + /** + * @param string[] $exceptions + */ private function isMimeContentField(IHeader $header, array $exceptions = []) : bool { return (\stripos($header->getName(), 'Content') === 0 @@ -145,15 +149,20 @@ class GenericHelper extends AbstractHelper * replaced, and instead $replacement's type headers are copied to $message, * and any children below $replacement are added directly below $message. */ - public function replacePart(IMessage $message, IMimePart $part, IMimePart $replacement) : static + public function replacePart(IMessage $message, IMessagePart $part, IMessagePart $replacement) : static { - $position = $message->removePart($replacement); + $replacementParent = $replacement->getParent(); + $position = ($replacementParent !== null) + ? \array_search($replacement, $replacementParent->getChildParts(), true) + : false; + $message->removePart($replacement); if ($part === $message) { + \assert($replacement instanceof IMimePart); $this->movePartContentAndChildren($replacement, $message); return $this; } $parent = $part->getParent(); - $parent->addChild($replacement, $position); + $parent->addChild($replacement, $position !== false ? $position : null); $parent->removePart($part); return $this; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/MultipartHelper.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/MultipartHelper.php index 2a2c5d9d..a0e893c9 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/MultipartHelper.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/MultipartHelper.php @@ -25,18 +25,12 @@ use ZBateson\MailMimeParser\Message\PartFilter; */ class MultipartHelper extends AbstractHelper { - /** - * @var GenericHelper a GenericHelper instance - */ - private GenericHelper $genericHelper; - public function __construct( IMimePartFactory $mimePartFactory, IUUEncodedPartFactory $uuEncodedPartFactory, - GenericHelper $genericHelper + private readonly GenericHelper $genericHelper ) { parent::__construct($mimePartFactory, $uuEncodedPartFactory); - $this->genericHelper = $genericHelper; } /** @@ -117,9 +111,9 @@ class MultipartHelper extends AbstractHelper * @param string $mimeType the content-type to find below $alternativePart * @param IMimePart $alternativePart The multipart/alternative part to look * under - * @return bool|IMimePart false if a part is not found + * @return IMimePart|false false if a part is not found */ - public function getContentPartContainerFromAlternative($mimeType, IMimePart $alternativePart) : bool|IMimePart + public function getContentPartContainerFromAlternative($mimeType, IMimePart $alternativePart) : IMimePart|false { $part = $alternativePart->getPart(0, PartFilter::fromInlineContentType($mimeType)); $contPart = null; @@ -130,6 +124,7 @@ class MultipartHelper extends AbstractHelper $contPart = $part; $part = $part->getParent(); } while ($part !== $alternativePart); + \assert($contPart instanceof IMimePart); return $contPart; } @@ -156,7 +151,7 @@ class MultipartHelper extends AbstractHelper $alternativePart = $message->getPart(0, PartFilter::fromInlineContentType('multipart/alternative')); } $message->removePart($rmPart); - if ($alternativePart !== null && $alternativePart instanceof IMultiPart) { + if ($alternativePart instanceof IMultiPart) { if ($alternativePart->getChildCount() === 1) { $this->genericHelper->replacePart($message, $alternativePart, $alternativePart->getChild(0)); } elseif ($alternativePart->getChildCount() === 0) { @@ -250,15 +245,15 @@ class MultipartHelper extends AbstractHelper * If the passed $mimeType is text/plain, searches for a text/html part. * Otherwise searches for a text/plain part to return. * - * @return IMimePart or null if not found + * @return IMessagePart or null if not found */ - public function findOtherContentPartFor(IMessage $message, string $mimeType) : ?IMimePart + public function findOtherContentPartFor(IMessage $message, string $mimeType) : ?IMessagePart { $altPart = $message->getPart( 0, PartFilter::fromInlineContentType(($mimeType === 'text/plain') ? 'text/html' : 'text/plain') ); - if ($altPart !== null && $altPart->getParent() !== null && $altPart->getParent()->isMultiPart()) { + if ($altPart?->getParent()?->isMultiPart()) { $altPartParent = $altPart->getParent(); if ($altPartParent->getChildCount(PartFilter::fromDisposition('inline')) !== 1) { $altPart = $this->createMultipartRelatedPartForInlineChildrenOf($altPartParent); @@ -345,6 +340,7 @@ class MultipartHelper extends AbstractHelper { $alt = $message->getPart(0, PartFilter::fromInlineContentType('multipart/alternative')); if ($alt !== null) { + \assert($alt instanceof IMimePart); return $this->removeAllContentPartsFromAlternative($message, $mimeType, $alt, $keepOtherContent); } $message->removeAllParts(PartFilter::fromInlineContentType($mimeType)); @@ -361,14 +357,14 @@ class MultipartHelper extends AbstractHelper { $parts = $message->getAllParts(PartFilter::fromInlineContentType($mimeType)); $alt = $message->getPart(0, PartFilter::fromInlineContentType('multipart/alternative')); - if ($parts === null || !isset($parts[$index])) { + if (!isset($parts[$index])) { return false; } elseif (\count($parts) === 1) { return $this->removeAllContentPartsByMimeType($message, $mimeType, true); } $part = $parts[$index]; $message->removePart($part); - if ($alt !== null && $alt->getChildCount() === 1) { + if ($alt?->getChildCount() === 1) { $this->genericHelper->replacePart($message, $alt, $alt->getChild(0)); } return true; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/PrivacyHelper.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/PrivacyHelper.php index fb17bb84..2ed36861 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/PrivacyHelper.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/Helper/PrivacyHelper.php @@ -21,25 +21,13 @@ use ZBateson\MailMimeParser\Message\IMessagePart; */ class PrivacyHelper extends AbstractHelper { - /** - * @var GenericHelper a GenericHelper instance - */ - private GenericHelper $genericHelper; - - /** - * @var MultipartHelper a MultipartHelper instance - */ - private MultipartHelper $multipartHelper; - public function __construct( IMimePartFactory $mimePartFactory, IUUEncodedPartFactory $uuEncodedPartFactory, - GenericHelper $genericHelper, - MultipartHelper $multipartHelper + private readonly GenericHelper $genericHelper, + private readonly MultipartHelper $multipartHelper ) { parent::__construct($mimePartFactory, $uuEncodedPartFactory); - $this->genericHelper = $genericHelper; - $this->multipartHelper = $multipartHelper; } /** @@ -99,9 +87,7 @@ class PrivacyHelper extends AbstractHelper */ public function overwrite8bitContentEncoding(IMessage $message) : static { - $parts = $message->getAllParts(function(IMessagePart $part) { - return \strcasecmp($part->getContentTransferEncoding(), '8bit') === 0; - }); + $parts = $message->getAllParts(fn(IMessagePart $part) => \strcasecmp($part->getContentTransferEncoding(), '8bit') === 0); foreach ($parts as $part) { $contentType = \strtolower($part->getContentType()); $part->setRawHeader( diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMessagePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMessagePart.php index baef622e..2c0c9146 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMessagePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMessagePart.php @@ -8,7 +8,6 @@ namespace ZBateson\MailMimeParser\Message; use Psr\Http\Message\StreamInterface; -use SplSubject; use ZBateson\MailMimeParser\IErrorBag; use ZBateson\MailMimeParser\MailMimeParser; @@ -19,12 +18,9 @@ use ZBateson\MailMimeParser\MailMimeParser; * contain content, have a parent, and identify the type of content (e.g. * mime-type or charset) agnostically. * - * The interface extends SplSubject -- any modifications to a message must - * notify any attached observers. - * * @author Zaahid Bateson */ -interface IMessagePart extends IErrorBag, SplSubject +interface IMessagePart extends IErrorBag { /** * Returns this part's parent. @@ -47,10 +43,10 @@ interface IMessagePart extends IErrorBag, SplSubject * Returns the mime type of the content, or $default if one is not set. * * @param string $default Optional override for the default return value of - * 'text/plain. + * 'text/plain'. * @return string the mime type */ - public function getContentType(string $default = 'text/plain') : ?string; + public function getContentType(string $default = 'text/plain') : string; /** * Returns the charset of the content, or null if not applicable/defined. @@ -127,7 +123,7 @@ interface IMessagePart extends IErrorBag, SplSubject * doesn't have a content section. * * To get a stream without charset conversion if you know the part's content - * contains a binary stream, call {@see self::getBinaryContentStream()} + * contains a binary stream, call {@see IMessagePart::getBinaryContentStream()} * instead. * * The library automatically handles decoding and charset conversion (to the @@ -282,7 +278,7 @@ interface IMessagePart extends IErrorBag, SplSubject * * If the part has not been modified and represents a parsed part, the * original stream should be returned. Otherwise a stream representation of - * the part including its modifications should be returned. This insures + * the part including its modifications should be returned. This ensures * that an unmodified, signed message could be passed on that way even after * parsing and reading. * @@ -299,7 +295,7 @@ interface IMessagePart extends IErrorBag, SplSubject * resource handle. * @see IMessagePart::__toString() to write the part to a string and return * it. - * @see IMessage::save() to write the part to a file, resource handle or + * @see IMessagePart::save() to write the part to a file, resource handle or * Psr7 stream. * @return resource the resource handle containing the part. */ @@ -311,7 +307,7 @@ interface IMessagePart extends IErrorBag, SplSubject * * If the part has not been modified and represents a parsed part, the * original stream should be returned. Otherwise a stream representation of - * the part including its modifications should be returned. This insures + * the part including its modifications should be returned. This ensures * that an unmodified, signed message could be passed on that way even after * parsing and reading. * @@ -327,7 +323,7 @@ interface IMessagePart extends IErrorBag, SplSubject * @see IMessagePart::getResourceHandle() to get a resource handle. * @see IMessagePart::__toString() to write the part to a string and return * it. - * @see IMessage::save() to write the part to a file, resource handle or + * @see IMessagePart::save() to write the part to a file, resource handle or * Psr7 stream. * @return StreamInterface the stream containing the part. */ @@ -340,7 +336,7 @@ interface IMessagePart extends IErrorBag, SplSubject * If the part has not been modified and represents a parsed part, the * original stream should be written to the file. Otherwise a stream * representation of the part including its modifications should be written. - * This insures that an unmodified, signed message could be passed on this + * This ensures that an unmodified, signed message could be passed on this * way even after parsing and reading. * * The written stream is not guaranteed to be RFC822 (or greater) compliant @@ -360,7 +356,7 @@ interface IMessagePart extends IErrorBag, SplSubject * * @see IMessagePart::getResourceHandle() to get a resource handle. * @see IMessagePart::__toString() to get the part in a string. - * @see IMessage::save() to write the part to a file, resource handle or + * @see IMessagePart::save() to write the part to a file, resource handle or * Psr7 stream. * @param string|resource|StreamInterface $filenameResourceOrStream the * file, resource, or stream to write to. @@ -379,7 +375,7 @@ interface IMessagePart extends IErrorBag, SplSubject * @see IMessagePart::getStream() to get a Psr7 StreamInterface instead of a * string. * @see IMessagePart::getResourceHandle() to get a resource handle. - * @see IMessage::save() to write the part to a file, resource handle or + * @see IMessagePart::save() to write the part to a file, resource handle or * Psr7 stream. */ public function __toString() : string; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMimePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMimePart.php index c91618f3..e69a7eb5 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMimePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMimePart.php @@ -36,7 +36,8 @@ interface IMimePart extends IMultiPart * same name, the one at the passed offset is returned. * * Note that mime header names aren't case sensitive, and the '-' character - * is ignored, so ret + * is ignored, so 'Content-Type', 'content-type', and 'ContentType' are + * equivalent. * * If a header with the given $name and $offset doesn't exist, null is * returned. @@ -58,7 +59,10 @@ interface IMimePart extends IMultiPart * @param string $name The name of the header to retrieve. * @param int $offset Optional offset if there are multiple headers with the * given name. - * @return ?IHeader the header object if it exists, or null if not + * @return ?IHeader The concrete header type depends on the header name + * (AddressHeader, DateHeader, GenericHeader, IdHeader, + * ParameterHeader, ReceivedHeader, or SubjectHeader). See + * {@see HeaderFactory} for the mapping. */ public function getHeader(string $name, int $offset = 0) : ?IHeader; @@ -70,6 +74,8 @@ interface IMimePart extends IMultiPart * same name, the one at the passed offset is returned. * * Note that mime headers aren't case sensitive, and the '-' character is + * ignored, so 'Content-Type', 'content-type', and 'ContentType' are + * equivalent. * * If a header with the given $name and $offset doesn't exist, null is * returned. diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMultiPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMultiPart.php index 0e87fbcf..07ed6dc5 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/IMultiPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/IMultiPart.php @@ -184,7 +184,7 @@ interface IMultiPart extends IMessagePart * this part. * @see IMultiPart::getAllParts() to get an array of all parts with an * optional filter. - * @return RecursiveIterator + * @return RecursiveIterator */ public function getChildIterator() : RecursiveIterator; @@ -250,18 +250,16 @@ interface IMultiPart extends IMessagePart * If the $position parameter is non-null, adds the part at the passed * position index, otherwise adds it as the last child. * - * @param MessagePart $part The part to add. + * @param IMessagePart $part The part to add. * @param int $position Optional insertion position 0-based index. */ - public function addChild(MessagePart $part, ?int $position = null) : static; + public function addChild(IMessagePart $part, ?int $position = null) : static; /** - * Removes the child part from this part and returns its previous position - * or null if it wasn't found. + * Removes the child part from this part. * - * Note that if the part is not a direct child of this part, the returned - * position is its index within its parent (calls removePart on its direct - * parent). + * Note that if the part is not a direct child of this part, it is removed + * from its direct parent instead. * * This also means that parts from unrelated parts/messages could be removed * by a call to removePart -- it will always remove the part from its parent @@ -269,10 +267,8 @@ interface IMultiPart extends IMessagePart * ```php $part->getParent()->removePart(); ```. * * @param IMessagePart $part The part to remove - * @return int|null The previous index position of the part within its old - * parent. */ - public function removePart(IMessagePart $part) : ?int; + public function removePart(IMessagePart $part) : static; /** * Removes all parts below the current part. If a callable filter is diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/MessagePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/MessagePart.php index 01289bcb..32b926fc 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/MessagePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/MessagePart.php @@ -13,27 +13,17 @@ use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; use SplObjectStorage; use SplObserver; +use SplSubject; use ZBateson\MailMimeParser\ErrorBag; use ZBateson\MailMimeParser\MailMimeParser; -use ZBateson\MailMimeParser\Stream\MessagePartStreamDecorator; /** * Most basic representation of a single part of an email. * * @author Zaahid Bateson */ -abstract class MessagePart extends ErrorBag implements IMessagePart +abstract class MessagePart extends ErrorBag implements IMessagePart, SplSubject { - /** - * @var ?IMimePart parent part - */ - protected ?IMimePart $parent; - - /** - * @var PartStreamContainer holds 'stream' and 'content stream'. - */ - protected PartStreamContainer $partStreamContainer; - /** * @var ?string can be used to set an override for content's charset in cases * where a user knows the charset on the content is not what it claims @@ -49,19 +39,17 @@ abstract class MessagePart extends ErrorBag implements IMessagePart protected bool $ignoreTransferEncoding = false; /** - * @var SplObjectStorage attached observers that need to be notified of + * @var SplObjectStorage attached observers that need to be notified of * modifications to this part. */ protected SplObjectStorage $observers; public function __construct( LoggerInterface $logger, - PartStreamContainer $streamContainer, - ?IMimePart $parent = null + protected readonly PartStreamContainer $partStreamContainer, + protected ?IMimePart $parent = null ) { parent::__construct($logger); - $this->partStreamContainer = $streamContainer; - $this->parent = $parent; $this->observers = new SplObjectStorage(); } @@ -108,7 +96,7 @@ abstract class MessagePart extends ErrorBag implements IMessagePart return $this; } - public function getContentStream(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?MessagePartStreamDecorator + public function getContentStream(string $charset = MailMimeParser::DEFAULT_CHARSET) : ?StreamInterface { if ($this->hasContent()) { $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding(); @@ -123,7 +111,7 @@ abstract class MessagePart extends ErrorBag implements IMessagePart return null; } - public function getBinaryContentStream() : ?MessagePartStreamDecorator + public function getBinaryContentStream() : ?StreamInterface { if ($this->hasContent()) { $tr = ($this->ignoreTransferEncoding) ? '' : $this->getContentTransferEncoding(); @@ -188,10 +176,10 @@ abstract class MessagePart extends ErrorBag implements IMessagePart return $this; } - public function setContent($resource, string $charset = MailMimeParser::DEFAULT_CHARSET) : static + public function setContent($resource, string $resourceCharset = MailMimeParser::DEFAULT_CHARSET) : static { $stream = Utils::streamFor($resource); - $this->attachContentStream($stream, $charset); + $this->attachContentStream($stream, $resourceCharset); // this->notify() called in attachContentStream return $this; } @@ -240,7 +228,8 @@ abstract class MessagePart extends ErrorBag implements IMessagePart } $params .= ', content-type=' . $this->getContentType(); $nsClass = static::class; - $class = \substr($nsClass, (\strrpos($nsClass, '\\') ?? -1) + 1); + $pos = \strrpos($nsClass, '\\'); + $class = ($pos !== false) ? \substr($nsClass, $pos + 1) : $nsClass; return $class . '(' . \spl_object_id($this) . $params . ')'; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/MimePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/MimePart.php index 855200bd..28f2c376 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/MimePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/MimePart.php @@ -29,12 +29,18 @@ class MimePart extends MultiPart implements IMimePart */ protected PartHeaderContainer $headerContainer; + /** + * @var string Fallback charset for text parts without a declared charset. + */ + protected string $fallbackCharset; + public function __construct( ?IMimePart $parent = null, ?LoggerInterface $logger = null, ?PartStreamContainer $streamContainer = null, ?PartHeaderContainer $headerContainer = null, - ?PartChildrenContainer $partChildrenContainer = null + ?PartChildrenContainer $partChildrenContainer = null, + string $defaultFallbackCharset = 'ISO-8859-1' ) { $di = MailMimeParser::getGlobalContainer(); parent::__construct( @@ -44,6 +50,7 @@ class MimePart extends MultiPart implements IMimePart $parent ); $this->headerContainer = $headerContainer ?? $di->get(PartHeaderContainer::class); + $this->fallbackCharset = $defaultFallbackCharset; } /** @@ -115,7 +122,7 @@ class MimePart extends MultiPart implements IMimePart * than text/plain if needed. * @return string the mime type */ - public function getContentType(string $default = 'text/plain') : ?string + public function getContentType(string $default = 'text/plain') : string { return \strtolower($this->getHeaderValue(HeaderConsts::CONTENT_TYPE, $default)); } @@ -137,7 +144,7 @@ class MimePart extends MultiPart implements IMimePart if ($charset === null || \strcasecmp($charset, 'binary') === 0) { $contentType = $this->getContentType(); if ($contentType === 'text/plain' || $contentType === 'text/html') { - return 'ISO-8859-1'; + return $this->fallbackCharset; } return null; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/MultiPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/MultiPart.php index dda70187..08e05310 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/MultiPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/MultiPart.php @@ -21,19 +21,13 @@ use RecursiveIteratorIterator; */ abstract class MultiPart extends MessagePart implements IMultiPart { - /** - * @var PartChildrenContainer child part container - */ - protected PartChildrenContainer $partChildrenContainer; - public function __construct( LoggerInterface $logger, PartStreamContainer $streamContainer, - PartChildrenContainer $partChildrenContainer, + protected readonly PartChildrenContainer $partChildrenContainer, ?IMimePart $parent = null ) { parent::__construct($logger, $streamContainer, $parent); - $this->partChildrenContainer = $partChildrenContainer; } private function getAllPartsIterator() : AppendIterator @@ -90,6 +84,9 @@ abstract class MultiPart extends MessagePart implements IMultiPart ); } + /** + * @return RecursiveIterator + */ public function getChildIterator() : RecursiveIterator { return $this->partChildrenContainer; @@ -133,9 +130,10 @@ abstract class MultiPart extends MessagePart implements IMultiPart }); } - public function addChild(MessagePart $part, ?int $position = null) : static + public function addChild(IMessagePart $part, ?int $position = null) : static { if ($part !== $this) { + \assert($part instanceof MessagePart); $part->parent = $this; $this->partChildrenContainer->add($part, $position); $this->notify(); @@ -143,18 +141,19 @@ abstract class MultiPart extends MessagePart implements IMultiPart return $this; } - public function removePart(IMessagePart $part) : ?int + public function removePart(IMessagePart $part) : static { $parent = $part->getParent(); if ($this !== $parent && $parent !== null) { - return $parent->removePart($part); + $parent->removePart($part); + return $this; } $position = $this->partChildrenContainer->remove($part); if ($position !== null) { $this->notify(); } - return $position; + return $this; } public function removeAllParts(?callable $fnFilter = null) : int diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/NonMimePart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/NonMimePart.php index 3fd81dc7..3112f743 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/NonMimePart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/NonMimePart.php @@ -7,6 +7,8 @@ namespace ZBateson\MailMimeParser\Message; +use Psr\Log\LoggerInterface; + /** * Represents part of a non-mime message. * @@ -14,6 +16,17 @@ namespace ZBateson\MailMimeParser\Message; */ abstract class NonMimePart extends MessagePart { + protected string $fallbackCharset = 'ISO-8859-1'; + + public function __construct( + LoggerInterface $logger, + PartStreamContainer $partStreamContainer, + ?IMimePart $parent = null, + string $defaultFallbackCharset = 'ISO-8859-1' + ) { + parent::__construct($logger, $partStreamContainer, $parent); + $this->fallbackCharset = $defaultFallbackCharset; + } /** * Returns true. * @@ -26,17 +39,17 @@ abstract class NonMimePart extends MessagePart /** * Returns text/plain */ - public function getContentType(string $default = 'text/plain') : ?string + public function getContentType(string $default = 'text/plain') : string { return $default; } /** - * Returns ISO-8859-1 + * Returns the configured fallback charset (ISO-8859-1 by default). */ public function getCharset() : ?string { - return 'ISO-8859-1'; + return $this->fallbackCharset; } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartChildrenContainer.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartChildrenContainer.php index 30a038ab..b2cadaab 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartChildrenContainer.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartChildrenContainer.php @@ -14,27 +14,23 @@ use RecursiveIterator; /** * Container of IMessagePart items for a parent IMultiPart. * + * @implements ArrayAccess + * @implements RecursiveIterator + * * @author Zaahid Bateson */ class PartChildrenContainer implements ArrayAccess, RecursiveIterator { - /** - * @var IMessagePart[] array of child parts of the IMultiPart object that is - * holding this container. - */ - protected array $children; - /** * @var int current key position within $children for iteration. */ - protected $position = 0; + protected int $position = 0; /** * @param IMessagePart[] $children */ - public function __construct(array $children = []) + public function __construct(protected array $children = []) { - $this->children = $children; } /** @@ -50,7 +46,7 @@ class PartChildrenContainer implements ArrayAccess, RecursiveIterator * If the current element points to an IMultiPart, its child iterator is * returned by calling {@see IMultiPart::getChildIterator()}. * - * @return RecursiveIterator|null the iterator + * @return RecursiveIterator|null the iterator */ public function getChildren() : ?RecursiveIterator { @@ -98,7 +94,7 @@ class PartChildrenContainer implements ArrayAccess, RecursiveIterator * @param int $position An optional index position (0-based) to add the * child at. */ - public function add(IMessagePart $part, $position = null) : static + public function add(IMessagePart $part, ?int $position = null) : static { if ($position === null || $position >= \count($this->children)) { $this->children[] = $part; @@ -147,7 +143,7 @@ class PartChildrenContainer implements ArrayAccess, RecursiveIterator } /** - * @param int $offset + * @param int|null $offset * @param IMessagePart $value */ public function offsetSet(mixed $offset, mixed $value) : void @@ -167,7 +163,7 @@ class PartChildrenContainer implements ArrayAccess, RecursiveIterator /** * @param int $offset */ - public function offsetUnset($offset) : void + public function offsetUnset(mixed $offset) : void { \array_splice($this->children, $offset, 1); if ($this->position >= $offset) { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartFilter.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartFilter.php index 50b4a9bf..ab35fe26 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartFilter.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartFilter.php @@ -13,8 +13,12 @@ namespace ZBateson\MailMimeParser\Message; * * @author Zaahid Bateson */ -abstract class PartFilter +final class PartFilter { + private function __construct() + { + } + /** * Provides an 'attachment' filter used by Message::getAttachmentPart. * @@ -70,9 +74,7 @@ abstract class PartFilter */ public static function fromContentType(string $mimeType) : callable { - return function(IMessagePart $part) use ($mimeType) { - return \strcasecmp($part->getContentType() ?: '', $mimeType) === 0; - }; + return fn(IMessagePart $part) => \strcasecmp($part->getContentType(), $mimeType) === 0; } /** @@ -85,7 +87,7 @@ abstract class PartFilter { return function(IMessagePart $part) use ($mimeType) { $disp = $part->getContentDisposition(); - return (\strcasecmp($part->getContentType() ?: '', $mimeType) === 0) && ($disp === null + return (\strcasecmp($part->getContentType(), $mimeType) === 0) && ($disp === null || \strcasecmp($disp, 'attachment') !== 0); }; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartHeaderContainer.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartHeaderContainer.php index 92feee44..e718c10f 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartHeaderContainer.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartHeaderContainer.php @@ -18,6 +18,8 @@ use ZBateson\MailMimeParser\Header\IHeader; /** * Maintains a collection of headers for a part. * + * @implements IteratorAggregate> + * * @author Zaahid Bateson */ class PartHeaderContainer extends ErrorBag implements IteratorAggregate @@ -25,36 +27,36 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate /** * @var HeaderFactory the HeaderFactory object used for created headers */ - protected $headerFactory; + protected HeaderFactory $headerFactory; /** * @var string[][] Each element in the array is an array with its first * element set to the header's name, and the second its value. */ - private $headers = []; + private array $headers = []; /** - * @var \ZBateson\MailMimeParser\Header\IHeader[] Each element is an IHeader + * @var array Each element is an IHeader * representing the header at the same index in the $headers array. If * an IHeader has not been constructed for the header at that index, * the element would be set to null. */ - private $headerObjects = []; + private array $headerObjects = []; /** - * @var array Maps header names by their "normalized" (lower-cased, + * @var array Maps header names by their "normalized" (lower-cased, * non-alphanumeric characters stripped) name to an array of indexes in * the $headers array. For example: * $headerMap['contenttype'] = [ 1, 4 ] * would indicate that the headers in $headers[1] and $headers[4] are * both headers with the name 'Content-Type' or 'contENTtype'. */ - private $headerMap = []; + private array $headerMap = []; /** * @var int the next index to use for $headers and $headerObjects. */ - private $nextIndex = 0; + private int $nextIndex = 0; /** * Pass a PartHeaderContainer as the second parameter. This is useful when @@ -103,10 +105,7 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate { $s = $this->headerFactory->getNormalizedHeaderName($name); if (isset($this->headerMap[$s])) { - $self = $this; - $filtered = \array_filter($this->headerMap[$s], function($h) use ($name, $self) { - return (\strcasecmp($self->headers[$h][0], $name) === 0); - }); + $filtered = \array_filter($this->headerMap[$s], fn($h) => \strcasecmp($this->headers[$h][0], $name) === 0); return (!empty($filtered)) ? $filtered : $this->headerMap[$s]; } return null; @@ -157,10 +156,7 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate { $a = $this->getAllWithOriginalHeaderNameIfSet($name); if (!empty($a)) { - $self = $this; - return \array_map(function($index) use ($self) { - return $self->getByIndex($index); - }, $a); + return \array_map(fn($index) => $this->getByIndex($index), $a); } return []; } @@ -284,7 +280,7 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate */ public function getHeaderObjects() : array { - return \array_filter(\array_map([$this, 'getByIndex'], \array_keys($this->headers))); + return \array_filter(\array_map($this->getByIndex(...), \array_keys($this->headers))); } /** @@ -312,7 +308,7 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate * * [ 'Header-Name', 'Header Value' ] * - * return Traversable> + * @return Traversable> */ public function getIterator() : Traversable { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartStreamContainer.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartStreamContainer.php index f0e5ac71..a3201168 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/PartStreamContainer.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/PartStreamContainer.php @@ -12,7 +12,6 @@ use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use ZBateson\MailMimeParser\ErrorBag; -use ZBateson\MailMimeParser\Stream\MessagePartStreamDecorator; use ZBateson\MailMimeParser\Stream\StreamFactory; use ZBateson\MbWrapper\MbWrapper; use ZBateson\MbWrapper\UnsupportedCharsetException; @@ -35,29 +34,10 @@ use ZBateson\MbWrapper\UnsupportedCharsetException; class PartStreamContainer extends ErrorBag { /** - * @var MbWrapper to test charsets and see if they're supported. + * @var StreamInterface stream containing the part's headers, content and + * children */ - protected MbWrapper $mbWrapper; - - /** - * @var bool if false, reading from a content stream with an unsupported - * charset will be tried with the default charset, otherwise the stream - * created with the unsupported charset, and an exception will be - * thrown when read from. - */ - protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets; - - /** - * @var StreamFactory used to apply psr7 stream decorators to the - * attached StreamInterface based on encoding. - */ - protected StreamFactory $streamFactory; - - /** - * @var MessagePartStreamDecorator stream containing the part's headers, - * content and children wrapped in a MessagePartStreamDecorator - */ - protected MessagePartStreamDecorator $stream; + protected StreamInterface $stream; /** * @var StreamInterface a stream containing this part's content @@ -82,7 +62,7 @@ class PartStreamContainer extends ErrorBag protected bool $detachParsedStream = false; /** - * @var array map of the active encoding filter on the current handle. + * @var array map of the active encoding filter on the current handle. */ private array $encoding = [ 'type' => null, @@ -90,7 +70,7 @@ class PartStreamContainer extends ErrorBag ]; /** - * @var array map of the active charset filter on the current handle. + * @var array map of the active charset filter on the current handle. */ private array $charset = [ 'from' => null, @@ -100,21 +80,18 @@ class PartStreamContainer extends ErrorBag public function __construct( LoggerInterface $logger, - StreamFactory $streamFactory, - MbWrapper $mbWrapper, - bool $throwExceptionReadingPartContentFromUnsupportedCharsets + protected readonly StreamFactory $streamFactory, + protected readonly MbWrapper $mbWrapper, + protected readonly bool $throwExceptionReadingPartContentFromUnsupportedCharsets ) { parent::__construct($logger); - $this->streamFactory = $streamFactory; - $this->mbWrapper = $mbWrapper; - $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; } /** * Sets the part's stream containing the part's headers, content, and * children. */ - public function setStream(MessagePartStreamDecorator $stream) : static + public function setStream(StreamInterface $stream) : static { $this->stream = $stream; return $this; @@ -124,7 +101,7 @@ class PartStreamContainer extends ErrorBag * Returns the part's stream containing the part's headers, content, and * children. */ - public function getStream() : MessagePartStreamDecorator + public function getStream() : StreamInterface { // error out if called before setStream, getStream should never return // null. @@ -285,7 +262,7 @@ class PartStreamContainer extends ErrorBag ?string $transferEncoding, ?string $fromCharset, ?string $toCharset - ) : ?MessagePartStreamDecorator { + ) : ?StreamInterface { if ($this->contentStream === null) { return null; } @@ -314,7 +291,7 @@ class PartStreamContainer extends ErrorBag * Checks what transfer-encoding decoder stream is attached on the * underlying stream, and resets it if the requested arguments differ. */ - public function getBinaryContentStream(IMessagePart $part, ?string $transferEncoding = null) : ?MessagePartStreamDecorator + public function getBinaryContentStream(IMessagePart $part, ?string $transferEncoding = null) : ?StreamInterface { if ($this->contentStream === null) { return null; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Message/UUEncodedPart.php b/libs/vendor/zbateson/mail-mime-parser/src/Message/UUEncodedPart.php index 3313c1fd..444cf550 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Message/UUEncodedPart.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Message/UUEncodedPart.php @@ -17,31 +17,21 @@ use ZBateson\MailMimeParser\MailMimeParser; */ class UUEncodedPart extends NonMimePart implements IUUEncodedPart { - /** - * @var int the unix file permission - */ - protected ?int $mode = null; - - /** - * @var string the name of the file in the uuencoding 'header'. - */ - protected ?string $filename = null; - public function __construct( - ?int $mode = null, - ?string $filename = null, + protected ?int $mode = null, + protected ?string $filename = null, ?IMimePart $parent = null, ?LoggerInterface $logger = null, - ?PartStreamContainer $streamContainer = null + ?PartStreamContainer $streamContainer = null, + string $defaultFallbackCharset = 'ISO-8859-1' ) { $di = MailMimeParser::getGlobalContainer(); parent::__construct( $logger ?? $di->get(LoggerInterface::class), $streamContainer ?? $di->get(PartStreamContainer::class), - $parent + $parent, + $defaultFallbackCharset ); - $this->mode = $mode; - $this->filename = $filename; } /** @@ -77,7 +67,7 @@ class UUEncodedPart extends NonMimePart implements IUUEncodedPart /** * Returns 'application/octet-stream'. */ - public function getContentType(string $default = 'application/octet-stream') : ?string + public function getContentType(string $default = 'application/octet-stream') : string { return 'application/octet-stream'; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/AbstractParserService.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/AbstractParserService.php index 9eb4547e..0a11fd47 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/AbstractParserService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/AbstractParserService.php @@ -21,26 +21,6 @@ use ZBateson\MailMimeParser\Parser\Proxy\ParserPartProxyFactory; */ abstract class AbstractParserService implements IParserService { - /** - * @var ParserPartProxyFactory the parser's message proxy factory service - * responsible for creating an IMessage part wrapped in a - * ParserPartProxy. - */ - protected ParserPartProxyFactory $parserMessageProxyFactory; - - /** - * @var ParserPartProxyFactory the parser's part proxy factory service - * responsible for creating IMessagePart parts wrapped in a - * ParserPartProxy. - */ - protected ParserPartProxyFactory $parserPartProxyFactory; - - /** - * @var PartBuilderFactory Service for creating PartBuilder objects for new - * children. - */ - protected PartBuilderFactory $partBuilderFactory; - /** * @var ParserManagerService the ParserManager, which should call setParserManager * when the parser is added. @@ -48,13 +28,10 @@ abstract class AbstractParserService implements IParserService protected ParserManagerService $parserManager; public function __construct( - ParserPartProxyFactory $parserMessageProxyFactory, - ParserPartProxyFactory $parserPartProxyFactory, - PartBuilderFactory $partBuilderFactory + protected readonly ParserPartProxyFactory $parserMessageProxyFactory, + protected readonly ParserPartProxyFactory $parserPartProxyFactory, + protected readonly PartBuilderFactory $partBuilderFactory ) { - $this->parserMessageProxyFactory = $parserMessageProxyFactory; - $this->parserPartProxyFactory = $parserPartProxyFactory; - $this->partBuilderFactory = $partBuilderFactory; } public function setParserManager(ParserManagerService $pm) : static diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/HeaderParserService.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/HeaderParserService.php index fb6f9a4c..c625d6b2 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/HeaderParserService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/HeaderParserService.php @@ -17,14 +17,10 @@ use ZBateson\MailMimeParser\Message\PartHeaderContainer; */ class HeaderParserService { - private int $maxHeaderCount; - - private int $maxHeaderSizeBytes; - - public function __construct(int $maxHeaderCount = 1000, int $maxHeaderSizeBytes = 1048576) - { - $this->maxHeaderCount = $maxHeaderCount; - $this->maxHeaderSizeBytes = $maxHeaderSizeBytes; + public function __construct( + private readonly int $maxHeaderCount = 1000, + private readonly int $maxHeaderSizeBytes = 1048576 + ) { } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/MessageParserService.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/MessageParserService.php index 6598f9e9..295311a6 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/MessageParserService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/MessageParserService.php @@ -19,39 +19,12 @@ use ZBateson\MailMimeParser\Message\Factory\PartHeaderContainerFactory; */ class MessageParserService { - /** - * @var PartHeaderContainerFactory To create a container to read the - * message's headers into. - */ - protected PartHeaderContainerFactory $partHeaderContainerFactory; - - /** - * @var ParserManagerService To figure out what parser is responsible for parsing a - * message. - */ - protected ParserManagerService $parserManager; - - /** - * @var PartBuilderFactory To create a PartBuilder representing this - * message, and to pass it to ParserManager. - */ - protected PartBuilderFactory $partBuilderFactory; - - /** - * @var HeaderParserService To parse the headers into a PartHeaderContainer. - */ - protected HeaderParserService $headerParser; - public function __construct( - PartBuilderFactory $pbf, - PartHeaderContainerFactory $phcf, - ParserManagerService $pm, - HeaderParserService $hp + protected readonly PartBuilderFactory $partBuilderFactory, + protected readonly PartHeaderContainerFactory $partHeaderContainerFactory, + protected readonly ParserManagerService $parserManager, + protected readonly HeaderParserService $headerParser ) { - $this->partBuilderFactory = $pbf; - $this->partHeaderContainerFactory = $phcf; - $this->parserManager = $pm; - $this->headerParser = $hp; } /** @@ -90,6 +63,8 @@ class MessageParserService $headerContainer ); $proxy = $this->parserManager->createParserProxyFor($partBuilder); - return $proxy->getPart(); + $part = $proxy->getPart(); + \assert($part instanceof IMessage); + return $part; } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/MimeParserService.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/MimeParserService.php index 403d98d2..0124a65e 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/MimeParserService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/MimeParserService.php @@ -22,34 +22,15 @@ use ZBateson\MailMimeParser\Parser\Proxy\ParserPartProxy; */ class MimeParserService extends AbstractParserService { - /** - * @var PartHeaderContainerFactory Factory service for creating - * PartHeaderContainers for headers. - */ - protected PartHeaderContainerFactory $partHeaderContainerFactory; - - /** - * @var HeaderParserService The HeaderParser service. - */ - protected HeaderParserService $headerParser; - - /** - * @var int Maximum multipart nesting depth. - */ - protected int $maxMimePartDepth; - public function __construct( ParserMessageProxyFactory $parserMessageProxyFactory, ParserMimePartProxyFactory $parserMimePartProxyFactory, PartBuilderFactory $partBuilderFactory, - PartHeaderContainerFactory $partHeaderContainerFactory, - HeaderParserService $headerParser, - int $maxMimePartDepth = 256 + protected readonly PartHeaderContainerFactory $partHeaderContainerFactory, + protected readonly HeaderParserService $headerParser, + protected readonly int $maxMimePartDepth = 256 ) { parent::__construct($parserMessageProxyFactory, $parserMimePartProxyFactory, $partBuilderFactory); - $this->partHeaderContainerFactory = $partHeaderContainerFactory; - $this->headerParser = $headerParser; - $this->maxMimePartDepth = $maxMimePartDepth; } /** @@ -121,6 +102,7 @@ class MimeParserService extends AbstractParserService public function parseContent(ParserPartProxy $proxy) : static { + \assert($proxy instanceof ParserMimePartProxy); $proxy->setStreamContentStartPos($proxy->getMessageResourceHandlePos()); $this->findContentBoundary($proxy); return $this; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/NonMimeParserService.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/NonMimeParserService.php index 5bf78781..d892b3ab 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/NonMimeParserService.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/NonMimeParserService.php @@ -22,16 +22,13 @@ use ZBateson\MailMimeParser\Parser\Proxy\ParserUUEncodedPartProxyFactory; */ class NonMimeParserService extends AbstractParserService { - protected UUEncodedPartHeaderContainerFactory $partHeaderContainerFactory; - public function __construct( ParserNonMimeMessageProxyFactory $parserNonMimeMessageProxyFactory, ParserUUEncodedPartProxyFactory $parserUuEncodedPartProxyFactory, PartBuilderFactory $partBuilderFactory, - UUEncodedPartHeaderContainerFactory $uuEncodedPartHeaderContainerFactory + protected readonly UUEncodedPartHeaderContainerFactory $partHeaderContainerFactory ) { parent::__construct($parserNonMimeMessageProxyFactory, $parserUuEncodedPartProxyFactory, $partBuilderFactory); - $this->partHeaderContainerFactory = $uuEncodedPartHeaderContainerFactory; } /** @@ -68,7 +65,7 @@ class NonMimeParserService extends AbstractParserService * * @param ParserNonMimeMessageProxy|ParserUUEncodedPartProxy $proxy */ - private function parseNextPart(ParserPartProxy $proxy) : static + private function parseNextPart(ParserNonMimeMessageProxy|ParserUUEncodedPartProxy $proxy) : static { $handle = $proxy->getMessageResourceHandle(); while (!\feof($handle)) { @@ -87,6 +84,7 @@ class NonMimeParserService extends AbstractParserService public function parseContent(ParserPartProxy $proxy) : static { + \assert($proxy instanceof ParserNonMimeMessageProxy || $proxy instanceof ParserUUEncodedPartProxy); $handle = $proxy->getMessageResourceHandle(); if ($proxy->getNextPartStart() !== null || \feof($handle)) { return $this; @@ -100,6 +98,7 @@ class NonMimeParserService extends AbstractParserService public function parseNextChild(ParserMimePartProxy $proxy) : ?ParserPartProxy { + \assert($proxy instanceof ParserNonMimeMessageProxy); $handle = $proxy->getMessageResourceHandle(); if ($proxy->getNextPartStart() === null || \feof($handle)) { return null; diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartChildrenContainer.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartChildrenContainer.php index 743670e9..03e7a300 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartChildrenContainer.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartChildrenContainer.php @@ -18,22 +18,15 @@ use ZBateson\MailMimeParser\Parser\Proxy\ParserMimePartProxy; */ class ParserPartChildrenContainer extends PartChildrenContainer { - /** - * @var ParserMimePartProxy The parser to proxy requests to when trying to - * get child parts. - */ - protected ParserMimePartProxy $parserProxy; - /** * @var bool Set to true once all parts have been parsed, and requests to * the proxy won't result in any more child parts. */ private bool $allParsed = false; - public function __construct(ParserMimePartProxy $parserProxy) + public function __construct(protected readonly ParserMimePartProxy $parserProxy) { parent::__construct([]); - $this->parserProxy = $parserProxy; } public function offsetExists($offset) : bool diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainer.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainer.php index b8b2f105..c5e00fd7 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainer.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainer.php @@ -14,7 +14,6 @@ use SplSubject; use ZBateson\MailMimeParser\Message\IMessagePart; use ZBateson\MailMimeParser\Message\PartStreamContainer; use ZBateson\MailMimeParser\Parser\Proxy\ParserPartProxy; -use ZBateson\MailMimeParser\Stream\MessagePartStreamDecorator; use ZBateson\MailMimeParser\Stream\StreamFactory; use ZBateson\MbWrapper\MbWrapper; @@ -36,16 +35,10 @@ use ZBateson\MbWrapper\MbWrapper; class ParserPartStreamContainer extends PartStreamContainer implements SplObserver { /** - * @var ParserPartProxy The parser proxy to ferry requests to on-demand. + * @var StreamInterface|null the original stream for a parsed message, used + * when the message hasn't changed */ - protected ParserPartProxy $parserProxy; - - /** - * @var MessagePartStreamDecorator the original stream for a parsed message, - * wrapped in a MessagePartStreamDecorator, and used when the message - * hasn't changed - */ - protected ?MessagePartStreamDecorator $parsedStream = null; + protected ?StreamInterface $parsedStream = null; /** * @var bool set to true if the part's been updated since it was created. @@ -63,10 +56,9 @@ class ParserPartStreamContainer extends PartStreamContainer implements SplObserv StreamFactory $streamFactory, MbWrapper $mbWrapper, bool $throwExceptionReadingPartContentFromUnsupportedCharsets, - ParserPartProxy $parserProxy + protected readonly ParserPartProxy $parserProxy ) { parent::__construct($logger, $streamFactory, $mbWrapper, $throwExceptionReadingPartContentFromUnsupportedCharsets); - $this->parserProxy = $parserProxy; } public function __destruct() @@ -107,9 +99,7 @@ class ParserPartStreamContainer extends PartStreamContainer implements SplObserv $this->parserProxy ) ); - if ($this->parsedStream !== null) { - $this->detachParsedStream = ($this->parsedStream->getMetadata('mmp-detached-stream') === true); - } + $this->detachParsedStream = ($this->parsedStream->getMetadata('mmp-detached-stream') === true); } return $this; } @@ -120,13 +110,13 @@ class ParserPartStreamContainer extends PartStreamContainer implements SplObserv return parent::hasContent(); } - public function getContentStream(IMessagePart $part, ?string $transferEncoding, ?string $fromCharset, ?string $toCharset) : ?MessagePartStreamDecorator + public function getContentStream(IMessagePart $part, ?string $transferEncoding, ?string $fromCharset, ?string $toCharset) : ?StreamInterface { $this->requestParsedContentStream(); return parent::getContentStream($part, $transferEncoding, $fromCharset, $toCharset); } - public function getBinaryContentStream(IMessagePart $part, ?string $transferEncoding = null) : ?MessagePartStreamDecorator + public function getBinaryContentStream(IMessagePart $part, ?string $transferEncoding = null) : ?StreamInterface { $this->requestParsedContentStream(); return parent::getBinaryContentStream($part, $transferEncoding); @@ -142,7 +132,7 @@ class ParserPartStreamContainer extends PartStreamContainer implements SplObserv return $this; } - public function getStream() : MessagePartStreamDecorator + public function getStream() : StreamInterface { $this->requestParsedStream(); if (!$this->partUpdated) { diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainerFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainerFactory.php index e4ca1f15..b6d45649 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainerFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/ParserPartStreamContainerFactory.php @@ -19,24 +19,12 @@ use ZBateson\MbWrapper\MbWrapper; */ class ParserPartStreamContainerFactory { - protected LoggerInterface $logger; - - protected StreamFactory $streamFactory; - - protected MbWrapper $mbWrapper; - - protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets; - public function __construct( - LoggerInterface $logger, - StreamFactory $streamFactory, - MbWrapper $mbWrapper, - bool $throwExceptionReadingPartContentFromUnsupportedCharsets + protected readonly LoggerInterface $logger, + protected readonly StreamFactory $streamFactory, + protected readonly MbWrapper $mbWrapper, + protected readonly bool $throwExceptionReadingPartContentFromUnsupportedCharsets ) { - $this->logger = $logger; - $this->streamFactory = $streamFactory; - $this->mbWrapper = $mbWrapper; - $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; } public function newInstance(ParserPartProxy $parserProxy) : ParserPartStreamContainer diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/UUEncodedPartHeaderContainerFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/UUEncodedPartHeaderContainerFactory.php index 2e4e2e88..3c9239a9 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/UUEncodedPartHeaderContainerFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Part/UUEncodedPartHeaderContainerFactory.php @@ -17,22 +17,10 @@ use ZBateson\MailMimeParser\Header\HeaderFactory; */ class UUEncodedPartHeaderContainerFactory { - protected LoggerInterface $logger; - - /** - * @var HeaderFactory the HeaderFactory passed to - * UUEncodedPartHeaderContainer instances. - */ - protected HeaderFactory $headerFactory; - - /** - * Constructor - * - */ - public function __construct(LoggerInterface $logger, HeaderFactory $headerFactory) - { - $this->logger = $logger; - $this->headerFactory = $headerFactory; + public function __construct( + protected readonly LoggerInterface $logger, + protected readonly HeaderFactory $headerFactory + ) { } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/PartBuilder.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/PartBuilder.php index 840c162a..a0e80d6a 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/PartBuilder.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/PartBuilder.php @@ -12,6 +12,7 @@ use Psr\Http\Message\StreamInterface; use ZBateson\MailMimeParser\Header\HeaderConsts; use ZBateson\MailMimeParser\Message\PartHeaderContainer; use ZBateson\MailMimeParser\Parser\Proxy\ParserMimePartProxy; +use ZBateson\MailMimeParser\Parser\Proxy\ParserPartProxy; /** * Holds generic/all purpose information about a part while it's being parsed. @@ -67,18 +68,18 @@ class PartBuilder private ?StreamInterface $messageStream = null; /** - * @var resource the raw message input stream handle constructed from + * @var resource|null the raw message input stream handle constructed from * $messageStream or null for a child part */ private mixed $messageHandle = null; /** - * @var ParserMimePartProxy The parent proxy part if one is set, or null if + * @var ParserPartProxy|null The parent proxy part if one is set, or null if * the part being built doesn't have a parent. */ - private ?ParserMimePartProxy $parent = null; + private ?ParserPartProxy $parent = null; - public function __construct(PartHeaderContainer $headerContainer, ?StreamInterface $messageStream = null, ?ParserMimePartProxy $parent = null) + public function __construct(PartHeaderContainer $headerContainer, ?StreamInterface $messageStream = null, ?ParserPartProxy $parent = null) { $this->headerContainer = $headerContainer; $this->messageStream = $messageStream; @@ -91,7 +92,7 @@ class PartBuilder public function __destruct() { - if ($this->messageHandle) { + if ($this->messageHandle !== null) { \fclose($this->messageHandle); } } @@ -99,7 +100,7 @@ class PartBuilder /** * The ParserPartProxy parent of this PartBuilder. */ - public function getParent() : ?ParserMimePartProxy + public function getParent() : ?ParserPartProxy { return $this->parent; } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMessageProxyFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMessageProxyFactory.php index 4fa93402..d1c4ae90 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMessageProxyFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMessageProxyFactory.php @@ -27,22 +27,17 @@ use ZBateson\MailMimeParser\Stream\StreamFactory; */ class ParserMessageProxyFactory extends ParserMimePartProxyFactory { - protected MultipartHelper $multipartHelper; - - protected PrivacyHelper $privacyHelper; - public function __construct( LoggerInterface $logger, - StreamFactory $sdf, - PartHeaderContainerFactory $phcf, - ParserPartStreamContainerFactory $pscf, - ParserPartChildrenContainerFactory $ppccf, - MultipartHelper $multipartHelper, - PrivacyHelper $privacyHelper + StreamFactory $streamFactory, + PartHeaderContainerFactory $partHeaderContainerFactory, + ParserPartStreamContainerFactory $parserPartStreamContainerFactory, + ParserPartChildrenContainerFactory $parserPartChildrenContainerFactory, + protected readonly MultipartHelper $multipartHelper, + protected readonly PrivacyHelper $privacyHelper, + string $defaultFallbackCharset = 'ISO-8859-1' ) { - parent::__construct($logger, $sdf, $phcf, $pscf, $ppccf); - $this->multipartHelper = $multipartHelper; - $this->privacyHelper = $privacyHelper; + parent::__construct($logger, $streamFactory, $partHeaderContainerFactory, $parserPartStreamContainerFactory, $parserPartChildrenContainerFactory, $defaultFallbackCharset); } /** @@ -63,7 +58,8 @@ class ParserMessageProxyFactory extends ParserMimePartProxyFactory $headerContainer, $childrenContainer, $this->multipartHelper, - $this->privacyHelper + $this->privacyHelper, + $this->defaultFallbackCharset ); $parserProxy->setPart($message); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxy.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxy.php index fdb8e9f3..f79fcf3c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxy.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxy.php @@ -133,7 +133,12 @@ class ParserMimePartProxy extends ParserPartProxy */ public function getContentType() : ?ParameterHeader { - return $this->getHeaderContainer()->get(HeaderConsts::CONTENT_TYPE); + $header = $this->getHeaderContainer()->get(HeaderConsts::CONTENT_TYPE); + if ($header === null) { + return null; + } + \assert($header instanceof ParameterHeader); + return $header; } /** @@ -163,7 +168,7 @@ class ParserMimePartProxy extends ParserPartProxy public function setEndBoundaryFound(string $line) : bool { $boundary = $this->getMimeBoundary(); - if ($this->getParent() !== null && $this->getParent()->setEndBoundaryFound($line)) { + if ($this->getParent()?->setEndBoundaryFound($line)) { $this->parentBoundaryFound = true; return true; } elseif ($boundary !== null) { @@ -222,7 +227,7 @@ class ParserMimePartProxy extends ParserPartProxy { // check if we're expecting a boundary and didn't find one if (!$this->endBoundaryFound && !$this->parentBoundaryFound) { - if (!empty($this->mimeBoundary) || ($this->getParent() !== null && !empty($this->getParent()->mimeBoundary))) { + if (!empty($this->mimeBoundary) || !empty($this->getParent()->mimeBoundary)) { $this->addError('End boundary for part not found', LogLevel::WARNING); } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxyFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxyFactory.php index 3f2b1dd4..5dd210b2 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxyFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserMimePartProxyFactory.php @@ -9,6 +9,7 @@ namespace ZBateson\MailMimeParser\Parser\Proxy; use Psr\Log\LoggerInterface; use ZBateson\MailMimeParser\Message\Factory\PartHeaderContainerFactory; +use ZBateson\MailMimeParser\Message\IMimePart; use ZBateson\MailMimeParser\Message\MimePart; use ZBateson\MailMimeParser\Parser\IParserService; use ZBateson\MailMimeParser\Parser\Part\ParserPartChildrenContainerFactory; @@ -24,28 +25,14 @@ use ZBateson\MailMimeParser\Stream\StreamFactory; */ class ParserMimePartProxyFactory extends ParserPartProxyFactory { - protected LoggerInterface $logger; - - protected StreamFactory $streamFactory; - - protected ParserPartStreamContainerFactory $parserPartStreamContainerFactory; - - protected PartHeaderContainerFactory $partHeaderContainerFactory; - - protected ParserPartChildrenContainerFactory $parserPartChildrenContainerFactory; - public function __construct( - LoggerInterface $logger, - StreamFactory $sdf, - PartHeaderContainerFactory $phcf, - ParserPartStreamContainerFactory $pscf, - ParserPartChildrenContainerFactory $ppccf + protected readonly LoggerInterface $logger, + protected readonly StreamFactory $streamFactory, + protected readonly PartHeaderContainerFactory $partHeaderContainerFactory, + protected readonly ParserPartStreamContainerFactory $parserPartStreamContainerFactory, + protected readonly ParserPartChildrenContainerFactory $parserPartChildrenContainerFactory, + protected readonly string $defaultFallbackCharset = 'ISO-8859-1' ) { - $this->logger = $logger; - $this->streamFactory = $sdf; - $this->partHeaderContainerFactory = $phcf; - $this->parserPartStreamContainerFactory = $pscf; - $this->parserPartChildrenContainerFactory = $ppccf; } /** @@ -61,12 +48,15 @@ class ParserMimePartProxyFactory extends ParserPartProxyFactory $headerContainer = $this->partHeaderContainerFactory->newInstance($parserProxy->getHeaderContainer()); $childrenContainer = $this->parserPartChildrenContainerFactory->newInstance($parserProxy); + $parent = $partBuilder->getParent()?->getPart(); + \assert($parent === null || $parent instanceof IMimePart); $part = new MimePart( - $partBuilder->getParent()->getPart(), + $parent, $this->logger, $streamContainer, $headerContainer, - $childrenContainer + $childrenContainer, + $this->defaultFallbackCharset ); $parserProxy->setPart($part); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserNonMimeMessageProxyFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserNonMimeMessageProxyFactory.php index 9c8b5e17..0b015803 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserNonMimeMessageProxyFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserNonMimeMessageProxyFactory.php @@ -36,7 +36,8 @@ class ParserNonMimeMessageProxyFactory extends ParserMessageProxyFactory $headerContainer, $childrenContainer, $this->multipartHelper, - $this->privacyHelper + $this->privacyHelper, + $this->defaultFallbackCharset ); $parserProxy->setPart($message); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserPartProxy.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserPartProxy.php index 0743496f..3efc4734 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserPartProxy.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserPartProxy.php @@ -24,25 +24,15 @@ use ZBateson\MailMimeParser\Parser\PartBuilder; */ abstract class ParserPartProxy extends PartBuilder { - /** - * @var IParserService The parser. - */ - protected IParserService $parser; - - /** - * @var PartBuilder The part's PartBuilder. - */ - protected PartBuilder $partBuilder; - /** * @var IMessagePart The part. */ private IMessagePart $part; - public function __construct(PartBuilder $partBuilder, IParserService $parser) - { - $this->partBuilder = $partBuilder; - $this->parser = $parser; + public function __construct( + protected readonly PartBuilder $partBuilder, + protected readonly IParserService $parser + ) { } /** @@ -94,7 +84,7 @@ abstract class ParserPartProxy extends PartBuilder return $this; } - public function getParent() : ?ParserMimePartProxy + public function getParent() : ?ParserPartProxy { return $this->partBuilder->getParent(); } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxy.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxy.php index df3cecf6..4e88410c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxy.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxy.php @@ -20,7 +20,9 @@ class ParserUUEncodedPartProxy extends ParserPartProxy */ public function getParent() : ParserNonMimeMessageProxy { - return parent::getParent(); + $parent = parent::getParent(); + \assert($parent instanceof ParserNonMimeMessageProxy); + return $parent; } /** diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxyFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxyFactory.php index 39baad20..c5876dcc 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxyFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Parser/Proxy/ParserUUEncodedPartProxyFactory.php @@ -8,6 +8,7 @@ namespace ZBateson\MailMimeParser\Parser\Proxy; use Psr\Log\LoggerInterface; +use ZBateson\MailMimeParser\Message\IMimePart; use ZBateson\MailMimeParser\Message\UUEncodedPart; use ZBateson\MailMimeParser\Parser\IParserService; use ZBateson\MailMimeParser\Parser\Part\ParserPartStreamContainerFactory; @@ -22,20 +23,12 @@ use ZBateson\MailMimeParser\Stream\StreamFactory; */ class ParserUUEncodedPartProxyFactory extends ParserPartProxyFactory { - protected LoggerInterface $logger; - - protected StreamFactory $streamFactory; - - protected ParserPartStreamContainerFactory $parserPartStreamContainerFactory; - public function __construct( - LoggerInterface $logger, - StreamFactory $sdf, - ParserPartStreamContainerFactory $parserPartStreamContainerFactory + protected readonly LoggerInterface $logger, + protected readonly StreamFactory $streamFactory, + protected readonly ParserPartStreamContainerFactory $parserPartStreamContainerFactory, + protected readonly string $defaultFallbackCharset = 'ISO-8859-1' ) { - $this->logger = $logger; - $this->streamFactory = $sdf; - $this->parserPartStreamContainerFactory = $parserPartStreamContainerFactory; } /** @@ -46,12 +39,15 @@ class ParserUUEncodedPartProxyFactory extends ParserPartProxyFactory $parserProxy = new ParserUUEncodedPartProxy($partBuilder, $parser); $streamContainer = $this->parserPartStreamContainerFactory->newInstance($parserProxy); + $parent = $partBuilder->getParent()?->getPart(); + \assert($parent === null || $parent instanceof IMimePart); $part = new UUEncodedPart( $parserProxy->getUnixFileMode(), $parserProxy->getFileName(), - $partBuilder->getParent()->getPart(), + $parent, $this->logger, - $streamContainer + $streamContainer, + $this->defaultFallbackCharset ); $parserProxy->setPart($part); diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Stream/HeaderStream.php b/libs/vendor/zbateson/mail-mime-parser/src/Stream/HeaderStream.php index d1eebcb2..0c1fa0f7 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Stream/HeaderStream.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Stream/HeaderStream.php @@ -39,9 +39,8 @@ class HeaderStream extends MessagePartStreamDecorator implements SplObserver, St parent::__construct($part); $part->attach($this); - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); + // Don't initialize $stream - let the StreamDecoratorTrait's __get() + // call createStream() lazily when needed. } public function __destruct() @@ -61,12 +60,14 @@ class HeaderStream extends MessagePartStreamDecorator implements SplObserver, St * * If the part is not a MimePart, Content-Type, Content-Disposition and * Content-Transfer-Encoding headers are generated manually. + * + * @return Traversable */ private function getPartHeadersIterator() : Traversable { if ($this->part instanceof IMimePart) { return $this->part->getRawHeaderIterator(); - } elseif ($this->part->getParent() !== null && $this->part->getParent()->isMime()) { + } elseif ($this->part->getParent()?->isMime()) { return new ArrayIterator([ [HeaderConsts::CONTENT_TYPE, $this->part->getContentType()], [HeaderConsts::CONTENT_DISPOSITION, $this->part->getContentDisposition()], diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStream.php b/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStream.php index c09b5ee5..d3bb777b 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStream.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStream.php @@ -56,9 +56,8 @@ class MessagePartStream extends MessagePartStreamDecorator implements SplObserve $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; $part->attach($this); - // unsetting the property forces the first access to go through - // __get(). - unset($this->stream); + // Don't initialize $stream - let the StreamDecoratorTrait's __get() + // call createStream() lazily when needed. } public function __destruct() @@ -69,8 +68,7 @@ class MessagePartStream extends MessagePartStreamDecorator implements SplObserve public function update(SplSubject $subject) : void { if ($this->appendStream !== null) { - // unset forces recreation in StreamDecoratorTrait with a call to __get - unset($this->stream); + $this->stream = null; $this->appendStream = null; } } @@ -121,9 +119,7 @@ class MessagePartStream extends MessagePartStreamDecorator implements SplObserve $boundary = $part->getHeaderParameter(HeaderConsts::CONTENT_TYPE, 'boundary'); if ($boundary === null) { return \array_map( - function($child) { - return $child->getStream(); - }, + fn($child) => $child->getStream(), $part->getChildParts() ); } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStreamDecorator.php b/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStreamDecorator.php index 6c2fe582..b04e5883 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStreamDecorator.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Stream/MessagePartStreamDecorator.php @@ -20,20 +20,42 @@ use ZBateson\MailMimeParser\Message\IMessagePart; class MessagePartStreamDecorator implements StreamInterface { use StreamDecoratorTrait { + StreamDecoratorTrait::__construct as private traitConstruct; read as private decoratorRead; } - /** - * @var IMessagePart The part to read from. - */ + protected ?StreamInterface $stream = null; + protected IMessagePart $part; - protected ?StreamInterface $stream; - - public function __construct(IMessagePart $part, ?StreamInterface $stream = null) - { + public function __construct( + IMessagePart $part, + ?StreamInterface $stream = null + ) { $this->part = $part; - $this->stream = $stream; + if ($stream !== null) { + $this->stream = $stream; + } + } + + /** + * Returns the underlying stream, lazily creating it via createStream() if + * not yet initialized. + */ + protected function resolveStream() : StreamInterface + { + if ($this->stream === null) { + $this->stream = $this->createStream(); + } + return $this->stream; + } + + public function __get(string $name) : StreamInterface + { + if ($name === 'stream') { + return $this->resolveStream(); + } + throw new \UnexpectedValueException("$name not found on class"); } /** @@ -45,7 +67,7 @@ class MessagePartStreamDecorator implements StreamInterface public function read(int $length) : string { try { - return $this->decoratorRead($length); + return $this->resolveStream()->read($length); } catch (MessagePartStreamReadException $me) { throw $me; } catch (RuntimeException $e) { @@ -58,4 +80,96 @@ class MessagePartStreamDecorator implements StreamInterface ); } } + + public function close() : void + { + $this->resolveStream()->close(); + } + + /** + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->resolveStream()->getMetadata($key); + } + + public function detach() + { + return $this->resolveStream()->detach(); + } + + public function getSize() : ?int + { + return $this->resolveStream()->getSize(); + } + + public function eof() : bool + { + return $this->resolveStream()->eof(); + } + + public function tell() : int + { + return $this->resolveStream()->tell(); + } + + public function isReadable() : bool + { + return $this->resolveStream()->isReadable(); + } + + public function isWritable() : bool + { + return $this->resolveStream()->isWritable(); + } + + public function isSeekable() : bool + { + return $this->resolveStream()->isSeekable(); + } + + public function seek($offset, $whence = SEEK_SET) : void + { + $this->resolveStream()->seek($offset, $whence); + } + + public function write($string) : int + { + return $this->resolveStream()->write($string); + } + + public function rewind() : void + { + $this->seek(0); + } + + public function __toString() : string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + throw $e; + } + } + + public function getContents() : string + { + return \GuzzleHttp\Psr7\Utils::copyToString($this); + } + + /** + * @param array $args + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->resolveStream(), $method]; + $result = ($callable)(...$args); + return $result === $this->stream ? $this : $result; + } } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/Stream/StreamFactory.php b/libs/vendor/zbateson/mail-mime-parser/src/Stream/StreamFactory.php index d6174a4a..b898082d 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/Stream/StreamFactory.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/Stream/StreamFactory.php @@ -27,15 +27,9 @@ use ZBateson\StreamDecorators\UUStream; */ class StreamFactory { - /** - * @var bool if true, saving a content stream with an unsupported charset - * will be written in the default charset. - */ - protected bool $throwExceptionReadingPartContentFromUnsupportedCharsets; - - public function __construct(bool $throwExceptionReadingPartContentFromUnsupportedCharsets) - { - $this->throwExceptionReadingPartContentFromUnsupportedCharsets = $throwExceptionReadingPartContentFromUnsupportedCharsets; + public function __construct( + protected readonly bool $throwExceptionReadingPartContentFromUnsupportedCharsets + ) { } /** @@ -136,24 +130,15 @@ class StreamFactory public function getTransferEncodingDecoratedStream(StreamInterface $stream, ?string $transferEncoding, ?string $filename = null) : StreamInterface { - $decorated = null; - switch ($transferEncoding) { - case 'quoted-printable': - $decorated = $this->newQuotedPrintableStream($stream); - break; - case 'base64': - $decorated = $this->newBase64Stream( - $this->newChunkSplitStream($stream) - ); - break; - case 'x-uuencode': - $decorated = $this->newUUStream($stream); - if ($filename !== null) { - $decorated->setFilename($filename); - } - break; - default: - return $stream; + $decorated = match ($transferEncoding) { + 'quoted-printable' => $this->newQuotedPrintableStream($stream), + 'base64' => $this->newBase64Stream($this->newChunkSplitStream($stream)), + 'x-uuencode' => $this->newUUStream($stream), + default => $stream, + }; + if ($transferEncoding === 'x-uuencode' && $filename !== null) { + \assert($decorated instanceof UUStream); + $decorated->setFilename($filename); } return $decorated; } @@ -169,7 +154,7 @@ class StreamFactory /** * Creates and returns a MessagePartStream */ - public function newMessagePartStream(IMessagePart $part) : MessagePartStreamDecorator + public function newMessagePartStream(IMessagePart $part) : StreamInterface { return new MessagePartStream($this, $part, $this->throwExceptionReadingPartContentFromUnsupportedCharsets); } @@ -191,7 +176,7 @@ class StreamFactory return new HeaderStream($part); } - public function newDecoratedMessagePartStream(IMessagePart $part, StreamInterface $stream) : MessagePartStreamDecorator + public function newDecoratedMessagePartStream(IMessagePart $part, StreamInterface $stream) : StreamInterface { return new MessagePartStreamDecorator($part, $stream); } diff --git a/libs/vendor/zbateson/mail-mime-parser/src/di_config.php b/libs/vendor/zbateson/mail-mime-parser/src/di_config.php index 65bf6165..3aea446c 100644 --- a/libs/vendor/zbateson/mail-mime-parser/src/di_config.php +++ b/libs/vendor/zbateson/mail-mime-parser/src/di_config.php @@ -12,11 +12,18 @@ use Psr\Log\NullLogger; use ZBateson\MailMimeParser\Header\Consumer\Received\DomainConsumerService; use ZBateson\MailMimeParser\Header\Consumer\Received\GenericReceivedConsumerService; use ZBateson\MailMimeParser\Header\Consumer\ReceivedConsumerService; +use ZBateson\MailMimeParser\Message\Factory\IMessagePartFactory; +use ZBateson\MailMimeParser\Message\Factory\IMimePartFactory; +use ZBateson\MailMimeParser\Message\Factory\IUUEncodedPartFactory; use ZBateson\MailMimeParser\Message\Factory\PartStreamContainerFactory; use ZBateson\MailMimeParser\Message\PartStreamContainer; use ZBateson\MailMimeParser\Parser\HeaderParserService; use ZBateson\MailMimeParser\Parser\MimeParserService; use ZBateson\MailMimeParser\Parser\Part\ParserPartStreamContainerFactory; +use ZBateson\MailMimeParser\Parser\Proxy\ParserMessageProxyFactory; +use ZBateson\MailMimeParser\Parser\Proxy\ParserMimePartProxyFactory; +use ZBateson\MailMimeParser\Parser\Proxy\ParserNonMimeMessageProxyFactory; +use ZBateson\MailMimeParser\Parser\Proxy\ParserUUEncodedPartProxyFactory; use ZBateson\MailMimeParser\Stream\StreamFactory; return [ @@ -26,6 +33,11 @@ return [ // header parts 'throwExceptionReadingPartContentFromUnsupportedCharsets' => false, + // Fallback charset for text/* content parts without a declared charset. + // Per RFC 2045, the default is ISO-8859-1 but many modern messages omit the + // charset and are actually UTF-8. Override this to 'UTF-8' if desired. + 'defaultFallbackCharset' => 'ISO-8859-1', + // Maximum multipart nesting depth before parsing stops with a recorded error. 'maxMimePartDepth' => 256, @@ -70,6 +82,34 @@ return [ ->constructor( throwExceptionReadingPartContentFromUnsupportedCharsets: new Reference('throwExceptionReadingPartContentFromUnsupportedCharsets') ), + IMessagePartFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + IMimePartFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + IUUEncodedPartFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + ParserMimePartProxyFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + ParserUUEncodedPartProxyFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + ParserMessageProxyFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), + ParserNonMimeMessageProxyFactory::class => (new AutowireDefinitionHelper()) + ->constructor( + defaultFallbackCharset: new Reference('defaultFallbackCharset') + ), HeaderParserService::class => (new AutowireDefinitionHelper()) ->constructor( maxHeaderCount: new Reference('maxHeaderCount'), diff --git a/libs/vendor/zbateson/mail-mime-parser/version.txt b/libs/vendor/zbateson/mail-mime-parser/version.txt index 818bd47a..c4e41f94 100644 --- a/libs/vendor/zbateson/mail-mime-parser/version.txt +++ b/libs/vendor/zbateson/mail-mime-parser/version.txt @@ -1 +1 @@ -3.0.6 +4.0.3 diff --git a/libs/vendor/zbateson/mb-wrapper/PhpCsFixer.php b/libs/vendor/zbateson/mb-wrapper/PhpCsFixer.php new file mode 100644 index 00000000..2559b40a --- /dev/null +++ b/libs/vendor/zbateson/mb-wrapper/PhpCsFixer.php @@ -0,0 +1,277 @@ +setRiskyAllowed(true) + ->setIndent(' ') + ->setLineEnding("\n") + ->setRules([ + // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. + 'align_multiline_comment' => ['comment_type'=>'all_multiline'], + // Each element of an array must be indented exactly once. + 'array_indentation' => true, + // Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`. + 'array_push' => true, + // PHP arrays should be declared using the configured syntax. + 'array_syntax' => ['syntax'=>'short'], + // Converts backtick operators to `shell_exec` calls. + 'backtick_to_shell_exec' => true, + // Binary operators should be surrounded by space as configured. + 'binary_operator_spaces' => true, + // There MUST be one blank line after the namespace declaration. + 'blank_line_after_namespace' => true, + // Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line. + 'blank_line_after_opening_tag' => true, + // An empty line feed must precede any configured statement. + 'blank_line_before_statement' => false, + // A single space or none should be between cast and variable. + 'cast_spaces' => ['space'=>'single'], + // Class, trait and interface elements must be separated with one or none blank line. + 'class_attributes_separation' => true, + // Whitespace around the keywords of a class, trait or interfaces definition should be one space. + 'class_definition' => true, + // Namespace must not contain spacing, comments or PHPDoc. + 'clean_namespace' => true, + // Using `isset($var) &&` multiple times should be done in one call. + 'combine_consecutive_issets' => true, + // Calling `unset` on multiple items should be done in one call. + 'combine_consecutive_unsets' => true, + // Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0. + 'combine_nested_dirname' => false, + // Comments with annotation should be docblock when used on structural elements. + 'comment_to_phpdoc' => false, + // Remove extra spaces in a nullable typehint. + 'compact_nullable_type_declaration' => true, + // Concatenation should be spaced according configuration. + 'concat_space' => ['spacing'=>'one'], + // The PHP constants `true`, `false`, and `null` MUST be written using the correct casing. + 'constant_case' => true, + // Equal sign in declare statement should be surrounded by spaces or not following configuration. + 'declare_equal_normalize' => ['space'=>'single'], + // Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant. + 'dir_constant' => true, + // The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words. + 'elseif' => true, + // PHP code MUST use only UTF-8 without BOM (remove BOM). + 'encoding' => true, + // Replace deprecated `ereg` regular expression functions with `preg`. + 'ereg_to_preg' => true, + // Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0. + 'explicit_indirect_variable' => false, + // Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax. + 'explicit_string_variable' => false, + // Order the flags in `fopen` calls, `b` and `t` must be last. + 'fopen_flag_order' => true, + // PHP code must use the long ` true, + // Spaces should be properly placed in a function declaration. + 'function_declaration' => ['closure_function_spacing'=>'none'], + // Replace core functions calls returning constants with the constants. + 'function_to_constant' => true, + // Ensure single space between function's argument and its typehint. + 'type_declaration_spaces' => true, + // Renames PHPDoc tags. + 'general_phpdoc_tag_rename' => true, + // Function `implode` must be called with 2 arguments in the documented order. + 'implode_call' => true, + // Include/Require and file path should be divided with a single space. File path should not be placed under brackets. + 'include' => true, + // Code MUST use configured indentation type. + 'indentation_type' => true, + // Replaces `is_null($var)` expression with `null === $var`. + 'is_null' => true, + // All PHP files must use same line ending. + 'line_ending' => true, + // Ensure there is no code on the same line as the PHP open tag. + 'linebreak_after_opening_tag' => true, + // List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1. + 'list_syntax' => false, + // Use `&&` and `||` logical operators instead of `and` and `or`. + 'logical_operators' => true, + // Cast should be written in lower case. + 'lowercase_cast' => true, + // PHP keywords MUST be in lower case. + 'lowercase_keywords' => true, + // Class static references `self`, `static` and `parent` MUST be in lower case. + 'lowercase_static_reference' => true, + // Magic constants should be referred to using the correct casing. + 'magic_constant_casing' => true, + // Magic method definitions and calls must be using the correct casing. + 'magic_method_casing' => true, + // In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line. + 'method_argument_space' => true, + // Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported. + 'method_chaining_indentation' => true, + // Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator. + 'modernize_types_casting' => true, + // Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls. + 'multiline_whitespace_before_semicolons' => true, + // Function defined by PHP should be called using the correct casing. + 'native_function_casing' => true, + // Add leading `\` before function invocation to speed up resolving. + 'native_function_invocation' => ['include'=>['@all','trans']], + // Native type hints for functions should use the correct case. + 'native_type_declaration_casing' => true, + // All instances created with new keyword must be followed by braces. + 'new_with_parentheses' => true, + // Master functions shall be used instead of aliases. + 'no_alias_functions' => true, + // Master language constructs shall be used instead of aliases. + 'no_alias_language_construct_call' => true, + // Replace control structure alternative syntax to use braces. + 'no_alternative_syntax' => true, + // There should not be blank lines between docblock and the documented element. + 'no_blank_lines_after_phpdoc' => true, + // There must be a comment when fall-through is intentional in a non-empty case body. + 'no_break_comment' => ['comment_text'=>'Intentionally fall through'], + // The closing `? >` tag MUST be omitted from files containing only PHP. + 'no_closing_tag' => true, + // There should not be any empty comments. + 'no_empty_comment' => true, + // There should not be empty PHPDoc blocks. + 'no_empty_phpdoc' => true, + // Remove useless (semicolon) statements. + 'no_empty_statement' => true, + // Replace accidental usage of homoglyphs (non ascii characters) in names. + 'no_homoglyph_names' => true, + // Remove leading slashes in `use` clauses. + 'no_leading_import_slash' => true, + // The namespace declaration line shouldn't contain leading whitespace. + 'no_leading_namespace_whitespace' => true, + // Either language construct `print` or `echo` should be used. + 'no_mixed_echo_print' => true, + // Operator `=>` should not be surrounded by multi-line whitespaces. + 'no_multiline_whitespace_around_double_arrow' => true, + // Convert PHP4-style constructors to `__construct`. + 'no_php4_constructor' => true, + // Short cast `bool` using double exclamation mark should not be used. + 'no_short_bool_cast' => true, + // When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis. + 'no_spaces_after_function_name' => true, + // Removes `@param`, `@return` and `@var` tags that don't provide any useful information. + 'no_superfluous_phpdoc_tags' => true, + // Remove trailing whitespace at the end of non-blank lines. + 'no_trailing_whitespace' => true, + // There MUST be no trailing spaces inside comment or PHPDoc. + 'no_trailing_whitespace_in_comment' => true, + // Removes unneeded parentheses around control statements. + 'no_unneeded_control_parentheses' => false, + // Removes unneeded curly braces that are superfluous and aren't part of a control structure's body. + 'no_unneeded_braces' => true, + // A `final` class must not have `final` methods and `private` methods must not be `final`. + 'no_unneeded_final_method' => true, + // In function arguments there must not be arguments with default values before non-default ones. + 'no_unreachable_default_argument_value' => true, + // Variables must be set `null` instead of using `(unset)` casting. + 'no_unset_cast' => true, + // Properties should be set to `null` instead of using `unset`. + 'no_unset_on_property' => false, + // Unused `use` statements must be removed. + 'no_unused_imports' => true, + // There should not be useless `else` cases. + 'no_useless_else' => true, + // There should not be an empty `return` statement at the end of a function. + 'no_useless_return' => true, + // There must be no `sprintf` calls with only the first argument. + 'no_useless_sprintf' => true, + // In array declaration, there MUST NOT be a whitespace before each comma. + 'no_whitespace_before_comma_in_array' => true, + // Remove trailing whitespace at the end of blank lines. + 'no_whitespace_in_blank_line' => true, + // Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. + 'non_printable_character' => ['use_escape_sequences_in_strings'=>true], + // Array index should always be written by using square braces. + 'normalize_index_brace' => true, + // Logical NOT operators (`!`) should have one trailing whitespace. + 'not_operator_with_successor_space' => false, + // Adds or removes `?` before type declarations for parameters with a default `null` value. + 'nullable_type_declaration_for_default_null_value' => true, + // There should not be space before or after object operators `->` and `?->`. + 'object_operator_without_whitespace' => true, + // Orders the elements of classes/interfaces/traits. + 'ordered_class_elements' => ['order'=>['use_trait','constant_public','constant_protected','constant_private','property_public','property_protected','property_private']], + // Ordering `use` statements. + 'ordered_imports' => true, + // Orders the interfaces in an `implements` or `interface extends` clause. + 'ordered_interfaces' => true, + // Trait `use` statements must be sorted alphabetically. + 'ordered_traits' => true, + // Classy that does not inherit must not have `@inheritdoc` tags. + 'phpdoc_no_useless_inheritdoc' => true, + // Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations. + 'phpdoc_order' => true, + // The type of `@return` annotations of methods returning a reference to itself must the configured one. + 'phpdoc_return_self_reference' => true, + // Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`. + 'phpdoc_scalar' => true, + // Fixes casing of PHPDoc tags. + 'phpdoc_tag_casing' => true, + // Converts `protected` variables and methods to `private` where possible. + 'protected_to_private' => true, + // Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name. + 'psr_autoloading' => true, + // There should be one or no space before colon, and one space after it in return type declarations, according to configuration. + 'return_type_declaration' => ['space_before'=>'one'], + // Instructions must be terminated with a semicolon. + 'semicolon_after_instruction' => true, + // Cast shall be used, not `settype`. + 'set_type_to_cast' => true, + // Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`. + 'short_scalar_cast' => true, + // Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`). + 'simple_to_complex_string_variable' => false, + // Simplify `if` control structures that return the boolean result of their condition. + 'simplified_if_return' => true, + // A return statement wishing to return `void` should not return `null`. + 'simplified_null_return' => false, + // A PHP file without end tag must always end with a single empty line feed. + 'single_blank_line_at_eof' => true, + // There should be exactly one blank line before a namespace declaration. + 'blank_lines_before_namespace' => ['max_line_breaks' => 2, 'min_line_breaks' => 2], + // There MUST NOT be more than one property or constant declared per statement. + 'single_class_element_per_statement' => true, + // There MUST be one use keyword per declaration. + 'single_import_per_statement' => true, + // Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block. + 'single_line_after_imports' => true, + // Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax. + 'single_line_comment_style' => true, + // Convert double quotes to single quotes for simple strings. + 'single_quote' => true, + // Each trait `use` must be done as single statement. + 'single_trait_insert_per_statement' => true, + // There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis. + 'spaces_inside_parentheses' => true, + // Replace all `<>` with `!=`. + 'standardize_not_equals' => true, + // Lambdas not (indirect) referencing `$this` must be declared `static`. + 'static_lambda' => false, + // All multi-line strings must use correct line ending. + 'string_line_ending' => true, + // A case should be followed by a colon and not a semicolon. + 'switch_case_semicolon_to_colon' => true, + // Removes extra spaces between colon and case value. + 'switch_case_space' => true, + // Switch case must not be ended with `continue` but with `break`. + 'switch_continue_to_break' => true, + // Standardize spaces around ternary operator. + 'ternary_operator_spaces' => true, + // Use the Elvis operator `?:` where possible. + 'ternary_to_elvis_operator' => true, + // Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0. + 'ternary_to_null_coalescing' => false, + // Arrays should be formatted like function/method arguments, without leading or trailing single line space. + 'trim_array_spaces' => true, + // Unary operators should be placed adjacent to their operands. + 'unary_operator_spaces' => true, + // Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility. + 'visibility_required' => true, + // Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1. + 'void_return' => true, + // In array declaration, there MUST be a whitespace after each comma. + 'whitespace_after_comma_in_array' => true, + // Write conditions in Yoda style (`true`), non-Yoda style (`['equal' => false, 'identical' => false, 'less_and_greater' => false]`) or ignore those conditions (`null`) based on configuration. + 'yoda_style' => false, + ]); + diff --git a/libs/vendor/zbateson/mb-wrapper/README.md b/libs/vendor/zbateson/mb-wrapper/README.md index b1567003..c043777e 100644 --- a/libs/vendor/zbateson/mb-wrapper/README.md +++ b/libs/vendor/zbateson/mb-wrapper/README.md @@ -2,51 +2,41 @@ Charset conversion and string manipulation wrapper with a large defined set of aliases. -[![Tests](https://github.com/zbateson/mb-wrapper.svg/actions/workflows/tests.yml/badge.svg)](https://github.com/zbateson/mb-wrapper.svg/actions/workflows/tests.yml) -[![Code Coverage](https://scrutinizer-ci.com/g/zbateson/mb-wrapper/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/zbateson/mb-wrapper/?branch=master) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zbateson/mb-wrapper/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/zbateson/mb-wrapper/?branch=master) +[![Build Status](https://github.com/zbateson/mb-wrapper/actions/workflows/tests.yml/badge.svg)](https://github.com/zbateson/mb-wrapper/actions/workflows/tests.yml) [![Total Downloads](https://poser.pugx.org/zbateson/mb-wrapper/downloads)](https://packagist.org/packages/zbateson/mb-wrapper) [![Latest Stable Version](https://poser.pugx.org/zbateson/mb-wrapper/version)](https://packagist.org/packages/zbateson/mb-wrapper) -The goals of this project are to be: - -* Well written -* Tested where possible -* Support as wide a range of charset aliases as possible - -To include it for use in your project, please install via composer: - ``` composer require zbateson/mb-wrapper ``` -## Php 7 Support Dropped +## Sponsors -As of mb-wrapper 2.0, support for php 7 has been dropped. +[![SecuMailer](https://mail-mime-parser.org/sponsors/logo-secumailer.png)](https://secumailer.com) + +A huge thank you to [all my sponsors](https://github.com/sponsors/zbateson). <3 + +If this project's helped you, please consider [sponsoring me](https://github.com/sponsors/zbateson). ## Requirements -mb-wrapper requires PHP 8.0 or newer. Tested on PHP 8.0, 8.1, 8.2, and 8.3 on GitHub Actions. - -## New in 2.0 - -If converting or performing an operation on a string fails in iconv, an UnsupportedCharsetException is now thrown. +PHP 8.1 or newer. Tested on PHP 8.1, 8.2, 8.3, 8.4, and 8.5. ## Description -MbWrapper is intended for use wherever mb_* or iconv_* is used. It scans supported charsets returned by mb_list_encodings(), and prefers mb_* functions, but will fallback to iconv if a charset isn't supported by the mb_ functions. +MbWrapper is intended for use wherever `mb_*` or `iconv_*` is used. It scans supported charsets returned by `mb_list_encodings()`, and prefers `mb_*` functions, but will fallback to `iconv` if a charset isn't supported by the `mb_*` functions. -A list of aliased charsets is maintained for both mb_* and iconv, where a supported charset exists for an alias. This is useful for mail and http parsing as other systems may report encodings not recognized by mb_* or iconv. +A list of aliased charsets is maintained for both `mb_*` and `iconv`, where a supported charset exists for an alias. This is useful for mail and http parsing as other systems may report encodings not recognized by `mb_*` or `iconv`. -Charset lookup is done by removing non-alphanumeric characters as well, so UTF8 will always be matched to UTF-8, etc... +Charset lookup is done by removing non-alphanumeric characters as well, so `UTF8` will always be matched to `UTF-8`, etc. ## Usage The following wrapper methods are exposed: -* mb_convert_encoding, iconv with MbWrapper::convert -* mb_substr, iconv_substr with MbWrapper::getSubstr -* mb_strlen, iconv_strlen with MbWrapper::getLength -* mb_check_encoding, iconv (for verification) with MbWrapper::checkEncoding +* `mb_convert_encoding`, `iconv` with `MbWrapper::convert` +* `mb_substr`, `iconv_substr` with `MbWrapper::getSubstr` +* `mb_strlen`, `iconv_strlen` with `MbWrapper::getLength` +* `mb_check_encoding`, `iconv` (for verification) with `MbWrapper::checkEncoding` ```php $mbWrapper = new \ZBateson\MbWrapper\MbWrapper(); diff --git a/libs/vendor/zbateson/mb-wrapper/composer.json b/libs/vendor/zbateson/mb-wrapper/composer.json index 6eb4ecb4..f51a7ea7 100644 --- a/libs/vendor/zbateson/mb-wrapper/composer.json +++ b/libs/vendor/zbateson/mb-wrapper/composer.json @@ -9,12 +9,12 @@ } ], "require": { - "php": ">=8.0", + "php": ">=8.1", "symfony/polyfill-mbstring": "^1.9", "symfony/polyfill-iconv": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^9.6|^10.0", + "phpunit/phpunit": "^10.0|^11.0", "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*" }, diff --git a/libs/vendor/zbateson/mb-wrapper/src/MbWrapper.php b/libs/vendor/zbateson/mb-wrapper/src/MbWrapper.php index 31acd5ae..8e3aa20e 100644 --- a/libs/vendor/zbateson/mb-wrapper/src/MbWrapper.php +++ b/libs/vendor/zbateson/mb-wrapper/src/MbWrapper.php @@ -1,9 +1,11 @@ aliased charsets supported by mb_convert_encoding. - * The alias is stripped of any non-alphanumeric characters (so CP367 - * is equal to CP-367) when comparing. - * Some of these translations are already supported by - * mb_convert_encoding on "my" PHP 5.5.9, but may not be supported in - * other implementations or versions since they're not part of - * documented support. + * Aliased charsets supported by mb_convert_encoding. + * The alias is stripped of any non-alphanumeric characters (so CP367 + * is equal to CP-367) when comparing. + * Some of these translations are already supported by + * mb_convert_encoding on "my" PHP 5.5.9, but may not be supported in + * other implementations or versions since they're not part of + * documented support. */ - public static $mbAliases = [ + public const MB_ALIASES = [ // supported but not included in mb_list_encodings for some reason... 'CP850' => 'CP850', 'GB2312' => 'GB18030', @@ -186,9 +188,9 @@ class MbWrapper ]; /** - * @var array aliased charsets supported by iconv. + * Aliased charsets supported by iconv. */ - public static $iconvAliases = [ + public const ICONV_ALIASES = [ // iconv aliases -- a lot of these may already be supported 'CESU8' => 'UTF8', 'CP154' => 'PT154', @@ -278,21 +280,12 @@ class MbWrapper '1258' => 'CP1258', ]; - /** - * @var string[] cached lookups for quicker retrieval - */ - protected $mappedMbCharsets = [ - 'UTF8' => 'UTF-8', - 'USASCII' => 'US-ASCII', - 'ISO88591' => 'ISO-8859-1', - ]; - /** * @var string[] An array of encodings supported by the mb_* extension, as * returned by mb_list_encodings(), with the key set to the charset's * name afte */ - private static $mbListedEncodings; + private static ?array $mbListedEncodings = null; /** * Initializes the static mb_* encoding array. @@ -311,19 +304,28 @@ class MbWrapper * characters before being returned. * * @param string|string[] $charset - * @return string|string[] + * @return ($charset is string ? string : string[]) */ - private function getNormalizedCharset($charset) + private function getNormalizedCharset(string|array $charset) : string|array { - $upper = null; if (\is_array($charset)) { - $upper = \array_map('strtoupper', $charset); + $upper = \array_map(\strtoupper(...), $charset); } else { $upper = \strtoupper($charset); } return \preg_replace('/[^A-Z0-9]+/', '', $upper); } + /** + * Strips iconv conversion modifiers (e.g. '//TRANSLIT', '//IGNORE') that are + * not part of a charset name. + */ + private function stripCharsetConversionModifier(string $cs) : string + { + $pos = \strpos($cs, '//'); + return ($pos === false) ? $cs : \substr($cs, 0, $pos); + } + private function iconv(string $fromCharset, string $toCharset, string $str) : string { $ret = @\iconv($fromCharset, $toCharset . '//TRANSLIT//IGNORE', $str); @@ -332,7 +334,7 @@ class MbWrapper } return $ret; } - + private function iconvStrlen(string $str, string $charset) : int { $ret = @\iconv_strlen($str, $charset . '//TRANSLIT//IGNORE'); @@ -341,7 +343,7 @@ class MbWrapper } return $ret; } - + private function iconvSubstr(string $str, string $charset, int $start, ?int $length = null) : string { $ret = @\iconv_substr($str, $start, $length, $charset . '//TRANSLIT//IGNORE'); @@ -429,7 +431,7 @@ class MbWrapper /** * Uses either mb_substr or iconv_substr to create and return a substring of * the passed $str. - * + * * If the offset provided in $start is greater than the length of the * string, an empty string is returned. * @@ -459,30 +461,30 @@ class MbWrapper * * On success, the method will return the charset name as accepted by mb_*. * - * @return string|bool */ - private function getMbCharset(string $cs) + private function getMbCharset(string $cs) : string|false { - $normalized = $this->getNormalizedCharset($cs); + $normalized = $this->getNormalizedCharset($this->stripCharsetConversionModifier($cs)); if (\array_key_exists($normalized, self::$mbListedEncodings)) { return self::$mbListedEncodings[$normalized]; - } elseif (\array_key_exists($normalized, self::$mbAliases)) { - return self::$mbAliases[$normalized]; + } elseif (\array_key_exists($normalized, self::MB_ALIASES)) { + return self::MB_ALIASES[$normalized]; } return false; } /** - * Looks up the passed charset in self::$iconvAliases, returning the mapped + * Looks up the passed charset in self::ICONV_ALIASES, returning the mapped * charset if applicable. Otherwise returns charset. * * @return string the mapped charset (if mapped) or $cs otherwise */ private function getIconvAlias(string $cs) : string { + $cs = $this->stripCharsetConversionModifier($cs); $normalized = $this->getNormalizedCharset($cs); - if (\array_key_exists($normalized, self::$iconvAliases)) { - return static::$iconvAliases[$normalized]; + if (\array_key_exists($normalized, self::ICONV_ALIASES)) { + return self::ICONV_ALIASES[$normalized]; } return $cs; } diff --git a/libs/vendor/zbateson/mb-wrapper/src/UnsupportedCharsetException.php b/libs/vendor/zbateson/mb-wrapper/src/UnsupportedCharsetException.php index 6eb6e32b..51129755 100644 --- a/libs/vendor/zbateson/mb-wrapper/src/UnsupportedCharsetException.php +++ b/libs/vendor/zbateson/mb-wrapper/src/UnsupportedCharsetException.php @@ -1,4 +1,5 @@ =8.0", - "guzzlehttp/psr7": "^2.5", - "zbateson/mb-wrapper": "^2.0" + "php": ">=8.1", + "guzzlehttp/psr7": "^2.5 || ^3.0", + "zbateson/mb-wrapper": "^2.0 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^9.6|^10.0", + "phpunit/phpunit": "^10.0 || ^11.0", "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*" }, diff --git a/libs/vendor/zbateson/stream-decorators/src/Base64Stream.php b/libs/vendor/zbateson/stream-decorators/src/Base64Stream.php index 7b79c558..e01431c2 100644 --- a/libs/vendor/zbateson/stream-decorators/src/Base64Stream.php +++ b/libs/vendor/zbateson/stream-decorators/src/Base64Stream.php @@ -47,7 +47,7 @@ class Base64Stream implements StreamInterface /** * @var BufferStream buffered bytes */ - private BufferStream $buffer; + private readonly BufferStream $buffer; /** * @var string remainder of write operation if the bytes didn't align to 3 @@ -60,14 +60,8 @@ class Base64Stream implements StreamInterface */ private int $position = 0; - /** - * @var StreamInterface $stream - */ - private StreamInterface $stream; - - public function __construct(StreamInterface $stream) + public function __construct(private readonly StreamInterface $stream) { - $this->stream = $stream; $this->buffer = new BufferStream(); } @@ -98,7 +92,7 @@ class Base64Stream implements StreamInterface * @param int $whence * @throws RuntimeException */ - public function seek($offset, $whence = SEEK_SET) : void + public function seek($offset, $whence = SEEK_SET) : never { throw new RuntimeException('Cannot seek a Base64Stream'); } diff --git a/libs/vendor/zbateson/stream-decorators/src/CharsetStream.php b/libs/vendor/zbateson/stream-decorators/src/CharsetStream.php index a5170406..6ac0bf1e 100644 --- a/libs/vendor/zbateson/stream-decorators/src/CharsetStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/CharsetStream.php @@ -24,18 +24,7 @@ class CharsetStream implements StreamInterface /** * @var MbWrapper the charset converter */ - protected MbWrapper $converter; - - /** - * @var string charset of the source stream - */ - protected string $streamCharset = 'ISO-8859-1'; - - /** - * @var string charset of strings passed in write operations, and returned - * in read operations. - */ - protected string $stringCharset = 'UTF-8'; + protected readonly MbWrapper $converter; /** * @var int current read/write position @@ -53,23 +42,18 @@ class CharsetStream implements StreamInterface */ private string $buffer = ''; - /** - * @var StreamInterface $stream - */ - private StreamInterface $stream; - /** * @param StreamInterface $stream Stream to decorate * @param string $streamCharset The underlying stream's charset * @param string $stringCharset The charset to encode strings to (or * expected for write) */ - public function __construct(StreamInterface $stream, string $streamCharset = 'ISO-8859-1', string $stringCharset = 'UTF-8') - { - $this->stream = $stream; + public function __construct( + private readonly StreamInterface $stream, + protected readonly string $streamCharset = 'ISO-8859-1', + protected readonly string $stringCharset = 'UTF-8', + ) { $this->converter = new MbWrapper(); - $this->streamCharset = $streamCharset; - $this->stringCharset = $stringCharset; } /** @@ -97,7 +81,7 @@ class CharsetStream implements StreamInterface * @param int $whence * @throws RuntimeException */ - public function seek($offset, $whence = SEEK_SET) : void + public function seek($offset, $whence = SEEK_SET) : never { throw new RuntimeException('Cannot seek a CharsetStream'); } diff --git a/libs/vendor/zbateson/stream-decorators/src/ChunkSplitStream.php b/libs/vendor/zbateson/stream-decorators/src/ChunkSplitStream.php index f90bed4d..e2fe0864 100644 --- a/libs/vendor/zbateson/stream-decorators/src/ChunkSplitStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/ChunkSplitStream.php @@ -25,34 +25,18 @@ class ChunkSplitStream implements StreamInterface * final $lineEnding on close (and so maintained instead of using * tell() directly) */ - private int $position; - - /** - * @var int The number of characters in a line before inserting $lineEnding. - */ - private int $lineLength; - - /** - * @var string The line ending characters to insert. - */ - private string $lineEnding; + private int $position = 0; /** * @var int The strlen() of $lineEnding */ - private int $lineEndingLength; + private readonly int $lineEndingLength; - /** - * @var StreamInterface $stream - */ - private StreamInterface $stream; - - public function __construct(StreamInterface $stream, int $lineLength = 76, string $lineEnding = "\r\n") - { - $this->stream = $stream; - $this->position = 0; - $this->lineLength = $lineLength; - $this->lineEnding = $lineEnding; + public function __construct( + private readonly StreamInterface $stream, + private readonly int $lineLength = 76, + private readonly string $lineEnding = "\r\n", + ) { $this->lineEndingLength = \strlen($this->lineEnding); } diff --git a/libs/vendor/zbateson/stream-decorators/src/DecoratedCachingStream.php b/libs/vendor/zbateson/stream-decorators/src/DecoratedCachingStream.php index a2b20b1b..48a37bd4 100644 --- a/libs/vendor/zbateson/stream-decorators/src/DecoratedCachingStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/DecoratedCachingStream.php @@ -33,13 +33,13 @@ class DecoratedCachingStream implements StreamInterface /** * @var StreamInterface the stream to read from and fill writeStream with */ - private StreamInterface $readStream; + private readonly StreamInterface $readStream; /** * @var StreamInterface the underlying undecorated stream to read from, * where $writeStream is being written to */ - private StreamInterface $stream; + private readonly StreamInterface $stream; /** * @var StreamInterface decorated $stream that will be written to for @@ -48,13 +48,6 @@ class DecoratedCachingStream implements StreamInterface */ private ?StreamInterface $writeStream; - /** - * @var int Minimum buffer read length. At least this many bytes will be - * read and cached into $writeStream on each call to read from - * $readStream - */ - private int $minBytesCache; - /** * @param StreamInterface $stream Stream to cache. The cursor is assumed to * be at the beginning of the stream. @@ -65,13 +58,12 @@ class DecoratedCachingStream implements StreamInterface public function __construct( StreamInterface $stream, callable $decorator, - int $minBytesCache = 16384 + private readonly int $minBytesCache = 16384, ) { $this->readStream = $stream; $bufferStream = new TellZeroStream(new BufferStream()); $this->stream = new CachingStream($bufferStream); $this->writeStream = $decorator(new NonClosingStream($bufferStream)); - $this->minBytesCache = $minBytesCache; } public function getSize(): ?int @@ -142,7 +134,7 @@ class DecoratedCachingStream implements StreamInterface return false; } - public function write($string): int + public function write($string): never { throw new \RuntimeException('Cannot write to a DecoratedCachingStream'); } @@ -167,9 +159,9 @@ class DecoratedCachingStream implements StreamInterface private function cacheEntireStream(): int { // as-is from CachingStream - $target = new FnStream(['write' => 'strlen']); + $target = new FnStream(['write' => strlen(...)]); Utils::copyToStream($this, $target); return $this->tell(); } -} \ No newline at end of file +} diff --git a/libs/vendor/zbateson/stream-decorators/src/NonClosingStream.php b/libs/vendor/zbateson/stream-decorators/src/NonClosingStream.php index 77bf6ff2..35a52c41 100644 --- a/libs/vendor/zbateson/stream-decorators/src/NonClosingStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/NonClosingStream.php @@ -51,7 +51,7 @@ class NonClosingStream implements StreamInterface */ public function close() : void { - $this->stream = null; // @phpstan-ignore-line + $this->stream = null; } /** @@ -61,7 +61,7 @@ class NonClosingStream implements StreamInterface */ public function detach() { - $this->stream = null; // @phpstan-ignore-line + $this->stream = null; return null; } } diff --git a/libs/vendor/zbateson/stream-decorators/src/PregReplaceFilterStream.php b/libs/vendor/zbateson/stream-decorators/src/PregReplaceFilterStream.php index 74fd5d4a..2577d51b 100644 --- a/libs/vendor/zbateson/stream-decorators/src/PregReplaceFilterStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/PregReplaceFilterStream.php @@ -24,31 +24,16 @@ class PregReplaceFilterStream implements StreamInterface { use StreamDecoratorTrait; - /** - * @var string The regex pattern - */ - private string $pattern; - - /** - * @var string The replacement - */ - private string $replacement; - /** * @var BufferStream Buffered stream of input from the underlying stream */ - private BufferStream $buffer; + private readonly BufferStream $buffer; - /** - * @var StreamInterface $stream - */ - private StreamInterface $stream; - - public function __construct(StreamInterface $stream, string $pattern, string $replacement) - { - $this->stream = $stream; - $this->pattern = $pattern; - $this->replacement = $replacement; + public function __construct( + private readonly StreamInterface $stream, + private readonly string $pattern, + private readonly string $replacement, + ) { $this->buffer = new BufferStream(); } @@ -67,7 +52,7 @@ class PregReplaceFilterStream implements StreamInterface * @param int $whence * @throws RuntimeException */ - public function seek($offset, $whence = SEEK_SET) : void + public function seek($offset, $whence = SEEK_SET) : never { throw new RuntimeException('Cannot seek a PregReplaceFilterStream'); } diff --git a/libs/vendor/zbateson/stream-decorators/src/QuotedPrintableStream.php b/libs/vendor/zbateson/stream-decorators/src/QuotedPrintableStream.php index 89fab31b..a8c54124 100644 --- a/libs/vendor/zbateson/stream-decorators/src/QuotedPrintableStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/QuotedPrintableStream.php @@ -34,7 +34,7 @@ class QuotedPrintableStream implements StreamInterface * @var StreamInterface $stream * @phpstan-ignore-next-line */ - private StreamInterface $stream; + private readonly StreamInterface $stream; /** * Overridden to return the position in the target encoding. @@ -61,7 +61,7 @@ class QuotedPrintableStream implements StreamInterface * @param int $whence * @throws RuntimeException */ - public function seek($offset, $whence = SEEK_SET) : void + public function seek($offset, $whence = SEEK_SET) : never { throw new RuntimeException('Cannot seek a QuotedPrintableStream'); } @@ -107,7 +107,7 @@ class QuotedPrintableStream implements StreamInterface */ private function decodeBlock(string $block) : string { - if (\substr($block, -1) === '=') { + if (\str_ends_with($block, '=')) { $block .= $this->readEncodedChars(2); } elseif (\substr($block, -2, 1) === '=') { $first = \substr($block, -1); diff --git a/libs/vendor/zbateson/stream-decorators/src/SeekingLimitStream.php b/libs/vendor/zbateson/stream-decorators/src/SeekingLimitStream.php index 5ee89a0f..a528e4af 100644 --- a/libs/vendor/zbateson/stream-decorators/src/SeekingLimitStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/SeekingLimitStream.php @@ -37,11 +37,6 @@ class SeekingLimitStream implements StreamInterface */ private int $position = 0; - /** - * @var StreamInterface $stream - */ - private StreamInterface $stream; - /** * @param StreamInterface $stream Stream to wrap * @param int $limit Total number of bytes to allow to be read @@ -49,9 +44,8 @@ class SeekingLimitStream implements StreamInterface * @param int $offset Position to seek to before reading (only * works on seekable streams). */ - public function __construct(StreamInterface $stream, int $limit = -1, int $offset = 0) + public function __construct(private readonly StreamInterface $stream, int $limit = -1, int $offset = 0) { - $this->stream = $stream; $this->setLimit($limit); $this->setOffset($offset); } @@ -123,17 +117,11 @@ class SeekingLimitStream implements StreamInterface */ public function seek($offset, $whence = SEEK_SET) : void { - $pos = $offset; - switch ($whence) { - case SEEK_CUR: - $pos = $this->position + $offset; - break; - case SEEK_END: - $pos = $this->limit + $offset; - break; - default: - break; - } + $pos = match ($whence) { + SEEK_CUR => $this->position + $offset, + SEEK_END => $this->limit + $offset, + default => $offset, + }; $this->doSeek($pos); } diff --git a/libs/vendor/zbateson/stream-decorators/src/TellZeroStream.php b/libs/vendor/zbateson/stream-decorators/src/TellZeroStream.php index f64e25be..434c09e9 100644 --- a/libs/vendor/zbateson/stream-decorators/src/TellZeroStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/TellZeroStream.php @@ -21,11 +21,12 @@ class TellZeroStream implements StreamInterface /** * @var StreamInterface + * @phpstan-ignore-next-line */ - private StreamInterface $stream; + private readonly StreamInterface $stream; public function tell() : int { return 0; } -} \ No newline at end of file +} diff --git a/libs/vendor/zbateson/stream-decorators/src/UUStream.php b/libs/vendor/zbateson/stream-decorators/src/UUStream.php index abeec216..11fa29c8 100644 --- a/libs/vendor/zbateson/stream-decorators/src/UUStream.php +++ b/libs/vendor/zbateson/stream-decorators/src/UUStream.php @@ -26,43 +26,44 @@ class UUStream implements StreamInterface use StreamDecoratorTrait; /** - * @var string name of the UUEncoded file + * @var ?string name of the UUEncoded file */ - protected $filename = null; + protected ?string $filename = null; /** * @var BufferStream of read and decoded bytes */ - private $buffer; + private readonly BufferStream $buffer; /** * @var string remainder of write operation if the bytes didn't align to 3 * bytes */ - private $remainder = ''; + private string $remainder = ''; + + /** + * @var string bytes read past the last line ending, carried to the next read + */ + private string $lineRemainder = ''; /** * @var int read/write position */ - private $position = 0; + private int $position = 0; /** * @var bool set to true when 'write' is called */ - private $isWriting = false; - - /** - * @var StreamInterface $stream - */ - private $stream; + private bool $isWriting = false; /** * @param StreamInterface $stream Stream to decorate * @param string $filename optional file name */ - public function __construct(StreamInterface $stream, ?string $filename = null) - { - $this->stream = $stream; + public function __construct( + private readonly StreamInterface $stream, + ?string $filename = null, + ) { $this->filename = $filename; $this->buffer = new BufferStream(); } @@ -92,7 +93,7 @@ class UUStream implements StreamInterface * @param int $whence * @throws RuntimeException */ - public function seek($offset, $whence = SEEK_SET) : void + public function seek($offset, $whence = SEEK_SET) : never { throw new RuntimeException('Cannot seek a UUStream'); } @@ -111,17 +112,23 @@ class UUStream implements StreamInterface */ private function readToEndOfLine(int $length) : string { - $str = $this->stream->read($length); + $str = $this->lineRemainder . $this->stream->read($length); + $this->lineRemainder = ''; if ($str === '') { return $str; } - while (\substr($str, -1) !== "\n") { - $chr = $this->stream->read(1); + while (\strpos($str, "\n") === false) { + $chr = $this->stream->read($length); if ($chr === '') { break; } $str .= $chr; } + $eol = \strrpos($str, "\n"); + if ($eol !== false && $eol < \strlen($str) - 1) { + $this->lineRemainder = \substr($str, $eol + 1); + $str = \substr($str, 0, $eol + 1); + } return $str; } @@ -139,10 +146,9 @@ class UUStream implements StreamInterface $this->filename = $matches[1]; } $ret = \preg_replace('/^\s*begin[^\r\n]+\s*$/im', '', $ret); - } else { - $ret = \preg_replace('/^\s*end\s*$/im', '', $ret); } - return \convert_uudecode(\trim($ret)); + $ret = \trim(\preg_replace('/^\s*end\s*$/im', '', $ret)); + return ($ret === '') ? '' : \convert_uudecode($ret); } /** @@ -168,7 +174,7 @@ class UUStream implements StreamInterface */ public function eof() : bool { - return ($this->buffer->eof() && $this->stream->eof()); + return ($this->buffer->eof() && $this->stream->eof() && $this->lineRemainder === ''); } /** @@ -264,7 +270,7 @@ class UUStream implements StreamInterface /** * Returns the filename set in the UUEncoded header (or null) */ - public function getFilename() : string + public function getFilename() : ?string { return $this->filename; }