webauthn
This commit is contained in:
parent
4d5d93c62b
commit
2d90e73b2f
|
@ -3,6 +3,7 @@
|
||||||
"vendor-dir": "lib/3rdParty/composer"
|
"vendor-dir": "lib/3rdParty/composer"
|
||||||
},
|
},
|
||||||
"require" : {
|
"require" : {
|
||||||
"web-auth/webauthn-lib" : "2.1.7"
|
"web-auth/webauthn-lib" : "2.1.7",
|
||||||
|
"symfony/http-foundation" : "5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "df09efa5a29ab9799e4e0c0ff066bfef",
|
"content-hash": "ba2488b4997bb9328901b5be3ed4bed0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "beberlei/assert",
|
"name": "beberlei/assert",
|
||||||
|
@ -646,17 +646,134 @@
|
||||||
"time": "2019-08-15T14:53:55+00:00"
|
"time": "2019-08-15T14:53:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/http-foundation",
|
||||||
"version": "v1.12.0",
|
"version": "v5.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/http-foundation.git",
|
||||||
"reference": "550ebaac289296ce228a706d0867afc34687e3f4"
|
"reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
|
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c5c226b6f164ae4f95c4bffbe940c81050940eda",
|
||||||
"reference": "550ebaac289296ce228a706d0867afc34687e3f4",
|
"reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5",
|
||||||
|
"symfony/mime": "^4.4|^5.0",
|
||||||
|
"symfony/polyfill-mbstring": "~1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"predis/predis": "~1.0",
|
||||||
|
"symfony/expression-language": "^4.4|^5.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\HttpFoundation\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony HttpFoundation Component",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"time": "2019-11-18T17:27:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/mime",
|
||||||
|
"version": "v5.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/mime.git",
|
||||||
|
"reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/mime/zipball/76f3c09b7382bf979af7bcd8e6f8033f1324285e",
|
||||||
|
"reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5",
|
||||||
|
"symfony/polyfill-intl-idn": "^1.10",
|
||||||
|
"symfony/polyfill-mbstring": "^1.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/mailer": "<4.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"egulias/email-validator": "^2.1.10",
|
||||||
|
"symfony/dependency-injection": "^4.4|^5.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Mime\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A library to manipulate MIME messages",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"mime",
|
||||||
|
"mime-type"
|
||||||
|
],
|
||||||
|
"time": "2019-11-18T17:27:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
|
||||||
|
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -668,7 +785,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.12-dev"
|
"dev-master": "1.13-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -701,7 +818,183 @@
|
||||||
"polyfill",
|
"polyfill",
|
||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"time": "2019-08-06T08:03:45+00:00"
|
"time": "2019-11-27T13:56:44+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-intl-idn",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||||
|
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
||||||
|
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3",
|
||||||
|
"symfony/polyfill-php72": "^1.9"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Intl\\Idn\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Laurent Bassin",
|
||||||
|
"email": "laurent@bassin.info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"idn",
|
||||||
|
"intl",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"time": "2019-11-27T13:56:44+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
|
||||||
|
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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 for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"time": "2019-11-27T14:18:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-php72",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||||
|
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038",
|
||||||
|
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Php72\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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 7.2+ features to lower PHP versions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"time": "2019-11-27T13:56:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "web-auth/cose-lib",
|
"name": "web-auth/cose-lib",
|
||||||
|
|
|
@ -542,7 +542,7 @@ class WebauthnProvider extends BaseProvider {
|
||||||
*/
|
*/
|
||||||
public function verify2ndFactor($user, $password, $serial, $twoFactorInput) {
|
public function verify2ndFactor($user, $password, $serial, $twoFactorInput) {
|
||||||
logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user);
|
logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user);
|
||||||
$response = $_POST['sig_response'];
|
$response = base64_decode($_POST['sig_response']);
|
||||||
include_once __DIR__ . '/3rdParty/composer/autoload.php';
|
include_once __DIR__ . '/3rdParty/composer/autoload.php';
|
||||||
include_once __DIR__ . '/webauthn.inc';
|
include_once __DIR__ . '/webauthn.inc';
|
||||||
$registrationObject = $_SESSION['webauthn_registration'];
|
$registrationObject = $_SESSION['webauthn_registration'];
|
||||||
|
|
|
@ -6,6 +6,9 @@ $vendorDir = dirname(dirname(__FILE__));
|
||||||
$baseDir = dirname(dirname(dirname($vendorDir)));
|
$baseDir = dirname(dirname(dirname($vendorDir)));
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php',
|
'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php',
|
||||||
|
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
||||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||||
|
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,12 @@ $baseDir = dirname(dirname(dirname($vendorDir)));
|
||||||
return array(
|
return array(
|
||||||
'Webauthn\\MetadataService\\' => array($vendorDir . '/web-auth/metadata-service/src'),
|
'Webauthn\\MetadataService\\' => array($vendorDir . '/web-auth/metadata-service/src'),
|
||||||
'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'),
|
'Webauthn\\' => array($vendorDir . '/web-auth/webauthn-lib/src'),
|
||||||
|
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
|
||||||
|
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
|
||||||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
||||||
|
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
|
||||||
|
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
|
||||||
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
|
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
|
||||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
||||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
||||||
|
|
|
@ -7,8 +7,11 @@ namespace Composer\Autoload;
|
||||||
class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
|
class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
|
||||||
{
|
{
|
||||||
public static $files = array (
|
public static $files = array (
|
||||||
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
|
'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
|
||||||
|
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
|
||||||
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
||||||
|
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $prefixLengthsPsr4 = array (
|
public static $prefixLengthsPsr4 = array (
|
||||||
|
@ -19,7 +22,12 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
|
||||||
),
|
),
|
||||||
'S' =>
|
'S' =>
|
||||||
array (
|
array (
|
||||||
|
'Symfony\\Polyfill\\Php72\\' => 23,
|
||||||
|
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
|
||||||
'Symfony\\Polyfill\\Ctype\\' => 23,
|
'Symfony\\Polyfill\\Ctype\\' => 23,
|
||||||
|
'Symfony\\Component\\Mime\\' => 23,
|
||||||
|
'Symfony\\Component\\HttpFoundation\\' => 33,
|
||||||
),
|
),
|
||||||
'R' =>
|
'R' =>
|
||||||
array (
|
array (
|
||||||
|
@ -66,10 +74,30 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src',
|
0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Polyfill\\Php72\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
|
||||||
|
),
|
||||||
|
'Symfony\\Polyfill\\Mbstring\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||||
|
),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
|
||||||
|
),
|
||||||
'Symfony\\Polyfill\\Ctype\\' =>
|
'Symfony\\Polyfill\\Ctype\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
|
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Component\\Mime\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/mime',
|
||||||
|
),
|
||||||
|
'Symfony\\Component\\HttpFoundation\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/http-foundation',
|
||||||
|
),
|
||||||
'Ramsey\\Uuid\\' =>
|
'Ramsey\\Uuid\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
|
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
|
||||||
|
|
|
@ -661,18 +661,139 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/http-foundation",
|
||||||
"version": "v1.12.0",
|
"version": "v5.0.0",
|
||||||
"version_normalized": "1.12.0.0",
|
"version_normalized": "5.0.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/http-foundation.git",
|
||||||
"reference": "550ebaac289296ce228a706d0867afc34687e3f4"
|
"reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
|
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c5c226b6f164ae4f95c4bffbe940c81050940eda",
|
||||||
"reference": "550ebaac289296ce228a706d0867afc34687e3f4",
|
"reference": "c5c226b6f164ae4f95c4bffbe940c81050940eda",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5",
|
||||||
|
"symfony/mime": "^4.4|^5.0",
|
||||||
|
"symfony/polyfill-mbstring": "~1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"predis/predis": "~1.0",
|
||||||
|
"symfony/expression-language": "^4.4|^5.0"
|
||||||
|
},
|
||||||
|
"time": "2019-11-18T17:27:11+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\HttpFoundation\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony HttpFoundation Component",
|
||||||
|
"homepage": "https://symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/mime",
|
||||||
|
"version": "v5.0.0",
|
||||||
|
"version_normalized": "5.0.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/mime.git",
|
||||||
|
"reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/mime/zipball/76f3c09b7382bf979af7bcd8e6f8033f1324285e",
|
||||||
|
"reference": "76f3c09b7382bf979af7bcd8e6f8033f1324285e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5",
|
||||||
|
"symfony/polyfill-intl-idn": "^1.10",
|
||||||
|
"symfony/polyfill-mbstring": "^1.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/mailer": "<4.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"egulias/email-validator": "^2.1.10",
|
||||||
|
"symfony/dependency-injection": "^4.4|^5.0"
|
||||||
|
},
|
||||||
|
"time": "2019-11-18T17:27:11+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Mime\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A library to manipulate MIME messages",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"mime",
|
||||||
|
"mime-type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"version_normalized": "1.13.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
|
||||||
|
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -681,11 +802,11 @@
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-ctype": "For best performance"
|
"ext-ctype": "For best performance"
|
||||||
},
|
},
|
||||||
"time": "2019-08-06T08:03:45+00:00",
|
"time": "2019-11-27T13:56:44+00:00",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.12-dev"
|
"dev-master": "1.13-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installation-source": "dist",
|
"installation-source": "dist",
|
||||||
|
@ -720,6 +841,188 @@
|
||||||
"portable"
|
"portable"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-intl-idn",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"version_normalized": "1.13.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||||
|
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
||||||
|
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3",
|
||||||
|
"symfony/polyfill-php72": "^1.9"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "For best performance"
|
||||||
|
},
|
||||||
|
"time": "2019-11-27T13:56:44+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Intl\\Idn\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Laurent Bassin",
|
||||||
|
"email": "laurent@bassin.info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"idn",
|
||||||
|
"intl",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"version_normalized": "1.13.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
|
||||||
|
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"time": "2019-11-27T14:18:11+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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 for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-php72",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"version_normalized": "1.13.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||||
|
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038",
|
||||||
|
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"time": "2019-11-27T13:56:44+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.13-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Php72\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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 7.2+ features to lower PHP versions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "web-auth/cose-lib",
|
"name": "web-auth/cose-lib",
|
||||||
"version": "v2.1.7",
|
"version": "v2.1.7",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
/Tests export-ignore
|
||||||
|
/phpunit.xml.dist export-ignore
|
||||||
|
/.gitignore export-ignore
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Accept-* header.
|
||||||
|
*
|
||||||
|
* An accept header is compound with a list of items,
|
||||||
|
* sorted by descending quality.
|
||||||
|
*
|
||||||
|
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||||
|
*/
|
||||||
|
class AcceptHeader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AcceptHeaderItem[]
|
||||||
|
*/
|
||||||
|
private $items = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $sorted = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AcceptHeaderItem[] $items
|
||||||
|
*/
|
||||||
|
public function __construct(array $items)
|
||||||
|
{
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$this->add($item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an AcceptHeader instance from a string.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function fromString(?string $headerValue)
|
||||||
|
{
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
$parts = HeaderUtils::split($headerValue ?? '', ',;=');
|
||||||
|
|
||||||
|
return new self(array_map(function ($subParts) use (&$index) {
|
||||||
|
$part = array_shift($subParts);
|
||||||
|
$attributes = HeaderUtils::combine($subParts);
|
||||||
|
|
||||||
|
$item = new AcceptHeaderItem($part[0], $attributes);
|
||||||
|
$item->setIndex($index++);
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
}, $parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns header value's string representation.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return implode(',', $this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if header has given value.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $value)
|
||||||
|
{
|
||||||
|
return isset($this->items[$value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns given value's item, if exists.
|
||||||
|
*
|
||||||
|
* @return AcceptHeaderItem|null
|
||||||
|
*/
|
||||||
|
public function get(string $value)
|
||||||
|
{
|
||||||
|
return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an item.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function add(AcceptHeaderItem $item)
|
||||||
|
{
|
||||||
|
$this->items[$item->getValue()] = $item;
|
||||||
|
$this->sorted = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all items.
|
||||||
|
*
|
||||||
|
* @return AcceptHeaderItem[]
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
$this->sort();
|
||||||
|
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters items on their value using given regex.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function filter(string $pattern)
|
||||||
|
{
|
||||||
|
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
|
||||||
|
return preg_match($pattern, $item->getValue());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns first item.
|
||||||
|
*
|
||||||
|
* @return AcceptHeaderItem|null
|
||||||
|
*/
|
||||||
|
public function first()
|
||||||
|
{
|
||||||
|
$this->sort();
|
||||||
|
|
||||||
|
return !empty($this->items) ? reset($this->items) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts items by descending quality.
|
||||||
|
*/
|
||||||
|
private function sort(): void
|
||||||
|
{
|
||||||
|
if (!$this->sorted) {
|
||||||
|
uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
|
||||||
|
$qA = $a->getQuality();
|
||||||
|
$qB = $b->getQuality();
|
||||||
|
|
||||||
|
if ($qA === $qB) {
|
||||||
|
return $a->getIndex() > $b->getIndex() ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $qA > $qB ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->sorted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Accept-* header item.
|
||||||
|
*
|
||||||
|
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||||
|
*/
|
||||||
|
class AcceptHeaderItem
|
||||||
|
{
|
||||||
|
private $value;
|
||||||
|
private $quality = 1.0;
|
||||||
|
private $index = 0;
|
||||||
|
private $attributes = [];
|
||||||
|
|
||||||
|
public function __construct(string $value, array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
foreach ($attributes as $name => $value) {
|
||||||
|
$this->setAttribute($name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an AcceptHeaderInstance instance from a string.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function fromString(?string $itemValue)
|
||||||
|
{
|
||||||
|
$parts = HeaderUtils::split($itemValue ?? '', ';=');
|
||||||
|
|
||||||
|
$part = array_shift($parts);
|
||||||
|
$attributes = HeaderUtils::combine($parts);
|
||||||
|
|
||||||
|
return new self($part[0], $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns header value's string representation.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
$string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
|
||||||
|
if (\count($this->attributes) > 0) {
|
||||||
|
$string .= '; '.HeaderUtils::toString($this->attributes, ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the item value.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setValue(string $value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item value.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the item quality.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setQuality(float $quality)
|
||||||
|
{
|
||||||
|
$this->quality = $quality;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item quality.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public function getQuality()
|
||||||
|
{
|
||||||
|
return $this->quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the item index.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setIndex(int $index)
|
||||||
|
{
|
||||||
|
$this->index = $index;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item index.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getIndex()
|
||||||
|
{
|
||||||
|
return $this->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if an attribute exists.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasAttribute(string $name)
|
||||||
|
{
|
||||||
|
return isset($this->attributes[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an attribute by its name.
|
||||||
|
*
|
||||||
|
* @param mixed $default
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getAttribute(string $name, $default = null)
|
||||||
|
{
|
||||||
|
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all attributes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAttributes()
|
||||||
|
{
|
||||||
|
return $this->attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an attribute.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setAttribute(string $name, string $value)
|
||||||
|
{
|
||||||
|
if ('q' === $name) {
|
||||||
|
$this->quality = (float) $value;
|
||||||
|
} else {
|
||||||
|
$this->attributes[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BinaryFileResponse represents an HTTP response delivering a file.
|
||||||
|
*
|
||||||
|
* @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
|
||||||
|
* @author stealth35 <stealth35-php@live.fr>
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
||||||
|
* @author Sergey Linnik <linniksa@gmail.com>
|
||||||
|
*/
|
||||||
|
class BinaryFileResponse extends Response
|
||||||
|
{
|
||||||
|
protected static $trustXSendfileTypeHeader = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var File
|
||||||
|
*/
|
||||||
|
protected $file;
|
||||||
|
protected $offset = 0;
|
||||||
|
protected $maxlen = -1;
|
||||||
|
protected $deleteFileAfterSend = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \SplFileInfo|string $file The file to stream
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
* @param bool $public Files are public by default
|
||||||
|
* @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||||
|
* @param bool $autoEtag Whether the ETag header should be automatically set
|
||||||
|
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
|
||||||
|
*/
|
||||||
|
public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||||
|
{
|
||||||
|
parent::__construct(null, $status, $headers);
|
||||||
|
|
||||||
|
$this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
|
||||||
|
|
||||||
|
if ($public) {
|
||||||
|
$this->setPublic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \SplFileInfo|string $file The file to stream
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
* @param bool $public Files are public by default
|
||||||
|
* @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||||
|
* @param bool $autoEtag Whether the ETag header should be automatically set
|
||||||
|
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||||
|
{
|
||||||
|
return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the file to stream.
|
||||||
|
*
|
||||||
|
* @param \SplFileInfo|string $file The file to stream
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws FileException
|
||||||
|
*/
|
||||||
|
public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||||
|
{
|
||||||
|
if (!$file instanceof File) {
|
||||||
|
if ($file instanceof \SplFileInfo) {
|
||||||
|
$file = new File($file->getPathname());
|
||||||
|
} else {
|
||||||
|
$file = new File((string) $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$file->isReadable()) {
|
||||||
|
throw new FileException('File must be readable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->file = $file;
|
||||||
|
|
||||||
|
if ($autoEtag) {
|
||||||
|
$this->setAutoEtag();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($autoLastModified) {
|
||||||
|
$this->setAutoLastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($contentDisposition) {
|
||||||
|
$this->setContentDisposition($contentDisposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file.
|
||||||
|
*
|
||||||
|
* @return File The file to stream
|
||||||
|
*/
|
||||||
|
public function getFile()
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically sets the Last-Modified header according the file modification date.
|
||||||
|
*/
|
||||||
|
public function setAutoLastModified()
|
||||||
|
{
|
||||||
|
$this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically sets the ETag header according to the checksum of the file.
|
||||||
|
*/
|
||||||
|
public function setAutoEtag()
|
||||||
|
{
|
||||||
|
$this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Content-Disposition header with the given filename.
|
||||||
|
*
|
||||||
|
* @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
|
||||||
|
* @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
|
||||||
|
* @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '')
|
||||||
|
{
|
||||||
|
if ('' === $filename) {
|
||||||
|
$filename = $this->file->getFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
|
||||||
|
$encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
|
||||||
|
|
||||||
|
for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
|
||||||
|
$char = mb_substr($filename, $i, 1, $encoding);
|
||||||
|
|
||||||
|
if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
|
||||||
|
$filenameFallback .= '_';
|
||||||
|
} else {
|
||||||
|
$filenameFallback .= $char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
|
||||||
|
$this->headers->set('Content-Disposition', $dispositionHeader);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function prepare(Request $request)
|
||||||
|
{
|
||||||
|
if (!$this->headers->has('Content-Type')) {
|
||||||
|
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
|
||||||
|
$this->setProtocolVersion('1.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ensureIEOverSSLCompatibility($request);
|
||||||
|
|
||||||
|
$this->offset = 0;
|
||||||
|
$this->maxlen = -1;
|
||||||
|
|
||||||
|
if (false === $fileSize = $this->file->getSize()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$this->headers->set('Content-Length', $fileSize);
|
||||||
|
|
||||||
|
if (!$this->headers->has('Accept-Ranges')) {
|
||||||
|
// Only accept ranges on safe HTTP methods
|
||||||
|
$this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
|
||||||
|
// Use X-Sendfile, do not send any content.
|
||||||
|
$type = $request->headers->get('X-Sendfile-Type');
|
||||||
|
$path = $this->file->getRealPath();
|
||||||
|
// Fall back to scheme://path for stream wrapped locations.
|
||||||
|
if (false === $path) {
|
||||||
|
$path = $this->file->getPathname();
|
||||||
|
}
|
||||||
|
if ('x-accel-redirect' === strtolower($type)) {
|
||||||
|
// Do X-Accel-Mapping substitutions.
|
||||||
|
// @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
|
||||||
|
$parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',=');
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
list($pathPrefix, $location) = $part;
|
||||||
|
if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
|
||||||
|
$path = $location.substr($path, \strlen($pathPrefix));
|
||||||
|
// Only set X-Accel-Redirect header if a valid URI can be produced
|
||||||
|
// as nginx does not serve arbitrary file paths.
|
||||||
|
$this->headers->set($type, $path);
|
||||||
|
$this->maxlen = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->headers->set($type, $path);
|
||||||
|
$this->maxlen = 0;
|
||||||
|
}
|
||||||
|
} elseif ($request->headers->has('Range')) {
|
||||||
|
// Process the range headers.
|
||||||
|
if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
|
||||||
|
$range = $request->headers->get('Range');
|
||||||
|
|
||||||
|
list($start, $end) = explode('-', substr($range, 6), 2) + [0];
|
||||||
|
|
||||||
|
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
|
||||||
|
|
||||||
|
if ('' === $start) {
|
||||||
|
$start = $fileSize - $end;
|
||||||
|
$end = $fileSize - 1;
|
||||||
|
} else {
|
||||||
|
$start = (int) $start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($start <= $end) {
|
||||||
|
if ($start < 0 || $end > $fileSize - 1) {
|
||||||
|
$this->setStatusCode(416);
|
||||||
|
$this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
|
||||||
|
} elseif (0 !== $start || $end !== $fileSize - 1) {
|
||||||
|
$this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
|
||||||
|
$this->offset = $start;
|
||||||
|
|
||||||
|
$this->setStatusCode(206);
|
||||||
|
$this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
|
||||||
|
$this->headers->set('Content-Length', $end - $start + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasValidIfRangeHeader(?string $header): bool
|
||||||
|
{
|
||||||
|
if ($this->getEtag() === $header) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $lastModified = $this->getLastModified()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the file.
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function sendContent()
|
||||||
|
{
|
||||||
|
if (!$this->isSuccessful()) {
|
||||||
|
return parent::sendContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $this->maxlen) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = fopen('php://output', 'wb');
|
||||||
|
$file = fopen($this->file->getPathname(), 'rb');
|
||||||
|
|
||||||
|
stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
|
||||||
|
|
||||||
|
fclose($out);
|
||||||
|
fclose($file);
|
||||||
|
|
||||||
|
if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
|
||||||
|
unlink($this->file->getPathname());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \LogicException when the content is not null
|
||||||
|
*/
|
||||||
|
public function setContent(?string $content)
|
||||||
|
{
|
||||||
|
if (null !== $content) {
|
||||||
|
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trust X-Sendfile-Type header.
|
||||||
|
*/
|
||||||
|
public static function trustXSendfileTypeHeader()
|
||||||
|
{
|
||||||
|
self::$trustXSendfileTypeHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is set to true, the file will be unlinked after the request is send
|
||||||
|
* Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function deleteFileAfterSend(bool $shouldDelete = true)
|
||||||
|
{
|
||||||
|
$this->deleteFileAfterSend = $shouldDelete;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
5.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* made `Cookie` auto-secure and lax by default
|
||||||
|
* removed classes in the `MimeType` namespace, use the Symfony Mime component instead
|
||||||
|
* removed method `UploadedFile::getClientSize()` and the related constructor argument
|
||||||
|
* made `Request::getSession()` throw if the session has not been set before
|
||||||
|
* removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL`
|
||||||
|
* passing a null url when instantiating a `RedirectResponse` is not allowed
|
||||||
|
|
||||||
|
4.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* passing arguments to `Request::isMethodSafe()` is deprecated.
|
||||||
|
* `ApacheRequest` is deprecated, use the `Request` class instead.
|
||||||
|
* passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead
|
||||||
|
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
|
||||||
|
make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
|
||||||
|
to speed up garbage collection of expired sessions.
|
||||||
|
* added `SessionHandlerFactory` to create session handlers with a DSN
|
||||||
|
* added `IpUtils::anonymize()` to help with GDPR compliance.
|
||||||
|
|
||||||
|
4.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`,
|
||||||
|
`ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame`
|
||||||
|
* deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`.
|
||||||
|
* deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`.
|
||||||
|
* deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`.
|
||||||
|
* deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`.
|
||||||
|
* added `UrlHelper` that allows to get an absolute URL and a relative path for a given path
|
||||||
|
|
||||||
|
4.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* the default value of the "$secure" and "$samesite" arguments of Cookie's constructor
|
||||||
|
will respectively change from "false" to "null" and from "null" to "lax" in Symfony
|
||||||
|
5.0, you should define their values explicitly or use "Cookie::create()" instead.
|
||||||
|
* added `matchPort()` in RequestMatcher
|
||||||
|
|
||||||
|
4.1.3
|
||||||
|
-----
|
||||||
|
|
||||||
|
* [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL`
|
||||||
|
HTTP headers has been dropped for security reasons.
|
||||||
|
|
||||||
|
4.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Query string normalization uses `parse_str()` instead of custom parsing logic.
|
||||||
|
* Passing the file size to the constructor of the `UploadedFile` class is deprecated.
|
||||||
|
* The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead.
|
||||||
|
* added `RedisSessionHandler` to use Redis as a session storage
|
||||||
|
* The `get()` method of the `AcceptHeader` class now takes into account the
|
||||||
|
`*` and `*/*` default values (if they are present in the Accept HTTP header)
|
||||||
|
when looking for items.
|
||||||
|
* deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead.
|
||||||
|
* added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`,
|
||||||
|
`IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to
|
||||||
|
handle failed `UploadedFile`.
|
||||||
|
* added `MigratingSessionHandler` for migrating between two session handlers without losing sessions
|
||||||
|
* added `HeaderUtils`.
|
||||||
|
|
||||||
|
4.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()`
|
||||||
|
methods have been removed
|
||||||
|
* the `Request::HEADER_CLIENT_IP` constant has been removed, use
|
||||||
|
`Request::HEADER_X_FORWARDED_FOR` instead
|
||||||
|
* the `Request::HEADER_CLIENT_HOST` constant has been removed, use
|
||||||
|
`Request::HEADER_X_FORWARDED_HOST` instead
|
||||||
|
* the `Request::HEADER_CLIENT_PROTO` constant has been removed, use
|
||||||
|
`Request::HEADER_X_FORWARDED_PROTO` instead
|
||||||
|
* the `Request::HEADER_CLIENT_PORT` constant has been removed, use
|
||||||
|
`Request::HEADER_X_FORWARDED_PORT` instead
|
||||||
|
* checking for cacheable HTTP methods using the `Request::isMethodSafe()`
|
||||||
|
method (by not passing `false` as its argument) is not supported anymore and
|
||||||
|
throws a `\BadMethodCallException`
|
||||||
|
* the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed
|
||||||
|
* setting session save handlers that do not implement `\SessionHandlerInterface` in
|
||||||
|
`NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a
|
||||||
|
`\TypeError`
|
||||||
|
|
||||||
|
3.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
|
||||||
|
`AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
|
||||||
|
* deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
|
||||||
|
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
|
||||||
|
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
|
||||||
|
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
|
||||||
|
|
||||||
|
3.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument,
|
||||||
|
see https://symfony.com/doc/current/deployment/proxies.html for more info,
|
||||||
|
* deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods,
|
||||||
|
* added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown,
|
||||||
|
disabling `Range` and `Content-Length` handling, switching to chunked encoding instead
|
||||||
|
* added the `Cookie::fromString()` method that allows to create a cookie from a
|
||||||
|
raw header string
|
||||||
|
|
||||||
|
3.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Added support for creating `JsonResponse` with a string of JSON data
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY"
|
||||||
|
|
||||||
|
2.8.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
|
||||||
|
will be removed in 3.0.
|
||||||
|
|
||||||
|
2.6.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* PdoSessionHandler changes
|
||||||
|
- implemented different session locking strategies to prevent loss of data by concurrent access to the same session
|
||||||
|
- [BC BREAK] save session data in a binary column without base64_encode
|
||||||
|
- [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
|
||||||
|
- implemented lazy connections that are only opened when a session is used by either passing a dsn string
|
||||||
|
explicitly or falling back to session.save_path ini setting
|
||||||
|
- added a createTable method that initializes a correctly defined table depending on the database vendor
|
||||||
|
|
||||||
|
2.5.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
|
||||||
|
of the options used while encoding data to JSON format.
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added RequestStack
|
||||||
|
* added Request::getEncodings()
|
||||||
|
* added accessors methods to session handlers
|
||||||
|
|
||||||
|
2.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added support for ranges of IPs in trusted proxies
|
||||||
|
* `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
|
||||||
|
* Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
|
||||||
|
to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
|
||||||
|
to verify that Exceptions are properly thrown when the PDO queries fail.
|
||||||
|
|
||||||
|
2.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* fixed the Request::create() precedence (URI information always take precedence now)
|
||||||
|
* added Request::getTrustedProxies()
|
||||||
|
* deprecated Request::isProxyTrusted()
|
||||||
|
* [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
|
||||||
|
* added a IpUtils class to check if an IP belongs to a CIDR
|
||||||
|
* added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
|
||||||
|
* disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
|
||||||
|
enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
|
||||||
|
* Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
|
||||||
|
* Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
|
||||||
|
|
||||||
|
2.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added Request::getSchemeAndHttpHost() and Request::getUserInfo()
|
||||||
|
* added a fluent interface to the Response class
|
||||||
|
* added Request::isProxyTrusted()
|
||||||
|
* added JsonResponse
|
||||||
|
* added a getTargetUrl method to RedirectResponse
|
||||||
|
* added support for streamed responses
|
||||||
|
* made Response::prepare() method the place to enforce HTTP specification
|
||||||
|
* [BC BREAK] moved management of the locale from the Session class to the Request class
|
||||||
|
* added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
|
||||||
|
* made FileBinaryMimeTypeGuesser command configurable
|
||||||
|
* added Request::getUser() and Request::getPassword()
|
||||||
|
* added support for the PATCH method in Request
|
||||||
|
* removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
|
||||||
|
* added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
|
||||||
|
* made mimetype to extension conversion configurable
|
||||||
|
* [BC BREAK] Moved all session related classes and interfaces into own namespace, as
|
||||||
|
`Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
|
||||||
|
Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
|
||||||
|
* SessionHandlers must implement `\SessionHandlerInterface` or extend from the
|
||||||
|
`Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
|
||||||
|
* Added internal storage driver proxy mechanism for forward compatibility with
|
||||||
|
PHP 5.4 `\SessionHandler` class.
|
||||||
|
* Added session handlers for custom Memcache, Memcached and Null session save handlers.
|
||||||
|
* [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
|
||||||
|
* [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
|
||||||
|
`remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
|
||||||
|
is a mediator for the session storage internals including the session handlers
|
||||||
|
which do the real work of participating in the internal PHP session workflow.
|
||||||
|
* [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
|
||||||
|
and functional testing without starting real PHP sessions. Removed
|
||||||
|
`ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
|
||||||
|
tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
|
||||||
|
for functional tests. These do not interact with global session ini
|
||||||
|
configuration values, session functions or `$_SESSION` superglobal. This means
|
||||||
|
they can be configured directly allowing multiple instances to work without
|
||||||
|
conflicting in the same PHP process.
|
||||||
|
* [BC BREAK] Removed the `close()` method from the `Session` class, as this is
|
||||||
|
now redundant.
|
||||||
|
* Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
|
||||||
|
`getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
|
||||||
|
which returns a `FlashBagInterface`.
|
||||||
|
* `Session->clear()` now only clears session attributes as before it cleared
|
||||||
|
flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
|
||||||
|
* Session data is now managed by `SessionBagInterface` to better encapsulate
|
||||||
|
session data.
|
||||||
|
* Refactored session attribute and flash messages system to their own
|
||||||
|
`SessionBagInterface` implementations.
|
||||||
|
* Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
|
||||||
|
implementation is ESI compatible.
|
||||||
|
* Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
|
||||||
|
behavior of messages auto expiring after one page page load. Messages must
|
||||||
|
be retrieved by `get()` or `all()`.
|
||||||
|
* Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
|
||||||
|
attributes storage behavior from 2.0.x (default).
|
||||||
|
* Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
|
||||||
|
namespace session attributes.
|
||||||
|
* Flash API can stores messages in an array so there may be multiple messages
|
||||||
|
per flash type. The old `Session` class API remains without BC break as it
|
||||||
|
will allow single messages as before.
|
||||||
|
* Added basic session meta-data to the session to record session create time,
|
||||||
|
last updated time, and the lifetime of the session cookie that was provided
|
||||||
|
to the client.
|
||||||
|
* Request::getClientIp() method doesn't take a parameter anymore but bases
|
||||||
|
itself on the trustProxy parameter.
|
||||||
|
* Added isMethod() to Request object.
|
||||||
|
* [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
|
||||||
|
a `Request` now all return a raw value (vs a urldecoded value before). Any call
|
||||||
|
to one of these methods must be checked and wrapped in a `rawurldecode()` if
|
||||||
|
needed.
|
|
@ -0,0 +1,302 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a cookie.
|
||||||
|
*
|
||||||
|
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*/
|
||||||
|
class Cookie
|
||||||
|
{
|
||||||
|
const SAMESITE_NONE = 'none';
|
||||||
|
const SAMESITE_LAX = 'lax';
|
||||||
|
const SAMESITE_STRICT = 'strict';
|
||||||
|
|
||||||
|
protected $name;
|
||||||
|
protected $value;
|
||||||
|
protected $domain;
|
||||||
|
protected $expire;
|
||||||
|
protected $path;
|
||||||
|
protected $secure;
|
||||||
|
protected $httpOnly;
|
||||||
|
|
||||||
|
private $raw;
|
||||||
|
private $sameSite;
|
||||||
|
private $secureDefault = false;
|
||||||
|
|
||||||
|
private static $reservedCharsList = "=,; \t\r\n\v\f";
|
||||||
|
private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
|
||||||
|
private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates cookie from raw header string.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function fromString(string $cookie, bool $decode = false)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'expires' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => null,
|
||||||
|
'secure' => false,
|
||||||
|
'httponly' => false,
|
||||||
|
'raw' => !$decode,
|
||||||
|
'samesite' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$parts = HeaderUtils::split($cookie, ';=');
|
||||||
|
$part = array_shift($parts);
|
||||||
|
|
||||||
|
$name = $decode ? urldecode($part[0]) : $part[0];
|
||||||
|
$value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null;
|
||||||
|
|
||||||
|
$data = HeaderUtils::combine($parts) + $data;
|
||||||
|
|
||||||
|
if (isset($data['max-age'])) {
|
||||||
|
$data['expires'] = time() + (int) $data['max-age'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self
|
||||||
|
{
|
||||||
|
return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name The name of the cookie
|
||||||
|
* @param string|null $value The value of the cookie
|
||||||
|
* @param int|string|\DateTimeInterface $expire The time the cookie expires
|
||||||
|
* @param string $path The path on the server in which the cookie will be available on
|
||||||
|
* @param string|null $domain The domain that the cookie is available to
|
||||||
|
* @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
|
||||||
|
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
|
||||||
|
* @param bool $raw Whether the cookie value should be sent with no url encoding
|
||||||
|
* @param string|null $sameSite Whether the cookie will be available for cross-site requests
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
|
||||||
|
{
|
||||||
|
// from PHP source code
|
||||||
|
if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($name)) {
|
||||||
|
throw new \InvalidArgumentException('The cookie name cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert expiration time to a Unix timestamp
|
||||||
|
if ($expire instanceof \DateTimeInterface) {
|
||||||
|
$expire = $expire->format('U');
|
||||||
|
} elseif (!is_numeric($expire)) {
|
||||||
|
$expire = strtotime($expire);
|
||||||
|
|
||||||
|
if (false === $expire) {
|
||||||
|
throw new \InvalidArgumentException('The cookie expiration time is not valid.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->value = $value;
|
||||||
|
$this->domain = $domain;
|
||||||
|
$this->expire = 0 < $expire ? (int) $expire : 0;
|
||||||
|
$this->path = empty($path) ? '/' : $path;
|
||||||
|
$this->secure = $secure;
|
||||||
|
$this->httpOnly = $httpOnly;
|
||||||
|
$this->raw = $raw;
|
||||||
|
|
||||||
|
if ('' === $sameSite) {
|
||||||
|
$sameSite = null;
|
||||||
|
} elseif (null !== $sameSite) {
|
||||||
|
$sameSite = strtolower($sameSite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) {
|
||||||
|
throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sameSite = $sameSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cookie as a string.
|
||||||
|
*
|
||||||
|
* @return string The cookie
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
if ($this->isRaw()) {
|
||||||
|
$str = $this->getName();
|
||||||
|
} else {
|
||||||
|
$str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$str .= '=';
|
||||||
|
|
||||||
|
if ('' === (string) $this->getValue()) {
|
||||||
|
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
|
||||||
|
} else {
|
||||||
|
$str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
|
||||||
|
|
||||||
|
if (0 !== $this->getExpiresTime()) {
|
||||||
|
$str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getPath()) {
|
||||||
|
$str .= '; path='.$this->getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getDomain()) {
|
||||||
|
$str .= '; domain='.$this->getDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true === $this->isSecure()) {
|
||||||
|
$str .= '; secure';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true === $this->isHttpOnly()) {
|
||||||
|
$str .= '; httponly';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->getSameSite()) {
|
||||||
|
$str .= '; samesite='.$this->getSameSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the cookie.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the cookie.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the domain that the cookie is available to.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getDomain()
|
||||||
|
{
|
||||||
|
return $this->domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the time the cookie expires.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getExpiresTime()
|
||||||
|
{
|
||||||
|
return $this->expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the max-age attribute.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getMaxAge()
|
||||||
|
{
|
||||||
|
$maxAge = $this->expire - time();
|
||||||
|
|
||||||
|
return 0 >= $maxAge ? 0 : $maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the path on the server in which the cookie will be available on.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPath()
|
||||||
|
{
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSecure()
|
||||||
|
{
|
||||||
|
return $this->secure ?? $this->secureDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the cookie will be made accessible only through the HTTP protocol.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isHttpOnly()
|
||||||
|
{
|
||||||
|
return $this->httpOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this cookie is about to be cleared.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isCleared()
|
||||||
|
{
|
||||||
|
return 0 !== $this->expire && $this->expire < time();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the cookie value should be sent with no url encoding.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRaw()
|
||||||
|
{
|
||||||
|
return $this->raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SameSite attribute.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getSameSite()
|
||||||
|
{
|
||||||
|
return $this->sameSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $default The default value of the "secure" flag when it is set to null
|
||||||
|
*/
|
||||||
|
public function setSecureDefault(bool $default): void
|
||||||
|
{
|
||||||
|
$this->secureDefault = $default;
|
||||||
|
}
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/ConflictingHeadersException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP request contains headers with conflicting information.
|
||||||
|
*
|
||||||
|
* @author Magnus Nordlander <magnus@fervo.se>
|
||||||
|
*/
|
||||||
|
class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/RequestExceptionInterface.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for Request exceptions.
|
||||||
|
*
|
||||||
|
* Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
|
||||||
|
*/
|
||||||
|
interface RequestExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
20
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php
vendored
Normal file
20
lam/lib/3rdParty/composer/symfony/http-foundation/Exception/SuspiciousOperationException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised when a user has performed an operation that should be considered
|
||||||
|
* suspicious from a security perspective.
|
||||||
|
*/
|
||||||
|
class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
47
lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php
vendored
Normal file
47
lam/lib/3rdParty/composer/symfony/http-foundation/ExpressionRequestMatcher.php
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExpressionRequestMatcher uses an expression to match a Request.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ExpressionRequestMatcher extends RequestMatcher
|
||||||
|
{
|
||||||
|
private $language;
|
||||||
|
private $expression;
|
||||||
|
|
||||||
|
public function setExpression(ExpressionLanguage $language, $expression)
|
||||||
|
{
|
||||||
|
$this->language = $language;
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function matches(Request $request)
|
||||||
|
{
|
||||||
|
if (!$this->language) {
|
||||||
|
throw new \LogicException('Unable to match the request as the expression language is not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->language->evaluate($this->expression, [
|
||||||
|
'request' => $request,
|
||||||
|
'method' => $request->getMethod(),
|
||||||
|
'path' => rawurldecode($request->getPathInfo()),
|
||||||
|
'host' => $request->getHost(),
|
||||||
|
'ip' => $request->getClientIp(),
|
||||||
|
'attributes' => $request->attributes->all(),
|
||||||
|
]) && parent::matches($request);
|
||||||
|
}
|
||||||
|
}
|
25
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php
vendored
Normal file
25
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/AccessDeniedException.php
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the access on a file was denied.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class AccessDeniedException extends FileException
|
||||||
|
{
|
||||||
|
public function __construct(string $path)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The file %s could not be accessed', $path));
|
||||||
|
}
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/CannotWriteFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class CannotWriteFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/ExtensionFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class ExtensionFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an error occurred in the component File.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class FileException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
25
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php
vendored
Normal file
25
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FileNotFoundException.php
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a file was not found.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class FileNotFoundException extends FileException
|
||||||
|
{
|
||||||
|
public function __construct(string $path)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The file "%s" does not exist', $path));
|
||||||
|
}
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/FormSizeFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class FormSizeFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/IniSizeFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class IniSizeFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class NoFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class NoTmpDirFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/PartialFileException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile.
|
||||||
|
*
|
||||||
|
* @author Florent Mata <florentmata@gmail.com>
|
||||||
|
*/
|
||||||
|
class PartialFileException extends FileException
|
||||||
|
{
|
||||||
|
}
|
20
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
vendored
Normal file
20
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
class UnexpectedTypeException extends FileException
|
||||||
|
{
|
||||||
|
public function __construct($value, string $expectedType)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value)));
|
||||||
|
}
|
||||||
|
}
|
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php
vendored
Normal file
21
lam/lib/3rdParty/composer/symfony/http-foundation/File/Exception/UploadException.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an error occurred during file upload.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class UploadException extends FileException
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
|
||||||
|
use Symfony\Component\Mime\MimeTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file in the file system.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class File extends \SplFileInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a new file from the given path.
|
||||||
|
*
|
||||||
|
* @param string $path The path to the file
|
||||||
|
* @param bool $checkPath Whether to check the path or not
|
||||||
|
*
|
||||||
|
* @throws FileNotFoundException If the given path is not a file
|
||||||
|
*/
|
||||||
|
public function __construct(string $path, bool $checkPath = true)
|
||||||
|
{
|
||||||
|
if ($checkPath && !is_file($path)) {
|
||||||
|
throw new FileNotFoundException($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension based on the mime type.
|
||||||
|
*
|
||||||
|
* If the mime type is unknown, returns null.
|
||||||
|
*
|
||||||
|
* This method uses the mime type as guessed by getMimeType()
|
||||||
|
* to guess the file extension.
|
||||||
|
*
|
||||||
|
* @return string|null The guessed extension or null if it cannot be guessed
|
||||||
|
*
|
||||||
|
* @see MimeTypes
|
||||||
|
* @see getMimeType()
|
||||||
|
*/
|
||||||
|
public function guessExtension()
|
||||||
|
{
|
||||||
|
return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mime type of the file.
|
||||||
|
*
|
||||||
|
* The mime type is guessed using a MimeTypeGuesserInterface instance,
|
||||||
|
* which uses finfo_file() then the "file" system binary,
|
||||||
|
* depending on which of those are available.
|
||||||
|
*
|
||||||
|
* @return string|null The guessed mime type (e.g. "application/pdf")
|
||||||
|
*
|
||||||
|
* @see MimeTypes
|
||||||
|
*/
|
||||||
|
public function getMimeType()
|
||||||
|
{
|
||||||
|
return MimeTypes::getDefault()->guessMimeType($this->getPathname());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the file to a new location.
|
||||||
|
*
|
||||||
|
* @return self A File object representing the new file
|
||||||
|
*
|
||||||
|
* @throws FileException if the target file could not be created
|
||||||
|
*/
|
||||||
|
public function move(string $directory, string $name = null)
|
||||||
|
{
|
||||||
|
$target = $this->getTargetFile($directory, $name);
|
||||||
|
|
||||||
|
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
|
||||||
|
$renamed = rename($this->getPathname(), $target);
|
||||||
|
restore_error_handler();
|
||||||
|
if (!$renamed) {
|
||||||
|
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@chmod($target, 0666 & ~umask());
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
protected function getTargetFile(string $directory, string $name = null)
|
||||||
|
{
|
||||||
|
if (!is_dir($directory)) {
|
||||||
|
if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
|
||||||
|
throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
|
||||||
|
}
|
||||||
|
} elseif (!is_writable($directory)) {
|
||||||
|
throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
|
||||||
|
|
||||||
|
return new self($target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns locale independent base name of the given path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getName(string $name)
|
||||||
|
{
|
||||||
|
$originalName = str_replace('\\', '/', $name);
|
||||||
|
$pos = strrpos($originalName, '/');
|
||||||
|
$originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
|
||||||
|
|
||||||
|
return $originalName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PHP stream of unknown size.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class Stream extends File
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSize()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\File;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\NoFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\Exception\PartialFileException;
|
||||||
|
use Symfony\Component\Mime\MimeTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file uploaded through a form.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class UploadedFile extends File
|
||||||
|
{
|
||||||
|
private $test = false;
|
||||||
|
private $originalName;
|
||||||
|
private $mimeType;
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts the information of the uploaded file as provided by the PHP global $_FILES.
|
||||||
|
*
|
||||||
|
* The file object is only created when the uploaded file is valid (i.e. when the
|
||||||
|
* isValid() method returns true). Otherwise the only methods that could be called
|
||||||
|
* on an UploadedFile instance are:
|
||||||
|
*
|
||||||
|
* * getClientOriginalName,
|
||||||
|
* * getClientMimeType,
|
||||||
|
* * isValid,
|
||||||
|
* * getError.
|
||||||
|
*
|
||||||
|
* Calling any other method on an non-valid instance will cause an unpredictable result.
|
||||||
|
*
|
||||||
|
* @param string $path The full temporary path to the file
|
||||||
|
* @param string $originalName The original file name of the uploaded file
|
||||||
|
* @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
|
||||||
|
* @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
|
||||||
|
* @param bool $test Whether the test mode is active
|
||||||
|
* Local files are used in test mode hence the code should not enforce HTTP uploads
|
||||||
|
*
|
||||||
|
* @throws FileException If file_uploads is disabled
|
||||||
|
* @throws FileNotFoundException If the file does not exist
|
||||||
|
*/
|
||||||
|
public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false)
|
||||||
|
{
|
||||||
|
$this->originalName = $this->getName($originalName);
|
||||||
|
$this->mimeType = $mimeType ?: 'application/octet-stream';
|
||||||
|
$this->error = $error ?: UPLOAD_ERR_OK;
|
||||||
|
$this->test = $test;
|
||||||
|
|
||||||
|
parent::__construct($path, UPLOAD_ERR_OK === $this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original file name.
|
||||||
|
*
|
||||||
|
* It is extracted from the request from which the file has been uploaded.
|
||||||
|
* Then it should not be considered as a safe value.
|
||||||
|
*
|
||||||
|
* @return string|null The original name
|
||||||
|
*/
|
||||||
|
public function getClientOriginalName()
|
||||||
|
{
|
||||||
|
return $this->originalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original file extension.
|
||||||
|
*
|
||||||
|
* It is extracted from the original file name that was uploaded.
|
||||||
|
* Then it should not be considered as a safe value.
|
||||||
|
*
|
||||||
|
* @return string The extension
|
||||||
|
*/
|
||||||
|
public function getClientOriginalExtension()
|
||||||
|
{
|
||||||
|
return pathinfo($this->originalName, PATHINFO_EXTENSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file mime type.
|
||||||
|
*
|
||||||
|
* The client mime type is extracted from the request from which the file
|
||||||
|
* was uploaded, so it should not be considered as a safe value.
|
||||||
|
*
|
||||||
|
* For a trusted mime type, use getMimeType() instead (which guesses the mime
|
||||||
|
* type based on the file content).
|
||||||
|
*
|
||||||
|
* @return string|null The mime type
|
||||||
|
*
|
||||||
|
* @see getMimeType()
|
||||||
|
*/
|
||||||
|
public function getClientMimeType()
|
||||||
|
{
|
||||||
|
return $this->mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension based on the client mime type.
|
||||||
|
*
|
||||||
|
* If the mime type is unknown, returns null.
|
||||||
|
*
|
||||||
|
* This method uses the mime type as guessed by getClientMimeType()
|
||||||
|
* to guess the file extension. As such, the extension returned
|
||||||
|
* by this method cannot be trusted.
|
||||||
|
*
|
||||||
|
* For a trusted extension, use guessExtension() instead (which guesses
|
||||||
|
* the extension based on the guessed mime type for the file).
|
||||||
|
*
|
||||||
|
* @return string|null The guessed extension or null if it cannot be guessed
|
||||||
|
*
|
||||||
|
* @see guessExtension()
|
||||||
|
* @see getClientMimeType()
|
||||||
|
*/
|
||||||
|
public function guessClientExtension()
|
||||||
|
{
|
||||||
|
return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the upload error.
|
||||||
|
*
|
||||||
|
* If the upload was successful, the constant UPLOAD_ERR_OK is returned.
|
||||||
|
* Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
|
||||||
|
*
|
||||||
|
* @return int The upload error
|
||||||
|
*/
|
||||||
|
public function getError()
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the file was uploaded successfully.
|
||||||
|
*
|
||||||
|
* @return bool True if the file has been uploaded with HTTP and no error occurred
|
||||||
|
*/
|
||||||
|
public function isValid()
|
||||||
|
{
|
||||||
|
$isOk = UPLOAD_ERR_OK === $this->error;
|
||||||
|
|
||||||
|
return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the file to a new location.
|
||||||
|
*
|
||||||
|
* @return File A File object representing the new file
|
||||||
|
*
|
||||||
|
* @throws FileException if, for any reason, the file could not have been moved
|
||||||
|
*/
|
||||||
|
public function move(string $directory, string $name = null)
|
||||||
|
{
|
||||||
|
if ($this->isValid()) {
|
||||||
|
if ($this->test) {
|
||||||
|
return parent::move($directory, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = $this->getTargetFile($directory, $name);
|
||||||
|
|
||||||
|
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
|
||||||
|
$moved = move_uploaded_file($this->getPathname(), $target);
|
||||||
|
restore_error_handler();
|
||||||
|
if (!$moved) {
|
||||||
|
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@chmod($target, 0666 & ~umask());
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->error) {
|
||||||
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
|
throw new IniSizeFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
|
throw new FormSizeFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_PARTIAL:
|
||||||
|
throw new PartialFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_NO_FILE:
|
||||||
|
throw new NoFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_CANT_WRITE:
|
||||||
|
throw new CannotWriteFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_NO_TMP_DIR:
|
||||||
|
throw new NoTmpDirFileException($this->getErrorMessage());
|
||||||
|
case UPLOAD_ERR_EXTENSION:
|
||||||
|
throw new ExtensionFileException($this->getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileException($this->getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum size of an uploaded file as configured in php.ini.
|
||||||
|
*
|
||||||
|
* @return int The maximum size of an uploaded file in bytes
|
||||||
|
*/
|
||||||
|
public static function getMaxFilesize()
|
||||||
|
{
|
||||||
|
$sizePostMax = self::parseFilesize(ini_get('post_max_size'));
|
||||||
|
$sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize'));
|
||||||
|
|
||||||
|
return min($sizePostMax ?: PHP_INT_MAX, $sizeUploadMax ?: PHP_INT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given size from an ini value in bytes.
|
||||||
|
*/
|
||||||
|
private static function parseFilesize($size): int
|
||||||
|
{
|
||||||
|
if ('' === $size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = strtolower($size);
|
||||||
|
|
||||||
|
$max = ltrim($size, '+');
|
||||||
|
if (0 === strpos($max, '0x')) {
|
||||||
|
$max = \intval($max, 16);
|
||||||
|
} elseif (0 === strpos($max, '0')) {
|
||||||
|
$max = \intval($max, 8);
|
||||||
|
} else {
|
||||||
|
$max = (int) $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (substr($size, -1)) {
|
||||||
|
case 't': $max *= 1024;
|
||||||
|
// no break
|
||||||
|
case 'g': $max *= 1024;
|
||||||
|
// no break
|
||||||
|
case 'm': $max *= 1024;
|
||||||
|
// no break
|
||||||
|
case 'k': $max *= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an informative upload error message.
|
||||||
|
*
|
||||||
|
* @return string The error message regarding the specified error code
|
||||||
|
*/
|
||||||
|
public function getErrorMessage()
|
||||||
|
{
|
||||||
|
static $errors = [
|
||||||
|
UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
|
||||||
|
UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
|
||||||
|
UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
|
||||||
|
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
|
||||||
|
UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
|
||||||
|
UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
|
||||||
|
UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
|
||||||
|
];
|
||||||
|
|
||||||
|
$errorCode = $this->error;
|
||||||
|
$maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
|
||||||
|
$message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
|
||||||
|
|
||||||
|
return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileBag is a container for uploaded files.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||||
|
*/
|
||||||
|
class FileBag extends ParameterBag
|
||||||
|
{
|
||||||
|
private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|UploadedFile[] $parameters An array of HTTP files
|
||||||
|
*/
|
||||||
|
public function __construct(array $parameters = [])
|
||||||
|
{
|
||||||
|
$this->replace($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function replace(array $files = [])
|
||||||
|
{
|
||||||
|
$this->parameters = [];
|
||||||
|
$this->add($files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value)
|
||||||
|
{
|
||||||
|
if (!\is_array($value) && !$value instanceof UploadedFile) {
|
||||||
|
throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::set($key, $this->convertFileInformation($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function add(array $files = [])
|
||||||
|
{
|
||||||
|
foreach ($files as $key => $file) {
|
||||||
|
$this->set($key, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts uploaded files to UploadedFile instances.
|
||||||
|
*
|
||||||
|
* @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
|
||||||
|
*
|
||||||
|
* @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
|
||||||
|
*/
|
||||||
|
protected function convertFileInformation($file)
|
||||||
|
{
|
||||||
|
if ($file instanceof UploadedFile) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($file)) {
|
||||||
|
$file = $this->fixPhpFilesArray($file);
|
||||||
|
$keys = array_keys($file);
|
||||||
|
sort($keys);
|
||||||
|
|
||||||
|
if ($keys == self::$fileKeys) {
|
||||||
|
if (UPLOAD_ERR_NO_FILE == $file['error']) {
|
||||||
|
$file = null;
|
||||||
|
} else {
|
||||||
|
$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$file = array_map([$this, 'convertFileInformation'], $file);
|
||||||
|
if (array_keys($keys) === $keys) {
|
||||||
|
$file = array_filter($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes a malformed PHP $_FILES array.
|
||||||
|
*
|
||||||
|
* PHP has a bug that the format of the $_FILES array differs, depending on
|
||||||
|
* whether the uploaded file fields had normal field names or array-like
|
||||||
|
* field names ("normal" vs. "parent[child]").
|
||||||
|
*
|
||||||
|
* This method fixes the array to look like the "normal" $_FILES array.
|
||||||
|
*
|
||||||
|
* It's safe to pass an already converted array, in which case this method
|
||||||
|
* just returns the original array unmodified.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function fixPhpFilesArray($data)
|
||||||
|
{
|
||||||
|
$keys = array_keys($data);
|
||||||
|
sort($keys);
|
||||||
|
|
||||||
|
if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $data;
|
||||||
|
foreach (self::$fileKeys as $k) {
|
||||||
|
unset($files[$k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data['name'] as $key => $name) {
|
||||||
|
$files[$key] = $this->fixPhpFilesArray([
|
||||||
|
'error' => $data['error'][$key],
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $data['type'][$key],
|
||||||
|
'tmp_name' => $data['tmp_name'][$key],
|
||||||
|
'size' => $data['size'][$key],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,288 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HeaderBag is a container for HTTP headers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class HeaderBag implements \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
protected const LOWER = '-abcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
protected $headers = [];
|
||||||
|
protected $cacheControl = [];
|
||||||
|
|
||||||
|
public function __construct(array $headers = [])
|
||||||
|
{
|
||||||
|
foreach ($headers as $key => $values) {
|
||||||
|
$this->set($key, $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the headers as a string.
|
||||||
|
*
|
||||||
|
* @return string The headers
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
if (!$headers = $this->all()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($headers);
|
||||||
|
$max = max(array_map('strlen', array_keys($headers))) + 1;
|
||||||
|
$content = '';
|
||||||
|
foreach ($headers as $name => $values) {
|
||||||
|
$name = ucwords($name, '-');
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the headers.
|
||||||
|
*
|
||||||
|
* @param string|null $key The name of the headers to return or null to get them all
|
||||||
|
*
|
||||||
|
* @return array An array of headers
|
||||||
|
*/
|
||||||
|
public function all(string $key = null)
|
||||||
|
{
|
||||||
|
if (null !== $key) {
|
||||||
|
return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter keys.
|
||||||
|
*
|
||||||
|
* @return array An array of parameter keys
|
||||||
|
*/
|
||||||
|
public function keys()
|
||||||
|
{
|
||||||
|
return array_keys($this->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current HTTP headers by a new set.
|
||||||
|
*/
|
||||||
|
public function replace(array $headers = [])
|
||||||
|
{
|
||||||
|
$this->headers = [];
|
||||||
|
$this->add($headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds new headers the current HTTP headers set.
|
||||||
|
*/
|
||||||
|
public function add(array $headers)
|
||||||
|
{
|
||||||
|
foreach ($headers as $key => $values) {
|
||||||
|
$this->set($key, $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a header value by name.
|
||||||
|
*
|
||||||
|
* @return string|null The first header value or default value
|
||||||
|
*/
|
||||||
|
public function get(string $key, string $default = null)
|
||||||
|
{
|
||||||
|
$headers = $this->all($key);
|
||||||
|
|
||||||
|
if (!$headers) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $headers[0]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $headers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a header by name.
|
||||||
|
*
|
||||||
|
* @param string|string[] $values The value or an array of values
|
||||||
|
* @param bool $replace Whether to replace the actual value or not (true by default)
|
||||||
|
*/
|
||||||
|
public function set(string $key, $values, bool $replace = true)
|
||||||
|
{
|
||||||
|
$key = strtr($key, self::UPPER, self::LOWER);
|
||||||
|
|
||||||
|
if (\is_array($values)) {
|
||||||
|
$values = array_values($values);
|
||||||
|
|
||||||
|
if (true === $replace || !isset($this->headers[$key])) {
|
||||||
|
$this->headers[$key] = $values;
|
||||||
|
} else {
|
||||||
|
$this->headers[$key] = array_merge($this->headers[$key], $values);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (true === $replace || !isset($this->headers[$key])) {
|
||||||
|
$this->headers[$key] = [$values];
|
||||||
|
} else {
|
||||||
|
$this->headers[$key][] = $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('cache-control' === $key) {
|
||||||
|
$this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the HTTP header is defined.
|
||||||
|
*
|
||||||
|
* @return bool true if the parameter exists, false otherwise
|
||||||
|
*/
|
||||||
|
public function has(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given HTTP header contains the given value.
|
||||||
|
*
|
||||||
|
* @return bool true if the value is contained in the header, false otherwise
|
||||||
|
*/
|
||||||
|
public function contains(string $key, string $value)
|
||||||
|
{
|
||||||
|
return \in_array($value, $this->all($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a header.
|
||||||
|
*/
|
||||||
|
public function remove(string $key)
|
||||||
|
{
|
||||||
|
$key = strtr($key, self::UPPER, self::LOWER);
|
||||||
|
|
||||||
|
unset($this->headers[$key]);
|
||||||
|
|
||||||
|
if ('cache-control' === $key) {
|
||||||
|
$this->cacheControl = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTTP header value converted to a date.
|
||||||
|
*
|
||||||
|
* @return \DateTimeInterface|null The parsed DateTime or the default value if the header does not exist
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException When the HTTP header is not parseable
|
||||||
|
*/
|
||||||
|
public function getDate(string $key, \DateTime $default = null)
|
||||||
|
{
|
||||||
|
if (null === $value = $this->get($key)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
|
||||||
|
throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom Cache-Control directive.
|
||||||
|
*
|
||||||
|
* @param mixed $value The Cache-Control directive value
|
||||||
|
*/
|
||||||
|
public function addCacheControlDirective(string $key, $value = true)
|
||||||
|
{
|
||||||
|
$this->cacheControl[$key] = $value;
|
||||||
|
|
||||||
|
$this->set('Cache-Control', $this->getCacheControlHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the Cache-Control directive is defined.
|
||||||
|
*
|
||||||
|
* @return bool true if the directive exists, false otherwise
|
||||||
|
*/
|
||||||
|
public function hasCacheControlDirective(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->cacheControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Cache-Control directive value by name.
|
||||||
|
*
|
||||||
|
* @return mixed|null The directive value if defined, null otherwise
|
||||||
|
*/
|
||||||
|
public function getCacheControlDirective(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a Cache-Control directive.
|
||||||
|
*/
|
||||||
|
public function removeCacheControlDirective(string $key)
|
||||||
|
{
|
||||||
|
unset($this->cacheControl[$key]);
|
||||||
|
|
||||||
|
$this->set('Cache-Control', $this->getCacheControlHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator for headers.
|
||||||
|
*
|
||||||
|
* @return \ArrayIterator An \ArrayIterator instance
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of headers.
|
||||||
|
*
|
||||||
|
* @return int The number of headers
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return \count($this->headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCacheControlHeader()
|
||||||
|
{
|
||||||
|
ksort($this->cacheControl);
|
||||||
|
|
||||||
|
return HeaderUtils::toString($this->cacheControl, ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a Cache-Control HTTP header.
|
||||||
|
*
|
||||||
|
* @return array An array representing the attribute values
|
||||||
|
*/
|
||||||
|
protected function parseCacheControl(string $header)
|
||||||
|
{
|
||||||
|
$parts = HeaderUtils::split($header, ',=');
|
||||||
|
|
||||||
|
return HeaderUtils::combine($parts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP header utility functions.
|
||||||
|
*
|
||||||
|
* @author Christian Schmidt <github@chsc.dk>
|
||||||
|
*/
|
||||||
|
class HeaderUtils
|
||||||
|
{
|
||||||
|
public const DISPOSITION_ATTACHMENT = 'attachment';
|
||||||
|
public const DISPOSITION_INLINE = 'inline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class should not be instantiated.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits an HTTP header by one or more separators.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* HeaderUtils::split("da, en-gb;q=0.8", ",;")
|
||||||
|
* // => ['da'], ['en-gb', 'q=0.8']]
|
||||||
|
*
|
||||||
|
* @param string $separators List of characters to split on, ordered by
|
||||||
|
* precedence, e.g. ",", ";=", or ",;="
|
||||||
|
*
|
||||||
|
* @return array Nested array with as many levels as there are characters in
|
||||||
|
* $separators
|
||||||
|
*/
|
||||||
|
public static function split(string $header, string $separators): array
|
||||||
|
{
|
||||||
|
$quotedSeparators = preg_quote($separators, '/');
|
||||||
|
|
||||||
|
preg_match_all('
|
||||||
|
/
|
||||||
|
(?!\s)
|
||||||
|
(?:
|
||||||
|
# quoted-string
|
||||||
|
"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
|
||||||
|
|
|
||||||
|
# token
|
||||||
|
[^"'.$quotedSeparators.']+
|
||||||
|
)+
|
||||||
|
(?<!\s)
|
||||||
|
|
|
||||||
|
# separator
|
||||||
|
\s*
|
||||||
|
(?<separator>['.$quotedSeparators.'])
|
||||||
|
\s*
|
||||||
|
/x', trim($header), $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
return self::groupParts($matches, $separators);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines an array of arrays into one associative array.
|
||||||
|
*
|
||||||
|
* Each of the nested arrays should have one or two elements. The first
|
||||||
|
* value will be used as the keys in the associative array, and the second
|
||||||
|
* will be used as the values, or true if the nested array only contains one
|
||||||
|
* element. Array keys are lowercased.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* HeaderUtils::combine([["foo", "abc"], ["bar"]])
|
||||||
|
* // => ["foo" => "abc", "bar" => true]
|
||||||
|
*/
|
||||||
|
public static function combine(array $parts): array
|
||||||
|
{
|
||||||
|
$assoc = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$name = strtolower($part[0]);
|
||||||
|
$value = $part[1] ?? true;
|
||||||
|
$assoc[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $assoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins an associative array into a string for use in an HTTP header.
|
||||||
|
*
|
||||||
|
* The key and value of each entry are joined with "=", and all entries
|
||||||
|
* are joined with the specified separator and an additional space (for
|
||||||
|
* readability). Values are quoted if necessary.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")
|
||||||
|
* // => 'foo=abc, bar, baz="a b c"'
|
||||||
|
*/
|
||||||
|
public static function toString(array $assoc, string $separator): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach ($assoc as $name => $value) {
|
||||||
|
if (true === $value) {
|
||||||
|
$parts[] = $name;
|
||||||
|
} else {
|
||||||
|
$parts[] = $name.'='.self::quote($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode($separator.' ', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a string as a quoted string, if necessary.
|
||||||
|
*
|
||||||
|
* If a string contains characters not allowed by the "token" construct in
|
||||||
|
* the HTTP specification, it is backslash-escaped and enclosed in quotes
|
||||||
|
* to match the "quoted-string" construct.
|
||||||
|
*/
|
||||||
|
public static function quote(string $s): string
|
||||||
|
{
|
||||||
|
if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '"'.addcslashes($s, '"\\"').'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a quoted string.
|
||||||
|
*
|
||||||
|
* If passed an unquoted string that matches the "token" construct (as
|
||||||
|
* defined in the HTTP specification), it is passed through verbatimly.
|
||||||
|
*/
|
||||||
|
public static function unquote(string $s): string
|
||||||
|
{
|
||||||
|
return preg_replace('/\\\\(.)|"/', '$1', $s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a HTTP Content-Disposition field-value.
|
||||||
|
*
|
||||||
|
* @param string $disposition One of "inline" or "attachment"
|
||||||
|
* @param string $filename A unicode string
|
||||||
|
* @param string $filenameFallback A string containing only ASCII characters that
|
||||||
|
* is semantically equivalent to $filename. If the filename is already ASCII,
|
||||||
|
* it can be omitted, or just copied from $filename
|
||||||
|
*
|
||||||
|
* @return string A string suitable for use as a Content-Disposition field-value
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @see RFC 6266
|
||||||
|
*/
|
||||||
|
public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
|
||||||
|
{
|
||||||
|
if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $filenameFallback) {
|
||||||
|
$filenameFallback = $filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// filenameFallback is not ASCII.
|
||||||
|
if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
|
||||||
|
throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// percent characters aren't safe in fallback.
|
||||||
|
if (false !== strpos($filenameFallback, '%')) {
|
||||||
|
throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// path separators aren't allowed in either.
|
||||||
|
if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
|
||||||
|
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = ['filename' => $filenameFallback];
|
||||||
|
if ($filename !== $filenameFallback) {
|
||||||
|
$params['filename*'] = "utf-8''".rawurlencode($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $disposition.'; '.self::toString($params, ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function groupParts(array $matches, string $separators): array
|
||||||
|
{
|
||||||
|
$separator = $separators[0];
|
||||||
|
$partSeparators = substr($separators, 1);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$partMatches = [];
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
if (isset($match['separator']) && $match['separator'] === $separator) {
|
||||||
|
++$i;
|
||||||
|
} else {
|
||||||
|
$partMatches[$i][] = $match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = [];
|
||||||
|
if ($partSeparators) {
|
||||||
|
foreach ($partMatches as $matches) {
|
||||||
|
$parts[] = self::groupParts($matches, $partSeparators);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($partMatches as $matches) {
|
||||||
|
$parts[] = self::unquote($matches[0][0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parts;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http utility functions.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class IpUtils
|
||||||
|
{
|
||||||
|
private static $checkedIps = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class should not be instantiated.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
|
||||||
|
*
|
||||||
|
* @param string|array $ips List of IPs or subnets (can be a string if only a single one)
|
||||||
|
*
|
||||||
|
* @return bool Whether the IP is valid
|
||||||
|
*/
|
||||||
|
public static function checkIp(?string $requestIp, $ips)
|
||||||
|
{
|
||||||
|
if (!\is_array($ips)) {
|
||||||
|
$ips = [$ips];
|
||||||
|
}
|
||||||
|
|
||||||
|
$method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
|
||||||
|
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
if (self::$method($requestIp, $ip)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two IPv4 addresses.
|
||||||
|
* In case a subnet is given, it checks if it contains the request IP.
|
||||||
|
*
|
||||||
|
* @param string $ip IPv4 address or subnet in CIDR notation
|
||||||
|
*
|
||||||
|
* @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
|
||||||
|
*/
|
||||||
|
public static function checkIp4(?string $requestIp, string $ip)
|
||||||
|
{
|
||||||
|
$cacheKey = $requestIp.'-'.$ip;
|
||||||
|
if (isset(self::$checkedIps[$cacheKey])) {
|
||||||
|
return self::$checkedIps[$cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== strpos($ip, '/')) {
|
||||||
|
list($address, $netmask) = explode('/', $ip, 2);
|
||||||
|
|
||||||
|
if ('0' === $netmask) {
|
||||||
|
return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($netmask < 0 || $netmask > 32) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$address = $ip;
|
||||||
|
$netmask = 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === ip2long($address)) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two IPv6 addresses.
|
||||||
|
* In case a subnet is given, it checks if it contains the request IP.
|
||||||
|
*
|
||||||
|
* @author David Soria Parra <dsp at php dot net>
|
||||||
|
*
|
||||||
|
* @see https://github.com/dsp/v6tools
|
||||||
|
*
|
||||||
|
* @param string $ip IPv6 address or subnet in CIDR notation
|
||||||
|
*
|
||||||
|
* @return bool Whether the IP is valid
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException When IPV6 support is not enabled
|
||||||
|
*/
|
||||||
|
public static function checkIp6(?string $requestIp, string $ip)
|
||||||
|
{
|
||||||
|
$cacheKey = $requestIp.'-'.$ip;
|
||||||
|
if (isset(self::$checkedIps[$cacheKey])) {
|
||||||
|
return self::$checkedIps[$cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) {
|
||||||
|
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== strpos($ip, '/')) {
|
||||||
|
list($address, $netmask) = explode('/', $ip, 2);
|
||||||
|
|
||||||
|
if ('0' === $netmask) {
|
||||||
|
return (bool) unpack('n*', @inet_pton($address));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($netmask < 1 || $netmask > 128) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$address = $ip;
|
||||||
|
$netmask = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytesAddr = unpack('n*', @inet_pton($address));
|
||||||
|
$bytesTest = unpack('n*', @inet_pton($requestIp));
|
||||||
|
|
||||||
|
if (!$bytesAddr || !$bytesTest) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
|
||||||
|
$left = $netmask - 16 * ($i - 1);
|
||||||
|
$left = ($left <= 16) ? $left : 16;
|
||||||
|
$mask = ~(0xffff >> $left) & 0xffff;
|
||||||
|
if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
|
||||||
|
return self::$checkedIps[$cacheKey] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$checkedIps[$cacheKey] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anonymizes an IP/IPv6.
|
||||||
|
*
|
||||||
|
* Removes the last byte for v4 and the last 8 bytes for v6 IPs
|
||||||
|
*/
|
||||||
|
public static function anonymize(string $ip): string
|
||||||
|
{
|
||||||
|
$wrappedIPv6 = false;
|
||||||
|
if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) {
|
||||||
|
$wrappedIPv6 = true;
|
||||||
|
$ip = substr($ip, 1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$packedAddress = inet_pton($ip);
|
||||||
|
if (4 === \strlen($packedAddress)) {
|
||||||
|
$mask = '255.255.255.0';
|
||||||
|
} elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) {
|
||||||
|
$mask = '::ffff:ffff:ff00';
|
||||||
|
} elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) {
|
||||||
|
$mask = '::ffff:ff00';
|
||||||
|
} else {
|
||||||
|
$mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
|
||||||
|
}
|
||||||
|
$ip = inet_ntop($packedAddress & inet_pton($mask));
|
||||||
|
|
||||||
|
if ($wrappedIPv6) {
|
||||||
|
$ip = '['.$ip.']';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response represents an HTTP response in JSON format.
|
||||||
|
*
|
||||||
|
* Note that this class does not force the returned JSON content to be an
|
||||||
|
* object. It is however recommended that you do return an object as it
|
||||||
|
* protects yourself against XSSI and JSON-JavaScript Hijacking.
|
||||||
|
*
|
||||||
|
* @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside
|
||||||
|
*
|
||||||
|
* @author Igor Wiedler <igor@wiedler.ch>
|
||||||
|
*/
|
||||||
|
class JsonResponse extends Response
|
||||||
|
{
|
||||||
|
protected $data;
|
||||||
|
protected $callback;
|
||||||
|
|
||||||
|
// Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
|
||||||
|
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
|
||||||
|
const DEFAULT_ENCODING_OPTIONS = 15;
|
||||||
|
|
||||||
|
protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data The response data
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
* @param bool $json If the data is already a JSON string
|
||||||
|
*/
|
||||||
|
public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false)
|
||||||
|
{
|
||||||
|
parent::__construct('', $status, $headers);
|
||||||
|
|
||||||
|
if (null === $data) {
|
||||||
|
$data = new \ArrayObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json ? $this->setJson($data) : $this->setData($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for chainability.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* return JsonResponse::create(['key' => 'value'])
|
||||||
|
* ->setSharedMaxAge(300);
|
||||||
|
*
|
||||||
|
* @param mixed $data The JSON response data
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($data = null, int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
return new static($data, $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for chainability.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* return JsonResponse::fromJsonString('{"key": "value"}')
|
||||||
|
* ->setSharedMaxAge(300);
|
||||||
|
*
|
||||||
|
* @param string|null $data The JSON response string
|
||||||
|
* @param int $status The response status code
|
||||||
|
* @param array $headers An array of response headers
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function fromJsonString(string $data = null, int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
return new static($data, $status, $headers, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the JSONP callback.
|
||||||
|
*
|
||||||
|
* @param string|null $callback The JSONP callback or null to use none
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When the callback name is not valid
|
||||||
|
*/
|
||||||
|
public function setCallback(string $callback = null)
|
||||||
|
{
|
||||||
|
if (null !== $callback) {
|
||||||
|
// partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/
|
||||||
|
// partially taken from https://github.com/willdurand/JsonpCallbackValidator
|
||||||
|
// JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
|
||||||
|
// (c) William Durand <william.durand1@gmail.com>
|
||||||
|
$pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
|
||||||
|
$reserved = [
|
||||||
|
'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
|
||||||
|
'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
|
||||||
|
'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
|
||||||
|
];
|
||||||
|
$parts = explode('.', $callback);
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
|
||||||
|
throw new \InvalidArgumentException('The callback name is not valid.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->callback = $callback;
|
||||||
|
|
||||||
|
return $this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a raw string containing a JSON document to be sent.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setJson(string $json)
|
||||||
|
{
|
||||||
|
$this->data = $json;
|
||||||
|
|
||||||
|
return $this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data to be sent as JSON.
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setData($data = [])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = json_encode($data, $this->encodingOptions);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
|
||||||
|
throw $e->getPrevious() ?: $e;
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 70300 && (JSON_THROW_ON_ERROR & $this->encodingOptions)) {
|
||||||
|
return $this->setJson($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||||
|
throw new \InvalidArgumentException(json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->setJson($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns options used while encoding data to JSON.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getEncodingOptions()
|
||||||
|
{
|
||||||
|
return $this->encodingOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets options used while encoding data to JSON.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setEncodingOptions(int $encodingOptions)
|
||||||
|
{
|
||||||
|
$this->encodingOptions = $encodingOptions;
|
||||||
|
|
||||||
|
return $this->setData(json_decode($this->data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the content and headers according to the JSON data and callback.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
protected function update()
|
||||||
|
{
|
||||||
|
if (null !== $this->callback) {
|
||||||
|
// Not using application/javascript for compatibility reasons with older browsers.
|
||||||
|
$this->headers->set('Content-Type', 'text/javascript');
|
||||||
|
|
||||||
|
return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
|
||||||
|
// in order to not overwrite a custom definition.
|
||||||
|
if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
|
||||||
|
$this->headers->set('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->setContent($this->data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2004-2019 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:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
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.
|
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ParameterBag is a container for key/value pairs.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ParameterBag implements \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parameter storage.
|
||||||
|
*/
|
||||||
|
protected $parameters;
|
||||||
|
|
||||||
|
public function __construct(array $parameters = [])
|
||||||
|
{
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameters.
|
||||||
|
*
|
||||||
|
* @return array An array of parameters
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return $this->parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter keys.
|
||||||
|
*
|
||||||
|
* @return array An array of parameter keys
|
||||||
|
*/
|
||||||
|
public function keys()
|
||||||
|
{
|
||||||
|
return array_keys($this->parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current parameters by a new set.
|
||||||
|
*/
|
||||||
|
public function replace(array $parameters = [])
|
||||||
|
{
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds parameters.
|
||||||
|
*/
|
||||||
|
public function add(array $parameters = [])
|
||||||
|
{
|
||||||
|
$this->parameters = array_replace($this->parameters, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a parameter by name.
|
||||||
|
*
|
||||||
|
* @param mixed $default The default value if the parameter key does not exist
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key, $default = null)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a parameter by name.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value)
|
||||||
|
{
|
||||||
|
$this->parameters[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the parameter is defined.
|
||||||
|
*
|
||||||
|
* @return bool true if the parameter exists, false otherwise
|
||||||
|
*/
|
||||||
|
public function has(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a parameter.
|
||||||
|
*/
|
||||||
|
public function remove(string $key)
|
||||||
|
{
|
||||||
|
unset($this->parameters[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the alphabetic characters of the parameter value.
|
||||||
|
*
|
||||||
|
* @return string The filtered value
|
||||||
|
*/
|
||||||
|
public function getAlpha(string $key, string $default = '')
|
||||||
|
{
|
||||||
|
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the alphabetic characters and digits of the parameter value.
|
||||||
|
*
|
||||||
|
* @return string The filtered value
|
||||||
|
*/
|
||||||
|
public function getAlnum(string $key, string $default = '')
|
||||||
|
{
|
||||||
|
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the digits of the parameter value.
|
||||||
|
*
|
||||||
|
* @return string The filtered value
|
||||||
|
*/
|
||||||
|
public function getDigits(string $key, string $default = '')
|
||||||
|
{
|
||||||
|
// we need to remove - and + because they're allowed in the filter
|
||||||
|
return str_replace(['-', '+'], '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter value converted to integer.
|
||||||
|
*
|
||||||
|
* @return int The filtered value
|
||||||
|
*/
|
||||||
|
public function getInt(string $key, int $default = 0)
|
||||||
|
{
|
||||||
|
return (int) $this->get($key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter value converted to boolean.
|
||||||
|
*
|
||||||
|
* @return bool The filtered value
|
||||||
|
*/
|
||||||
|
public function getBoolean(string $key, bool $default = false)
|
||||||
|
{
|
||||||
|
return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter key.
|
||||||
|
*
|
||||||
|
* @param mixed $default Default = null
|
||||||
|
* @param int $filter FILTER_* constant
|
||||||
|
* @param mixed $options Filter options
|
||||||
|
*
|
||||||
|
* @see https://php.net/filter-var
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function filter(string $key, $default = null, int $filter = FILTER_DEFAULT, $options = [])
|
||||||
|
{
|
||||||
|
$value = $this->get($key, $default);
|
||||||
|
|
||||||
|
// Always turn $options into an array - this allows filter_var option shortcuts.
|
||||||
|
if (!\is_array($options) && $options) {
|
||||||
|
$options = ['flags' => $options];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a convenience check for arrays.
|
||||||
|
if (\is_array($value) && !isset($options['flags'])) {
|
||||||
|
$options['flags'] = FILTER_REQUIRE_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter_var($value, $filter, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator for parameters.
|
||||||
|
*
|
||||||
|
* @return \ArrayIterator An \ArrayIterator instance
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of parameters.
|
||||||
|
*
|
||||||
|
* @return int The number of parameters
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return \count($this->parameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
HttpFoundation Component
|
||||||
|
========================
|
||||||
|
|
||||||
|
The HttpFoundation component defines an object-oriented layer for the HTTP
|
||||||
|
specification.
|
||||||
|
|
||||||
|
Resources
|
||||||
|
---------
|
||||||
|
|
||||||
|
* [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
|
||||||
|
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||||
|
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||||
|
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||||
|
in the [main Symfony repository](https://github.com/symfony/symfony)
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RedirectResponse represents an HTTP response doing a redirect.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class RedirectResponse extends Response
|
||||||
|
{
|
||||||
|
protected $targetUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a redirect response so that it conforms to the rules defined for a redirect status code.
|
||||||
|
*
|
||||||
|
* @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
|
||||||
|
* but practically every browser redirects on paths only as well
|
||||||
|
* @param int $status The status code (302 by default)
|
||||||
|
* @param array $headers The headers (Location is always set to the given URL)
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/rfc2616#section-10.3
|
||||||
|
*/
|
||||||
|
public function __construct(string $url, int $status = 302, array $headers = [])
|
||||||
|
{
|
||||||
|
parent::__construct('', $status, $headers);
|
||||||
|
|
||||||
|
$this->setTargetUrl($url);
|
||||||
|
|
||||||
|
if (!$this->isRedirect()) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
|
||||||
|
$this->headers->remove('cache-control');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for chainability.
|
||||||
|
*
|
||||||
|
* @param string $url The URL to redirect to
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($url = '', int $status = 302, array $headers = [])
|
||||||
|
{
|
||||||
|
return new static($url, $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target URL.
|
||||||
|
*
|
||||||
|
* @return string target URL
|
||||||
|
*/
|
||||||
|
public function getTargetUrl()
|
||||||
|
{
|
||||||
|
return $this->targetUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the redirect target of this response.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setTargetUrl(string $url)
|
||||||
|
{
|
||||||
|
if ('' === $url) {
|
||||||
|
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->targetUrl = $url;
|
||||||
|
|
||||||
|
$this->setContent(
|
||||||
|
sprintf('<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
|
||||||
|
|
||||||
|
<title>Redirecting to %1$s</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Redirecting to <a href="%1$s">%1$s</a>.
|
||||||
|
</body>
|
||||||
|
</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
|
||||||
|
|
||||||
|
$this->headers->set('Location', $url);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RequestMatcher compares a pre-defined set of checks against a Request instance.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class RequestMatcher implements RequestMatcherInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
private $port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $methods = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $ips = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $schemes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|string[]|null $methods
|
||||||
|
* @param string|string[]|null $ips
|
||||||
|
* @param string|string[]|null $schemes
|
||||||
|
*/
|
||||||
|
public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null)
|
||||||
|
{
|
||||||
|
$this->matchPath($path);
|
||||||
|
$this->matchHost($host);
|
||||||
|
$this->matchMethod($methods);
|
||||||
|
$this->matchIps($ips);
|
||||||
|
$this->matchScheme($schemes);
|
||||||
|
$this->matchPort($port);
|
||||||
|
|
||||||
|
foreach ($attributes as $k => $v) {
|
||||||
|
$this->matchAttribute($k, $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the HTTP scheme.
|
||||||
|
*
|
||||||
|
* @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
|
||||||
|
*/
|
||||||
|
public function matchScheme($scheme)
|
||||||
|
{
|
||||||
|
$this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the URL host name.
|
||||||
|
*/
|
||||||
|
public function matchHost(?string $regexp)
|
||||||
|
{
|
||||||
|
$this->host = $regexp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the the URL port.
|
||||||
|
*
|
||||||
|
* @param int|null $port The port number to connect to
|
||||||
|
*/
|
||||||
|
public function matchPort(?int $port)
|
||||||
|
{
|
||||||
|
$this->port = $port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the URL path info.
|
||||||
|
*/
|
||||||
|
public function matchPath(?string $regexp)
|
||||||
|
{
|
||||||
|
$this->path = $regexp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the client IP.
|
||||||
|
*
|
||||||
|
* @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
|
||||||
|
*/
|
||||||
|
public function matchIp(string $ip)
|
||||||
|
{
|
||||||
|
$this->matchIps($ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the client IP.
|
||||||
|
*
|
||||||
|
* @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
|
||||||
|
*/
|
||||||
|
public function matchIps($ips)
|
||||||
|
{
|
||||||
|
$this->ips = null !== $ips ? (array) $ips : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for the HTTP method.
|
||||||
|
*
|
||||||
|
* @param string|string[]|null $method An HTTP method or an array of HTTP methods
|
||||||
|
*/
|
||||||
|
public function matchMethod($method)
|
||||||
|
{
|
||||||
|
$this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a check for request attribute.
|
||||||
|
*/
|
||||||
|
public function matchAttribute(string $key, string $regexp)
|
||||||
|
{
|
||||||
|
$this->attributes[$key] = $regexp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function matches(Request $request)
|
||||||
|
{
|
||||||
|
if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->attributes as $key => $pattern) {
|
||||||
|
if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note to future implementors: add additional checks above the
|
||||||
|
// foreach above or else your check might not be run!
|
||||||
|
return 0 === \count($this->ips);
|
||||||
|
}
|
||||||
|
}
|
27
lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php
vendored
Normal file
27
lam/lib/3rdParty/composer/symfony/http-foundation/RequestMatcherInterface.php
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RequestMatcherInterface is an interface for strategies to match a Request.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface RequestMatcherInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decides whether the rule(s) implemented by the strategy matches the supplied request.
|
||||||
|
*
|
||||||
|
* @return bool true if the request matches, false otherwise
|
||||||
|
*/
|
||||||
|
public function matches(Request $request);
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request stack that controls the lifecycle of requests.
|
||||||
|
*
|
||||||
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
*/
|
||||||
|
class RequestStack
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Request[]
|
||||||
|
*/
|
||||||
|
private $requests = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a Request on the stack.
|
||||||
|
*
|
||||||
|
* This method should generally not be called directly as the stack
|
||||||
|
* management should be taken care of by the application itself.
|
||||||
|
*/
|
||||||
|
public function push(Request $request)
|
||||||
|
{
|
||||||
|
$this->requests[] = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pops the current request from the stack.
|
||||||
|
*
|
||||||
|
* This operation lets the current request go out of scope.
|
||||||
|
*
|
||||||
|
* This method should generally not be called directly as the stack
|
||||||
|
* management should be taken care of by the application itself.
|
||||||
|
*
|
||||||
|
* @return Request|null
|
||||||
|
*/
|
||||||
|
public function pop()
|
||||||
|
{
|
||||||
|
if (!$this->requests) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_pop($this->requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Request|null
|
||||||
|
*/
|
||||||
|
public function getCurrentRequest()
|
||||||
|
{
|
||||||
|
return end($this->requests) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the master Request.
|
||||||
|
*
|
||||||
|
* Be warned that making your code aware of the master request
|
||||||
|
* might make it un-compatible with other features of your framework
|
||||||
|
* like ESI support.
|
||||||
|
*
|
||||||
|
* @return Request|null
|
||||||
|
*/
|
||||||
|
public function getMasterRequest()
|
||||||
|
{
|
||||||
|
if (!$this->requests) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->requests[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parent request of the current.
|
||||||
|
*
|
||||||
|
* Be warned that making your code aware of the parent request
|
||||||
|
* might make it un-compatible with other features of your framework
|
||||||
|
* like ESI support.
|
||||||
|
*
|
||||||
|
* If current Request is the master request, it returns null.
|
||||||
|
*
|
||||||
|
* @return Request|null
|
||||||
|
*/
|
||||||
|
public function getParentRequest()
|
||||||
|
{
|
||||||
|
$pos = \count($this->requests) - 2;
|
||||||
|
|
||||||
|
if (!isset($this->requests[$pos])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->requests[$pos];
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,293 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResponseHeaderBag is a container for Response HTTP headers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ResponseHeaderBag extends HeaderBag
|
||||||
|
{
|
||||||
|
const COOKIES_FLAT = 'flat';
|
||||||
|
const COOKIES_ARRAY = 'array';
|
||||||
|
|
||||||
|
const DISPOSITION_ATTACHMENT = 'attachment';
|
||||||
|
const DISPOSITION_INLINE = 'inline';
|
||||||
|
|
||||||
|
protected $computedCacheControl = [];
|
||||||
|
protected $cookies = [];
|
||||||
|
protected $headerNames = [];
|
||||||
|
|
||||||
|
public function __construct(array $headers = [])
|
||||||
|
{
|
||||||
|
parent::__construct($headers);
|
||||||
|
|
||||||
|
if (!isset($this->headers['cache-control'])) {
|
||||||
|
$this->set('Cache-Control', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC2616 - 14.18 says all Responses need to have a Date */
|
||||||
|
if (!isset($this->headers['date'])) {
|
||||||
|
$this->initDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the headers, with original capitalizations.
|
||||||
|
*
|
||||||
|
* @return array An array of headers
|
||||||
|
*/
|
||||||
|
public function allPreserveCase()
|
||||||
|
{
|
||||||
|
$headers = [];
|
||||||
|
foreach ($this->all() as $name => $value) {
|
||||||
|
$headers[$this->headerNames[$name] ?? $name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allPreserveCaseWithoutCookies()
|
||||||
|
{
|
||||||
|
$headers = $this->allPreserveCase();
|
||||||
|
if (isset($this->headerNames['set-cookie'])) {
|
||||||
|
unset($headers[$this->headerNames['set-cookie']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function replace(array $headers = [])
|
||||||
|
{
|
||||||
|
$this->headerNames = [];
|
||||||
|
|
||||||
|
parent::replace($headers);
|
||||||
|
|
||||||
|
if (!isset($this->headers['cache-control'])) {
|
||||||
|
$this->set('Cache-Control', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->headers['date'])) {
|
||||||
|
$this->initDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function all(string $key = null)
|
||||||
|
{
|
||||||
|
$headers = parent::all();
|
||||||
|
|
||||||
|
if (null !== $key) {
|
||||||
|
$key = strtr($key, self::UPPER, self::LOWER);
|
||||||
|
|
||||||
|
return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->getCookies() as $cookie) {
|
||||||
|
$headers['set-cookie'][] = (string) $cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $key, $values, bool $replace = true)
|
||||||
|
{
|
||||||
|
$uniqueKey = strtr($key, self::UPPER, self::LOWER);
|
||||||
|
|
||||||
|
if ('set-cookie' === $uniqueKey) {
|
||||||
|
if ($replace) {
|
||||||
|
$this->cookies = [];
|
||||||
|
}
|
||||||
|
foreach ((array) $values as $cookie) {
|
||||||
|
$this->setCookie(Cookie::fromString($cookie));
|
||||||
|
}
|
||||||
|
$this->headerNames[$uniqueKey] = $key;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->headerNames[$uniqueKey] = $key;
|
||||||
|
|
||||||
|
parent::set($key, $values, $replace);
|
||||||
|
|
||||||
|
// ensure the cache-control header has sensible defaults
|
||||||
|
if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) {
|
||||||
|
$this->headers['cache-control'] = [$computed];
|
||||||
|
$this->headerNames['cache-control'] = 'Cache-Control';
|
||||||
|
$this->computedCacheControl = $this->parseCacheControl($computed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function remove(string $key)
|
||||||
|
{
|
||||||
|
$uniqueKey = strtr($key, self::UPPER, self::LOWER);
|
||||||
|
unset($this->headerNames[$uniqueKey]);
|
||||||
|
|
||||||
|
if ('set-cookie' === $uniqueKey) {
|
||||||
|
$this->cookies = [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::remove($key);
|
||||||
|
|
||||||
|
if ('cache-control' === $uniqueKey) {
|
||||||
|
$this->computedCacheControl = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('date' === $uniqueKey) {
|
||||||
|
$this->initDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function hasCacheControlDirective(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->computedCacheControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCacheControlDirective(string $key)
|
||||||
|
{
|
||||||
|
return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCookie(Cookie $cookie)
|
||||||
|
{
|
||||||
|
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
|
||||||
|
$this->headerNames['set-cookie'] = 'Set-Cookie';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a cookie from the array, but does not unset it in the browser.
|
||||||
|
*/
|
||||||
|
public function removeCookie(string $name, ?string $path = '/', string $domain = null)
|
||||||
|
{
|
||||||
|
if (null === $path) {
|
||||||
|
$path = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->cookies[$domain][$path][$name]);
|
||||||
|
|
||||||
|
if (empty($this->cookies[$domain][$path])) {
|
||||||
|
unset($this->cookies[$domain][$path]);
|
||||||
|
|
||||||
|
if (empty($this->cookies[$domain])) {
|
||||||
|
unset($this->cookies[$domain]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->cookies)) {
|
||||||
|
unset($this->headerNames['set-cookie']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all cookies.
|
||||||
|
*
|
||||||
|
* @return Cookie[]
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When the $format is invalid
|
||||||
|
*/
|
||||||
|
public function getCookies(string $format = self::COOKIES_FLAT)
|
||||||
|
{
|
||||||
|
if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::COOKIES_ARRAY === $format) {
|
||||||
|
return $this->cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
$flattenedCookies = [];
|
||||||
|
foreach ($this->cookies as $path) {
|
||||||
|
foreach ($path as $cookies) {
|
||||||
|
foreach ($cookies as $cookie) {
|
||||||
|
$flattenedCookies[] = $cookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $flattenedCookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears a cookie in the browser.
|
||||||
|
*/
|
||||||
|
public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true)
|
||||||
|
{
|
||||||
|
$this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see HeaderUtils::makeDisposition()
|
||||||
|
*/
|
||||||
|
public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '')
|
||||||
|
{
|
||||||
|
return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the calculated value of the cache-control header.
|
||||||
|
*
|
||||||
|
* This considers several other headers and calculates or modifies the
|
||||||
|
* cache-control header to a sensible, conservative value.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function computeCacheControlValue()
|
||||||
|
{
|
||||||
|
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
|
||||||
|
return 'no-cache, private';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->cacheControl) {
|
||||||
|
// conservative by default
|
||||||
|
return 'private, must-revalidate';
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $this->getCacheControlHeader();
|
||||||
|
if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public if s-maxage is defined, private otherwise
|
||||||
|
if (!isset($this->cacheControl['s-maxage'])) {
|
||||||
|
return $header.', private';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initDate(): void
|
||||||
|
{
|
||||||
|
$now = \DateTime::createFromFormat('U', time());
|
||||||
|
$now->setTimezone(new \DateTimeZone('UTC'));
|
||||||
|
$this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServerBag is a container for HTTP headers from the $_SERVER variable.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||||
|
* @author Robert Kiss <kepten@gmail.com>
|
||||||
|
*/
|
||||||
|
class ServerBag extends ParameterBag
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the HTTP headers.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
$headers = [];
|
||||||
|
foreach ($this->parameters as $key => $value) {
|
||||||
|
if (0 === strpos($key, 'HTTP_')) {
|
||||||
|
$headers[substr($key, 5)] = $value;
|
||||||
|
} elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) {
|
||||||
|
$headers[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->parameters['PHP_AUTH_USER'])) {
|
||||||
|
$headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
|
||||||
|
$headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
|
||||||
|
* For this workaround to work, add these lines to your .htaccess file:
|
||||||
|
* RewriteCond %{HTTP:Authorization} ^(.+)$
|
||||||
|
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
*
|
||||||
|
* A sample .htaccess file:
|
||||||
|
* RewriteEngine On
|
||||||
|
* RewriteCond %{HTTP:Authorization} ^(.+)$
|
||||||
|
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
* RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
* RewriteRule ^(.*)$ app.php [QSA,L]
|
||||||
|
*/
|
||||||
|
|
||||||
|
$authorizationHeader = null;
|
||||||
|
if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
|
||||||
|
$authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
|
||||||
|
} elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||||
|
$authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $authorizationHeader) {
|
||||||
|
if (0 === stripos($authorizationHeader, 'basic ')) {
|
||||||
|
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
|
||||||
|
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
|
||||||
|
if (2 == \count($exploded)) {
|
||||||
|
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
|
||||||
|
}
|
||||||
|
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
|
||||||
|
// In some circumstances PHP_AUTH_DIGEST needs to be set
|
||||||
|
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||||
|
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
|
||||||
|
} elseif (0 === stripos($authorizationHeader, 'bearer ')) {
|
||||||
|
/*
|
||||||
|
* XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
|
||||||
|
* I'll just set $headers['AUTHORIZATION'] here.
|
||||||
|
* https://php.net/reserved.variables.server
|
||||||
|
*/
|
||||||
|
$headers['AUTHORIZATION'] = $authorizationHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($headers['AUTHORIZATION'])) {
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHP_AUTH_USER/PHP_AUTH_PW
|
||||||
|
if (isset($headers['PHP_AUTH_USER'])) {
|
||||||
|
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
|
||||||
|
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
|
||||||
|
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
148
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php
vendored
Normal file
148
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBag.php
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class relates to session attribute storage.
|
||||||
|
*/
|
||||||
|
class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
private $name = 'attributes';
|
||||||
|
private $storageKey;
|
||||||
|
|
||||||
|
protected $attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $storageKey The key used to store attributes in the session
|
||||||
|
*/
|
||||||
|
public function __construct(string $storageKey = '_sf2_attributes')
|
||||||
|
{
|
||||||
|
$this->storageKey = $storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialize(array &$attributes)
|
||||||
|
{
|
||||||
|
$this->attributes = &$attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStorageKey()
|
||||||
|
{
|
||||||
|
return $this->storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has(string $name)
|
||||||
|
{
|
||||||
|
return \array_key_exists($name, $this->attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get(string $name, $default = null)
|
||||||
|
{
|
||||||
|
return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $name, $value)
|
||||||
|
{
|
||||||
|
$this->attributes[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return $this->attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function replace(array $attributes)
|
||||||
|
{
|
||||||
|
$this->attributes = [];
|
||||||
|
foreach ($attributes as $key => $value) {
|
||||||
|
$this->set($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function remove(string $name)
|
||||||
|
{
|
||||||
|
$retval = null;
|
||||||
|
if (\array_key_exists($name, $this->attributes)) {
|
||||||
|
$retval = $this->attributes[$name];
|
||||||
|
unset($this->attributes[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$return = $this->attributes;
|
||||||
|
$this->attributes = [];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator for attributes.
|
||||||
|
*
|
||||||
|
* @return \ArrayIterator An \ArrayIterator instance
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of attributes.
|
||||||
|
*
|
||||||
|
* @return int The number of attributes
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return \count($this->attributes);
|
||||||
|
}
|
||||||
|
}
|
61
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
vendored
Normal file
61
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Attribute;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes store.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
interface AttributeBagInterface extends SessionBagInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if an attribute is defined.
|
||||||
|
*
|
||||||
|
* @return bool true if the attribute is defined, false otherwise
|
||||||
|
*/
|
||||||
|
public function has(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an attribute.
|
||||||
|
*
|
||||||
|
* @param mixed $default The default value if not found
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $name, $default = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an attribute.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function set(string $name, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns attributes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function all();
|
||||||
|
|
||||||
|
public function replace(array $attributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an attribute.
|
||||||
|
*
|
||||||
|
* @return mixed The removed value or null when it does not exist
|
||||||
|
*/
|
||||||
|
public function remove(string $name);
|
||||||
|
}
|
157
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
vendored
Normal file
157
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
vendored
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides structured storage of session attributes using
|
||||||
|
* a name spacing character in the key.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class NamespacedAttributeBag extends AttributeBag
|
||||||
|
{
|
||||||
|
private $namespaceCharacter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $storageKey Session storage key
|
||||||
|
* @param string $namespaceCharacter Namespace character to use in keys
|
||||||
|
*/
|
||||||
|
public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/')
|
||||||
|
{
|
||||||
|
$this->namespaceCharacter = $namespaceCharacter;
|
||||||
|
parent::__construct($storageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has(string $name)
|
||||||
|
{
|
||||||
|
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
|
||||||
|
$attributes = $this->resolveAttributePath($name);
|
||||||
|
$name = $this->resolveKey($name);
|
||||||
|
|
||||||
|
if (null === $attributes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \array_key_exists($name, $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get(string $name, $default = null)
|
||||||
|
{
|
||||||
|
// reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
|
||||||
|
$attributes = $this->resolveAttributePath($name);
|
||||||
|
$name = $this->resolveKey($name);
|
||||||
|
|
||||||
|
if (null === $attributes) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \array_key_exists($name, $attributes) ? $attributes[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $name, $value)
|
||||||
|
{
|
||||||
|
$attributes = &$this->resolveAttributePath($name, true);
|
||||||
|
$name = $this->resolveKey($name);
|
||||||
|
$attributes[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function remove(string $name)
|
||||||
|
{
|
||||||
|
$retval = null;
|
||||||
|
$attributes = &$this->resolveAttributePath($name);
|
||||||
|
$name = $this->resolveKey($name);
|
||||||
|
if (null !== $attributes && \array_key_exists($name, $attributes)) {
|
||||||
|
$retval = $attributes[$name];
|
||||||
|
unset($attributes[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a path in attributes property and returns it as a reference.
|
||||||
|
*
|
||||||
|
* This method allows structured namespacing of session attributes.
|
||||||
|
*
|
||||||
|
* @param string $name Key name
|
||||||
|
* @param bool $writeContext Write context, default false
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function &resolveAttributePath(string $name, bool $writeContext = false)
|
||||||
|
{
|
||||||
|
$array = &$this->attributes;
|
||||||
|
$name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
|
||||||
|
|
||||||
|
// Check if there is anything to do, else return
|
||||||
|
if (!$name) {
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode($this->namespaceCharacter, $name);
|
||||||
|
if (\count($parts) < 2) {
|
||||||
|
if (!$writeContext) {
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
$array[$parts[0]] = [];
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($parts[\count($parts) - 1]);
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (null !== $array && !\array_key_exists($part, $array)) {
|
||||||
|
if (!$writeContext) {
|
||||||
|
$null = null;
|
||||||
|
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$array[$part] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$array = &$array[$part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the key from the name.
|
||||||
|
*
|
||||||
|
* This is the last part in a dot separated string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function resolveKey(string $name)
|
||||||
|
{
|
||||||
|
if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
|
||||||
|
$name = substr($name, $pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
}
|
161
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
vendored
Normal file
161
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Flash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutoExpireFlashBag flash message container.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class AutoExpireFlashBag implements FlashBagInterface
|
||||||
|
{
|
||||||
|
private $name = 'flashes';
|
||||||
|
private $flashes = ['display' => [], 'new' => []];
|
||||||
|
private $storageKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $storageKey The key used to store flashes in the session
|
||||||
|
*/
|
||||||
|
public function __construct(string $storageKey = '_symfony_flashes')
|
||||||
|
{
|
||||||
|
$this->storageKey = $storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialize(array &$flashes)
|
||||||
|
{
|
||||||
|
$this->flashes = &$flashes;
|
||||||
|
|
||||||
|
// The logic: messages from the last request will be stored in new, so we move them to previous
|
||||||
|
// This request we will show what is in 'display'. What is placed into 'new' this time round will
|
||||||
|
// be moved to display next time round.
|
||||||
|
$this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : [];
|
||||||
|
$this->flashes['new'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function add(string $type, $message)
|
||||||
|
{
|
||||||
|
$this->flashes['new'][$type][] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function peek(string $type, array $default = [])
|
||||||
|
{
|
||||||
|
return $this->has($type) ? $this->flashes['display'][$type] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function peekAll()
|
||||||
|
{
|
||||||
|
return \array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get(string $type, array $default = [])
|
||||||
|
{
|
||||||
|
$return = $default;
|
||||||
|
|
||||||
|
if (!$this->has($type)) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->flashes['display'][$type])) {
|
||||||
|
$return = $this->flashes['display'][$type];
|
||||||
|
unset($this->flashes['display'][$type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
$return = $this->flashes['display'];
|
||||||
|
$this->flashes['display'] = [];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setAll(array $messages)
|
||||||
|
{
|
||||||
|
$this->flashes['new'] = $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $type, $messages)
|
||||||
|
{
|
||||||
|
$this->flashes['new'][$type] = (array) $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has(string $type)
|
||||||
|
{
|
||||||
|
return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function keys()
|
||||||
|
{
|
||||||
|
return array_keys($this->flashes['display']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStorageKey()
|
||||||
|
{
|
||||||
|
return $this->storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
return $this->all();
|
||||||
|
}
|
||||||
|
}
|
152
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php
vendored
Normal file
152
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBag.php
vendored
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Flash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlashBag flash message container.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class FlashBag implements FlashBagInterface
|
||||||
|
{
|
||||||
|
private $name = 'flashes';
|
||||||
|
private $flashes = [];
|
||||||
|
private $storageKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $storageKey The key used to store flashes in the session
|
||||||
|
*/
|
||||||
|
public function __construct(string $storageKey = '_symfony_flashes')
|
||||||
|
{
|
||||||
|
$this->storageKey = $storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialize(array &$flashes)
|
||||||
|
{
|
||||||
|
$this->flashes = &$flashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function add(string $type, $message)
|
||||||
|
{
|
||||||
|
$this->flashes[$type][] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function peek(string $type, array $default = [])
|
||||||
|
{
|
||||||
|
return $this->has($type) ? $this->flashes[$type] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function peekAll()
|
||||||
|
{
|
||||||
|
return $this->flashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get(string $type, array $default = [])
|
||||||
|
{
|
||||||
|
if (!$this->has($type)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = $this->flashes[$type];
|
||||||
|
|
||||||
|
unset($this->flashes[$type]);
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
$return = $this->peekAll();
|
||||||
|
$this->flashes = [];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $type, $messages)
|
||||||
|
{
|
||||||
|
$this->flashes[$type] = (array) $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setAll(array $messages)
|
||||||
|
{
|
||||||
|
$this->flashes = $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has(string $type)
|
||||||
|
{
|
||||||
|
return \array_key_exists($type, $this->flashes) && $this->flashes[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function keys()
|
||||||
|
{
|
||||||
|
return array_keys($this->flashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStorageKey()
|
||||||
|
{
|
||||||
|
return $this->storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
return $this->all();
|
||||||
|
}
|
||||||
|
}
|
88
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php
vendored
Normal file
88
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Flash/FlashBagInterface.php
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Flash;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlashBagInterface.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
interface FlashBagInterface extends SessionBagInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Adds a flash message for the given type.
|
||||||
|
*
|
||||||
|
* @param mixed $message
|
||||||
|
*/
|
||||||
|
public function add(string $type, $message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers one or more messages for a given type.
|
||||||
|
*
|
||||||
|
* @param string|array $messages
|
||||||
|
*/
|
||||||
|
public function set(string $type, $messages);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets flash messages for a given type.
|
||||||
|
*
|
||||||
|
* @param string $type Message category type
|
||||||
|
* @param array $default Default value if $type does not exist
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function peek(string $type, array $default = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all flash messages.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function peekAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and clears flash from the stack.
|
||||||
|
*
|
||||||
|
* @param array $default Default value if $type does not exist
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get(string $type, array $default = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and clears flashes from the stack.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function all();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all flash messages.
|
||||||
|
*/
|
||||||
|
public function setAll(array $messages);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has flash messages for a given type?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all defined types.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function keys();
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
protected $storage;
|
||||||
|
|
||||||
|
private $flashName;
|
||||||
|
private $attributeName;
|
||||||
|
private $data = [];
|
||||||
|
private $usageIndex = 0;
|
||||||
|
|
||||||
|
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
|
||||||
|
{
|
||||||
|
$this->storage = $storage ?: new NativeSessionStorage();
|
||||||
|
|
||||||
|
$attributes = $attributes ?: new AttributeBag();
|
||||||
|
$this->attributeName = $attributes->getName();
|
||||||
|
$this->registerBag($attributes);
|
||||||
|
|
||||||
|
$flashes = $flashes ?: new FlashBag();
|
||||||
|
$this->flashName = $flashes->getName();
|
||||||
|
$this->registerBag($flashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
return $this->storage->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has(string $name)
|
||||||
|
{
|
||||||
|
return $this->getAttributeBag()->has($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get(string $name, $default = null)
|
||||||
|
{
|
||||||
|
return $this->getAttributeBag()->get($name, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set(string $name, $value)
|
||||||
|
{
|
||||||
|
$this->getAttributeBag()->set($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return $this->getAttributeBag()->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function replace(array $attributes)
|
||||||
|
{
|
||||||
|
$this->getAttributeBag()->replace($attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function remove(string $name)
|
||||||
|
{
|
||||||
|
return $this->getAttributeBag()->remove($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->getAttributeBag()->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isStarted()
|
||||||
|
{
|
||||||
|
return $this->storage->isStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator for attributes.
|
||||||
|
*
|
||||||
|
* @return \ArrayIterator An \ArrayIterator instance
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->getAttributeBag()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of attributes.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return \count($this->getAttributeBag()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function &getUsageIndex(): int
|
||||||
|
{
|
||||||
|
return $this->usageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
if ($this->isStarted()) {
|
||||||
|
++$this->usageIndex;
|
||||||
|
}
|
||||||
|
foreach ($this->data as &$data) {
|
||||||
|
if (!empty($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function invalidate(int $lifetime = null)
|
||||||
|
{
|
||||||
|
$this->storage->clear();
|
||||||
|
|
||||||
|
return $this->migrate(true, $lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function migrate(bool $destroy = false, int $lifetime = null)
|
||||||
|
{
|
||||||
|
return $this->storage->regenerate($destroy, $lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$this->storage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->storage->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setId(string $id)
|
||||||
|
{
|
||||||
|
if ($this->storage->getId() !== $id) {
|
||||||
|
$this->storage->setId($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->storage->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->storage->setName($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMetadataBag()
|
||||||
|
{
|
||||||
|
++$this->usageIndex;
|
||||||
|
|
||||||
|
return $this->storage->getMetadataBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function registerBag(SessionBagInterface $bag)
|
||||||
|
{
|
||||||
|
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getBag(string $name)
|
||||||
|
{
|
||||||
|
$bag = $this->storage->getBag($name);
|
||||||
|
|
||||||
|
return method_exists($bag, 'getBag') ? $bag->getBag() : $bag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the flashbag interface.
|
||||||
|
*
|
||||||
|
* @return FlashBagInterface
|
||||||
|
*/
|
||||||
|
public function getFlashBag()
|
||||||
|
{
|
||||||
|
return $this->getBag($this->flashName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attributebag interface.
|
||||||
|
*
|
||||||
|
* Note that this method was added to help with IDE autocompletion.
|
||||||
|
*/
|
||||||
|
private function getAttributeBag(): AttributeBagInterface
|
||||||
|
{
|
||||||
|
return $this->getBag($this->attributeName);
|
||||||
|
}
|
||||||
|
}
|
46
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php
vendored
Normal file
46
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagInterface.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session Bag store.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
interface SessionBagInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets this bag's name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Bag.
|
||||||
|
*/
|
||||||
|
public function initialize(array &$array);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the storage key for this bag.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getStorageKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears out data from bag.
|
||||||
|
*
|
||||||
|
* @return mixed Whatever data was contained
|
||||||
|
*/
|
||||||
|
public function clear();
|
||||||
|
}
|
83
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php
vendored
Normal file
83
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionBagProxy.php
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class SessionBagProxy implements SessionBagInterface
|
||||||
|
{
|
||||||
|
private $bag;
|
||||||
|
private $data;
|
||||||
|
private $usageIndex;
|
||||||
|
|
||||||
|
public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex)
|
||||||
|
{
|
||||||
|
$this->bag = $bag;
|
||||||
|
$this->data = &$data;
|
||||||
|
$this->usageIndex = &$usageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBag(): SessionBagInterface
|
||||||
|
{
|
||||||
|
++$this->usageIndex;
|
||||||
|
|
||||||
|
return $this->bag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
if (!isset($this->data[$this->bag->getStorageKey()])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++$this->usageIndex;
|
||||||
|
|
||||||
|
return empty($this->data[$this->bag->getStorageKey()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->bag->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialize(array &$array): void
|
||||||
|
{
|
||||||
|
++$this->usageIndex;
|
||||||
|
$this->data[$this->bag->getStorageKey()] = &$array;
|
||||||
|
|
||||||
|
$this->bag->initialize($array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStorageKey(): string
|
||||||
|
{
|
||||||
|
return $this->bag->getStorageKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
return $this->bag->clear();
|
||||||
|
}
|
||||||
|
}
|
166
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php
vendored
Normal file
166
lam/lib/3rdParty/composer/symfony/http-foundation/Session/SessionInterface.php
vendored
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the session.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
interface SessionInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Starts the session storage.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException if session fails to start
|
||||||
|
*/
|
||||||
|
public function start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the session ID.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session ID.
|
||||||
|
*/
|
||||||
|
public function setId(string $id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the session name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session name.
|
||||||
|
*/
|
||||||
|
public function setName(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the current session.
|
||||||
|
*
|
||||||
|
* Clears all session attributes and flashes and regenerates the
|
||||||
|
* session and deletes the old session from persistence.
|
||||||
|
*
|
||||||
|
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
|
||||||
|
* will leave the system settings unchanged, 0 sets the cookie
|
||||||
|
* to expire with browser session. Time is in seconds, and is
|
||||||
|
* not a Unix timestamp.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function invalidate(int $lifetime = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates the current session to a new session id while maintaining all
|
||||||
|
* session attributes.
|
||||||
|
*
|
||||||
|
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
|
||||||
|
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
|
||||||
|
* will leave the system settings unchanged, 0 sets the cookie
|
||||||
|
* to expire with browser session. Time is in seconds, and is
|
||||||
|
* not a Unix timestamp.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function migrate(bool $destroy = false, int $lifetime = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the session to be saved and closed.
|
||||||
|
*
|
||||||
|
* This method is generally not required for real sessions as
|
||||||
|
* the session will be automatically saved at the end of
|
||||||
|
* code execution.
|
||||||
|
*/
|
||||||
|
public function save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an attribute is defined.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an attribute.
|
||||||
|
*
|
||||||
|
* @param mixed $default The default value if not found
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $name, $default = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an attribute.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function set(string $name, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns attributes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function all();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets attributes.
|
||||||
|
*/
|
||||||
|
public function replace(array $attributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an attribute.
|
||||||
|
*
|
||||||
|
* @return mixed The removed value or null when it does not exist
|
||||||
|
*/
|
||||||
|
public function remove(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all attributes.
|
||||||
|
*/
|
||||||
|
public function clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the session was started.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isStarted();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a SessionBagInterface with the session.
|
||||||
|
*/
|
||||||
|
public function registerBag(SessionBagInterface $bag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a bag instance by name.
|
||||||
|
*
|
||||||
|
* @return SessionBagInterface
|
||||||
|
*/
|
||||||
|
public function getBag(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets session meta.
|
||||||
|
*
|
||||||
|
* @return MetadataBag
|
||||||
|
*/
|
||||||
|
public function getMetadataBag();
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session utility functions.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
* @author Rémon van de Kamp <rpkamp@gmail.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class SessionUtils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Finds the session header amongst the headers that are to be sent, removes it, and returns
|
||||||
|
* it so the caller can process it further.
|
||||||
|
*/
|
||||||
|
public static function popSessionCookie(string $sessionName, string $sessionId): ?string
|
||||||
|
{
|
||||||
|
$sessionCookie = null;
|
||||||
|
$sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName));
|
||||||
|
$sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId));
|
||||||
|
$otherCookies = [];
|
||||||
|
foreach (headers_list() as $h) {
|
||||||
|
if (0 !== stripos($h, 'Set-Cookie:')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (11 === strpos($h, $sessionCookiePrefix, 11)) {
|
||||||
|
$sessionCookie = $h;
|
||||||
|
|
||||||
|
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
|
||||||
|
$otherCookies[] = $h;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$otherCookies[] = $h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null === $sessionCookie) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_remove('Set-Cookie');
|
||||||
|
foreach ($otherCookies as $h) {
|
||||||
|
header($h, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sessionCookie;
|
||||||
|
}
|
||||||
|
}
|
141
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
vendored
Normal file
141
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This abstract session handler provides a generic implementation
|
||||||
|
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
|
||||||
|
* enabling strict and lazy session handling.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
private $sessionName;
|
||||||
|
private $prefetchId;
|
||||||
|
private $prefetchData;
|
||||||
|
private $newSessionId;
|
||||||
|
private $igbinaryEmptyData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function open($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
$this->sessionName = $sessionName;
|
||||||
|
if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
|
||||||
|
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract protected function doRead(string $sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract protected function doWrite(string $sessionId, string $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract protected function doDestroy(string $sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateId($sessionId)
|
||||||
|
{
|
||||||
|
$this->prefetchData = $this->read($sessionId);
|
||||||
|
$this->prefetchId = $sessionId;
|
||||||
|
|
||||||
|
return '' !== $this->prefetchData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($sessionId)
|
||||||
|
{
|
||||||
|
if (null !== $this->prefetchId) {
|
||||||
|
$prefetchId = $this->prefetchId;
|
||||||
|
$prefetchData = $this->prefetchData;
|
||||||
|
$this->prefetchId = $this->prefetchData = null;
|
||||||
|
|
||||||
|
if ($prefetchId === $sessionId || '' === $prefetchData) {
|
||||||
|
$this->newSessionId = '' === $prefetchData ? $sessionId : null;
|
||||||
|
|
||||||
|
return $prefetchData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->doRead($sessionId);
|
||||||
|
$this->newSessionId = '' === $data ? $sessionId : null;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function write($sessionId, $data)
|
||||||
|
{
|
||||||
|
if (null === $this->igbinaryEmptyData) {
|
||||||
|
// see https://github.com/igbinary/igbinary/issues/146
|
||||||
|
$this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : '';
|
||||||
|
}
|
||||||
|
if ('' === $data || $this->igbinaryEmptyData === $data) {
|
||||||
|
return $this->destroy($sessionId);
|
||||||
|
}
|
||||||
|
$this->newSessionId = null;
|
||||||
|
|
||||||
|
return $this->doWrite($sessionId, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function destroy($sessionId)
|
||||||
|
{
|
||||||
|
if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) {
|
||||||
|
if (!$this->sessionName) {
|
||||||
|
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this)));
|
||||||
|
}
|
||||||
|
$cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We send an invalidation Set-Cookie header (zero lifetime)
|
||||||
|
* when either the session was started or a cookie with
|
||||||
|
* the session name was sent by the client (in which case
|
||||||
|
* we know it's invalid as a valid session cookie would've
|
||||||
|
* started the session).
|
||||||
|
*/
|
||||||
|
if (null === $cookie || isset($_COOKIE[$this->sessionName])) {
|
||||||
|
if (\PHP_VERSION_ID < 70300) {
|
||||||
|
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
|
||||||
|
} else {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
unset($params['lifetime']);
|
||||||
|
setcookie($this->sessionName, '', $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memcached based session storage handler based on the Memcached class
|
||||||
|
* provided by the PHP memcached extension.
|
||||||
|
*
|
||||||
|
* @see https://php.net/memcached
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class MemcachedSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
private $memcached;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int Time to live in seconds
|
||||||
|
*/
|
||||||
|
private $ttl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Key prefix for shared environments
|
||||||
|
*/
|
||||||
|
private $prefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* List of available options:
|
||||||
|
* * prefix: The prefix to use for the memcached keys in order to avoid collision
|
||||||
|
* * expiretime: The time to live in seconds.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When unsupported options are passed
|
||||||
|
*/
|
||||||
|
public function __construct(\Memcached $memcached, array $options = [])
|
||||||
|
{
|
||||||
|
$this->memcached = $memcached;
|
||||||
|
|
||||||
|
if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
|
||||||
|
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
return $this->memcached->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId)
|
||||||
|
{
|
||||||
|
return $this->memcached->get($this->prefix.$sessionId) ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
$this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data)
|
||||||
|
{
|
||||||
|
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId)
|
||||||
|
{
|
||||||
|
$result = $this->memcached->delete($this->prefix.$sessionId);
|
||||||
|
|
||||||
|
return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
// not required here because memcached will auto expire the records anyhow.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Memcached instance.
|
||||||
|
*
|
||||||
|
* @return \Memcached
|
||||||
|
*/
|
||||||
|
protected function getMemcached()
|
||||||
|
{
|
||||||
|
return $this->memcached;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrating session handler for migrating from one handler to another. It reads
|
||||||
|
* from the current handler and writes both the current and new ones.
|
||||||
|
*
|
||||||
|
* It ignores errors from the new handler.
|
||||||
|
*
|
||||||
|
* @author Ross Motley <ross.motley@amara.com>
|
||||||
|
* @author Oliver Radwell <oliver.radwell@amara.com>
|
||||||
|
*/
|
||||||
|
class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
private $currentHandler;
|
||||||
|
private $writeOnlyHandler;
|
||||||
|
|
||||||
|
public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler)
|
||||||
|
{
|
||||||
|
if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) {
|
||||||
|
$currentHandler = new StrictSessionHandler($currentHandler);
|
||||||
|
}
|
||||||
|
if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) {
|
||||||
|
$writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentHandler = $currentHandler;
|
||||||
|
$this->writeOnlyHandler = $writeOnlyHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->close();
|
||||||
|
$this->writeOnlyHandler->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function destroy($sessionId)
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->destroy($sessionId);
|
||||||
|
$this->writeOnlyHandler->destroy($sessionId);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->gc($maxlifetime);
|
||||||
|
$this->writeOnlyHandler->gc($maxlifetime);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function open($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->open($savePath, $sessionName);
|
||||||
|
$this->writeOnlyHandler->open($savePath, $sessionName);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($sessionId)
|
||||||
|
{
|
||||||
|
// No reading from new handler until switch-over
|
||||||
|
return $this->currentHandler->read($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function write($sessionId, $sessionData)
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->write($sessionId, $sessionData);
|
||||||
|
$this->writeOnlyHandler->write($sessionId, $sessionData);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateId($sessionId)
|
||||||
|
{
|
||||||
|
// No reading from new handler until switch-over
|
||||||
|
return $this->currentHandler->validateId($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $sessionData)
|
||||||
|
{
|
||||||
|
$result = $this->currentHandler->updateTimestamp($sessionId, $sessionData);
|
||||||
|
$this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
187
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
vendored
Normal file
187
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session handler using the mongodb/mongodb package and MongoDB driver extension.
|
||||||
|
*
|
||||||
|
* @author Markus Bachmann <markus.bachmann@bachi.biz>
|
||||||
|
*
|
||||||
|
* @see https://packagist.org/packages/mongodb/mongodb
|
||||||
|
* @see https://php.net/mongodb
|
||||||
|
*/
|
||||||
|
class MongoDbSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
private $mongo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \MongoDB\Collection
|
||||||
|
*/
|
||||||
|
private $collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* List of available options:
|
||||||
|
* * database: The name of the database [required]
|
||||||
|
* * collection: The name of the collection [required]
|
||||||
|
* * id_field: The field name for storing the session id [default: _id]
|
||||||
|
* * data_field: The field name for storing the session data [default: data]
|
||||||
|
* * time_field: The field name for storing the timestamp [default: time]
|
||||||
|
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
|
||||||
|
*
|
||||||
|
* It is strongly recommended to put an index on the `expiry_field` for
|
||||||
|
* garbage-collection. Alternatively it's possible to automatically expire
|
||||||
|
* the sessions in the database as described below:
|
||||||
|
*
|
||||||
|
* A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
|
||||||
|
* automatically. Such an index can for example look like this:
|
||||||
|
*
|
||||||
|
* db.<session-collection>.ensureIndex(
|
||||||
|
* { "<expiry-field>": 1 },
|
||||||
|
* { "expireAfterSeconds": 0 }
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* More details on: https://docs.mongodb.org/manual/tutorial/expire-data/
|
||||||
|
*
|
||||||
|
* If you use such an index, you can drop `gc_probability` to 0 since
|
||||||
|
* no garbage-collection is required.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When "database" or "collection" not provided
|
||||||
|
*/
|
||||||
|
public function __construct(\MongoDB\Client $mongo, array $options)
|
||||||
|
{
|
||||||
|
if (!isset($options['database']) || !isset($options['collection'])) {
|
||||||
|
throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mongo = $mongo;
|
||||||
|
|
||||||
|
$this->options = array_merge([
|
||||||
|
'id_field' => '_id',
|
||||||
|
'data_field' => 'data',
|
||||||
|
'time_field' => 'time',
|
||||||
|
'expiry_field' => 'expires_at',
|
||||||
|
], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId)
|
||||||
|
{
|
||||||
|
$this->getCollection()->deleteOne([
|
||||||
|
$this->options['id_field'] => $sessionId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
$this->getCollection()->deleteMany([
|
||||||
|
$this->options['expiry_field'] => ['$lt' => new \MongoDB\BSON\UTCDateTime()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data)
|
||||||
|
{
|
||||||
|
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000);
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
$this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(),
|
||||||
|
$this->options['expiry_field'] => $expiry,
|
||||||
|
$this->options['data_field'] => new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->getCollection()->updateOne(
|
||||||
|
[$this->options['id_field'] => $sessionId],
|
||||||
|
['$set' => $fields],
|
||||||
|
['upsert' => true]
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000);
|
||||||
|
|
||||||
|
$this->getCollection()->updateOne(
|
||||||
|
[$this->options['id_field'] => $sessionId],
|
||||||
|
['$set' => [
|
||||||
|
$this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(),
|
||||||
|
$this->options['expiry_field'] => $expiry,
|
||||||
|
]]
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId)
|
||||||
|
{
|
||||||
|
$dbData = $this->getCollection()->findOne([
|
||||||
|
$this->options['id_field'] => $sessionId,
|
||||||
|
$this->options['expiry_field'] => ['$gte' => new \MongoDB\BSON\UTCDateTime()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (null === $dbData) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dbData[$this->options['data_field']]->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCollection(): \MongoDB\Collection
|
||||||
|
{
|
||||||
|
if (null === $this->collection) {
|
||||||
|
$this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \MongoDB\Client
|
||||||
|
*/
|
||||||
|
protected function getMongo()
|
||||||
|
{
|
||||||
|
return $this->mongo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native session handler using PHP's built in file storage.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class NativeFileSessionHandler extends \SessionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $savePath Path of directory to save session files
|
||||||
|
* Default null will leave setting as defined by PHP.
|
||||||
|
* '/path', 'N;/path', or 'N;octal-mode;/path
|
||||||
|
*
|
||||||
|
* @see https://php.net/session.configuration#ini.session.save-path for further details.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException On invalid $savePath
|
||||||
|
* @throws \RuntimeException When failing to create the save directory
|
||||||
|
*/
|
||||||
|
public function __construct(string $savePath = null)
|
||||||
|
{
|
||||||
|
if (null === $savePath) {
|
||||||
|
$savePath = ini_get('session.save_path');
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseDir = $savePath;
|
||||||
|
|
||||||
|
if ($count = substr_count($savePath, ';')) {
|
||||||
|
if ($count > 2) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// characters after last ';' are the path
|
||||||
|
$baseDir = ltrim(strrchr($savePath, ';'), ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
|
||||||
|
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('session.save_path', $savePath);
|
||||||
|
ini_set('session.save_handler', 'files');
|
||||||
|
}
|
||||||
|
}
|
76
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
vendored
Normal file
76
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used in unit testing or in a situations where persisted sessions are not desired.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class NullSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateId($sessionId)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId)
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
899
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
vendored
Normal file
899
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,899 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session handler using a PDO connection to read and write data.
|
||||||
|
*
|
||||||
|
* It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
|
||||||
|
* different locking strategies to handle concurrent access to the same session.
|
||||||
|
* Locking is necessary to prevent loss of data due to race conditions and to keep
|
||||||
|
* the session data consistent between read() and write(). With locking, requests
|
||||||
|
* for the same session will wait until the other one finished writing. For this
|
||||||
|
* reason it's best practice to close a session as early as possible to improve
|
||||||
|
* concurrency. PHPs internal files session handler also implements locking.
|
||||||
|
*
|
||||||
|
* Attention: Since SQLite does not support row level locks but locks the whole database,
|
||||||
|
* it means only one session can be accessed at a time. Even different sessions would wait
|
||||||
|
* for another to finish. So saving session in SQLite should only be considered for
|
||||||
|
* development or prototypes.
|
||||||
|
*
|
||||||
|
* Session data is a binary string that can contain non-printable characters like the null byte.
|
||||||
|
* For this reason it must be saved in a binary column in the database like BLOB in MySQL.
|
||||||
|
* Saving it in a character column could corrupt the data. You can use createTable()
|
||||||
|
* to initialize a correctly defined table.
|
||||||
|
*
|
||||||
|
* @see https://php.net/sessionhandlerinterface
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Michael Williams <michael.williams@funsational.com>
|
||||||
|
* @author Tobias Schultze <http://tobion.de>
|
||||||
|
*/
|
||||||
|
class PdoSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* No locking is done. This means sessions are prone to loss of data due to
|
||||||
|
* race conditions of concurrent requests to the same session. The last session
|
||||||
|
* write will win in this case. It might be useful when you implement your own
|
||||||
|
* logic to deal with this like an optimistic approach.
|
||||||
|
*/
|
||||||
|
const LOCK_NONE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an application-level lock on a session. The disadvantage is that the
|
||||||
|
* lock is not enforced by the database and thus other, unaware parts of the
|
||||||
|
* application could still concurrently modify the session. The advantage is it
|
||||||
|
* does not require a transaction.
|
||||||
|
* This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
|
||||||
|
*/
|
||||||
|
const LOCK_ADVISORY = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues a real row lock. Since it uses a transaction between opening and
|
||||||
|
* closing a session, you have to be careful when you use same database connection
|
||||||
|
* that you also use for your application logic. This mode is the default because
|
||||||
|
* it's the only reliable solution across DBMSs.
|
||||||
|
*/
|
||||||
|
const LOCK_TRANSACTIONAL = 2;
|
||||||
|
|
||||||
|
private const MAX_LIFETIME = 315576000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \PDO|null PDO instance or null when not connected yet
|
||||||
|
*/
|
||||||
|
private $pdo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled
|
||||||
|
*/
|
||||||
|
private $dsn = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Database driver
|
||||||
|
*/
|
||||||
|
private $driver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Table name
|
||||||
|
*/
|
||||||
|
private $table = 'sessions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Column for session id
|
||||||
|
*/
|
||||||
|
private $idCol = 'sess_id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Column for session data
|
||||||
|
*/
|
||||||
|
private $dataCol = 'sess_data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Column for lifetime
|
||||||
|
*/
|
||||||
|
private $lifetimeCol = 'sess_lifetime';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Column for timestamp
|
||||||
|
*/
|
||||||
|
private $timeCol = 'sess_time';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Username when lazy-connect
|
||||||
|
*/
|
||||||
|
private $username = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Password when lazy-connect
|
||||||
|
*/
|
||||||
|
private $password = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Connection options when lazy-connect
|
||||||
|
*/
|
||||||
|
private $connectionOptions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int The strategy for locking, see constants
|
||||||
|
*/
|
||||||
|
private $lockMode = self::LOCK_TRANSACTIONAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's an array to support multiple reads before closing which is manual, non-standard usage.
|
||||||
|
*
|
||||||
|
* @var \PDOStatement[] An array of statements to release advisory locks
|
||||||
|
*/
|
||||||
|
private $unlockStatements = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool True when the current session exists but expired according to session.gc_maxlifetime
|
||||||
|
*/
|
||||||
|
private $sessionExpired = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool Whether a transaction is active
|
||||||
|
*/
|
||||||
|
private $inTransaction = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool Whether gc() has been called
|
||||||
|
*/
|
||||||
|
private $gcCalled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can either pass an existing database connection as PDO instance or
|
||||||
|
* pass a DSN string that will be used to lazy-connect to the database
|
||||||
|
* when the session is actually used. Furthermore it's possible to pass null
|
||||||
|
* which will then use the session.save_path ini setting as PDO DSN parameter.
|
||||||
|
*
|
||||||
|
* List of available options:
|
||||||
|
* * db_table: The name of the table [default: sessions]
|
||||||
|
* * db_id_col: The column where to store the session id [default: sess_id]
|
||||||
|
* * db_data_col: The column where to store the session data [default: sess_data]
|
||||||
|
* * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
|
||||||
|
* * db_time_col: The column where to store the timestamp [default: sess_time]
|
||||||
|
* * db_username: The username when lazy-connect [default: '']
|
||||||
|
* * db_password: The password when lazy-connect [default: '']
|
||||||
|
* * db_connection_options: An array of driver-specific connection options [default: []]
|
||||||
|
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
|
||||||
|
*
|
||||||
|
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
||||||
|
*/
|
||||||
|
public function __construct($pdoOrDsn = null, array $options = [])
|
||||||
|
{
|
||||||
|
if ($pdoOrDsn instanceof \PDO) {
|
||||||
|
if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdo = $pdoOrDsn;
|
||||||
|
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||||
|
} elseif (\is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
|
||||||
|
$this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
|
||||||
|
} else {
|
||||||
|
$this->dsn = $pdoOrDsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
|
||||||
|
$this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
|
||||||
|
$this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
|
||||||
|
$this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
|
||||||
|
$this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
|
||||||
|
$this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
|
||||||
|
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
|
||||||
|
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
|
||||||
|
$this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the table to store sessions which can be called once for setup.
|
||||||
|
*
|
||||||
|
* Session ID is saved in a column of maximum length 128 because that is enough even
|
||||||
|
* for a 512 bit configured session.hash_function like Whirlpool. Session data is
|
||||||
|
* saved in a BLOB. One could also use a shorter inlined varbinary column
|
||||||
|
* if one was sure the data fits into it.
|
||||||
|
*
|
||||||
|
* @throws \PDOException When the table already exists
|
||||||
|
* @throws \DomainException When an unsupported PDO driver is used
|
||||||
|
*/
|
||||||
|
public function createTable()
|
||||||
|
{
|
||||||
|
// connect if we are not yet
|
||||||
|
$this->getConnection();
|
||||||
|
|
||||||
|
switch ($this->driver) {
|
||||||
|
case 'mysql':
|
||||||
|
// We use varbinary for the ID column because it prevents unwanted conversions:
|
||||||
|
// - character set conversions between server and client
|
||||||
|
// - trailing space removal
|
||||||
|
// - case-insensitivity
|
||||||
|
// - language processing like é == e
|
||||||
|
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
|
||||||
|
break;
|
||||||
|
case 'sqlite':
|
||||||
|
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
|
||||||
|
break;
|
||||||
|
case 'pgsql':
|
||||||
|
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
|
||||||
|
break;
|
||||||
|
case 'oci':
|
||||||
|
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
|
||||||
|
break;
|
||||||
|
case 'sqlsrv':
|
||||||
|
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pdo->exec($sql);
|
||||||
|
$this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)");
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when the current session exists but expired according to session.gc_maxlifetime.
|
||||||
|
*
|
||||||
|
* Can be used to distinguish between a new session and one that expired due to inactivity.
|
||||||
|
*
|
||||||
|
* @return bool Whether current session expired
|
||||||
|
*/
|
||||||
|
public function isSessionExpired()
|
||||||
|
{
|
||||||
|
return $this->sessionExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function open($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
$this->sessionExpired = false;
|
||||||
|
|
||||||
|
if (null === $this->pdo) {
|
||||||
|
$this->connect($this->dsn ?: $savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::open($savePath, $sessionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($sessionId)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return parent::read($sessionId);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
// We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
|
||||||
|
// This way, pruning expired sessions does not block them from being started while the current session is used.
|
||||||
|
$this->gcCalled = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId)
|
||||||
|
{
|
||||||
|
// delete the record associated with this id
|
||||||
|
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$stmt->execute();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data)
|
||||||
|
{
|
||||||
|
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We use a single MERGE SQL query when supported by the database.
|
||||||
|
$mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
|
||||||
|
if (null !== $mergeStmt) {
|
||||||
|
$mergeStmt->execute();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime);
|
||||||
|
$updateStmt->execute();
|
||||||
|
|
||||||
|
// When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
|
||||||
|
// duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
|
||||||
|
// We can just catch such an error and re-execute the update. This is similar to a serializable
|
||||||
|
// transaction with retry logic on serialization failures but without the overhead and without possible
|
||||||
|
// false positives due to longer gap locking.
|
||||||
|
if (!$updateStmt->rowCount()) {
|
||||||
|
try {
|
||||||
|
$insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime);
|
||||||
|
$insertStmt->execute();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
// Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
|
||||||
|
if (0 === strpos($e->getCode(), '23')) {
|
||||||
|
$updateStmt->execute();
|
||||||
|
} else {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
$expiry = time() + (int) ini_get('session.gc_maxlifetime');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$updateStmt = $this->pdo->prepare(
|
||||||
|
"UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"
|
||||||
|
);
|
||||||
|
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT);
|
||||||
|
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
$updateStmt->execute();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$this->commit();
|
||||||
|
|
||||||
|
while ($unlockStmt = array_shift($this->unlockStatements)) {
|
||||||
|
$unlockStmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->gcCalled) {
|
||||||
|
$this->gcCalled = false;
|
||||||
|
|
||||||
|
// delete the session records that have expired
|
||||||
|
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min";
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
// to be removed in 6.0
|
||||||
|
if ('mysql' === $this->driver) {
|
||||||
|
$legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time";
|
||||||
|
} else {
|
||||||
|
$legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol";
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($legacySql);
|
||||||
|
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== $this->dsn) {
|
||||||
|
$this->pdo = null; // only close lazy-connection
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-connects to the database.
|
||||||
|
*/
|
||||||
|
private function connect(string $dsn): void
|
||||||
|
{
|
||||||
|
$this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
|
||||||
|
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||||
|
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a PDO DSN from a URL-like connection string.
|
||||||
|
*
|
||||||
|
* @todo implement missing support for oci DSN (which look totally different from other PDO ones)
|
||||||
|
*/
|
||||||
|
private function buildDsnFromUrl(string $dsnOrUrl): string
|
||||||
|
{
|
||||||
|
// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
|
||||||
|
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
|
||||||
|
|
||||||
|
$params = parse_url($url);
|
||||||
|
|
||||||
|
if (false === $params) {
|
||||||
|
return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = array_map('rawurldecode', $params);
|
||||||
|
|
||||||
|
// Override the default username and password. Values passed through options will still win over these in the constructor.
|
||||||
|
if (isset($params['user'])) {
|
||||||
|
$this->username = $params['user'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['pass'])) {
|
||||||
|
$this->password = $params['pass'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($params['scheme'])) {
|
||||||
|
throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
|
||||||
|
}
|
||||||
|
|
||||||
|
$driverAliasMap = [
|
||||||
|
'mssql' => 'sqlsrv',
|
||||||
|
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
|
||||||
|
'postgres' => 'pgsql',
|
||||||
|
'postgresql' => 'pgsql',
|
||||||
|
'sqlite3' => 'sqlite',
|
||||||
|
];
|
||||||
|
|
||||||
|
$driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
|
||||||
|
|
||||||
|
// Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
|
||||||
|
if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
|
||||||
|
$driver = substr($driver, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($driver) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'pgsql':
|
||||||
|
$dsn = $driver.':';
|
||||||
|
|
||||||
|
if (isset($params['host']) && '' !== $params['host']) {
|
||||||
|
$dsn .= 'host='.$params['host'].';';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['port']) && '' !== $params['port']) {
|
||||||
|
$dsn .= 'port='.$params['port'].';';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['path'])) {
|
||||||
|
$dbName = substr($params['path'], 1); // Remove the leading slash
|
||||||
|
$dsn .= 'dbname='.$dbName.';';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dsn;
|
||||||
|
|
||||||
|
case 'sqlite':
|
||||||
|
return 'sqlite:'.substr($params['path'], 1);
|
||||||
|
|
||||||
|
case 'sqlsrv':
|
||||||
|
$dsn = 'sqlsrv:server=';
|
||||||
|
|
||||||
|
if (isset($params['host'])) {
|
||||||
|
$dsn .= $params['host'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['port']) && '' !== $params['port']) {
|
||||||
|
$dsn .= ','.$params['port'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params['path'])) {
|
||||||
|
$dbName = substr($params['path'], 1); // Remove the leading slash
|
||||||
|
$dsn .= ';Database='.$dbName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dsn;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to begin a transaction.
|
||||||
|
*
|
||||||
|
* Since SQLite does not support row level locks, we have to acquire a reserved lock
|
||||||
|
* on the database immediately. Because of https://bugs.php.net/42766 we have to create
|
||||||
|
* such a transaction manually which also means we cannot use PDO::commit or
|
||||||
|
* PDO::rollback or PDO::inTransaction for SQLite.
|
||||||
|
*
|
||||||
|
* Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
|
||||||
|
* due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
|
||||||
|
* So we change it to READ COMMITTED.
|
||||||
|
*/
|
||||||
|
private function beginTransaction(): void
|
||||||
|
{
|
||||||
|
if (!$this->inTransaction) {
|
||||||
|
if ('sqlite' === $this->driver) {
|
||||||
|
$this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
|
||||||
|
} else {
|
||||||
|
if ('mysql' === $this->driver) {
|
||||||
|
$this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
|
||||||
|
}
|
||||||
|
$this->pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
$this->inTransaction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to commit a transaction.
|
||||||
|
*/
|
||||||
|
private function commit(): void
|
||||||
|
{
|
||||||
|
if ($this->inTransaction) {
|
||||||
|
try {
|
||||||
|
// commit read-write transaction which also releases the lock
|
||||||
|
if ('sqlite' === $this->driver) {
|
||||||
|
$this->pdo->exec('COMMIT');
|
||||||
|
} else {
|
||||||
|
$this->pdo->commit();
|
||||||
|
}
|
||||||
|
$this->inTransaction = false;
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->rollback();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to rollback a transaction.
|
||||||
|
*/
|
||||||
|
private function rollback(): void
|
||||||
|
{
|
||||||
|
// We only need to rollback if we are in a transaction. Otherwise the resulting
|
||||||
|
// error would hide the real problem why rollback was called. We might not be
|
||||||
|
// in a transaction when not using the transactional locking behavior or when
|
||||||
|
// two callbacks (e.g. destroy and write) are invoked that both fail.
|
||||||
|
if ($this->inTransaction) {
|
||||||
|
if ('sqlite' === $this->driver) {
|
||||||
|
$this->pdo->exec('ROLLBACK');
|
||||||
|
} else {
|
||||||
|
$this->pdo->rollBack();
|
||||||
|
}
|
||||||
|
$this->inTransaction = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the session data in respect to the different locking strategies.
|
||||||
|
*
|
||||||
|
* We need to make sure we do not return session data that is already considered garbage according
|
||||||
|
* to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId)
|
||||||
|
{
|
||||||
|
if (self::LOCK_ADVISORY === $this->lockMode) {
|
||||||
|
$this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectSql = $this->getSelectSql();
|
||||||
|
$selectStmt = $this->pdo->prepare($selectSql);
|
||||||
|
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$insertStmt = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$selectStmt->execute();
|
||||||
|
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
|
||||||
|
|
||||||
|
if ($sessionRows) {
|
||||||
|
$expiry = (int) $sessionRows[0][1];
|
||||||
|
if ($expiry <= self::MAX_LIFETIME) {
|
||||||
|
$expiry += $sessionRows[0][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expiry < time()) {
|
||||||
|
$this->sessionExpired = true;
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $insertStmt) {
|
||||||
|
$this->rollback();
|
||||||
|
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_var(ini_get('session.use_strict_mode'), FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
|
||||||
|
// In strict mode, session fixation is not possible: new sessions always start with a unique
|
||||||
|
// random id, so that concurrency is not possible and this code path can be skipped.
|
||||||
|
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
|
||||||
|
// until other connections to the session are committed.
|
||||||
|
try {
|
||||||
|
$insertStmt = $this->getInsertStatement($sessionId, '', 0);
|
||||||
|
$insertStmt->execute();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
// Catch duplicate key error because other connection created the session already.
|
||||||
|
// It would only not be the case when the other connection destroyed the session.
|
||||||
|
if (0 === strpos($e->getCode(), '23')) {
|
||||||
|
// Retrieve finished session data written by concurrent connection by restarting the loop.
|
||||||
|
// We have to start a new transaction as a failed query will mark the current transaction as
|
||||||
|
// aborted in PostgreSQL and disallow further queries within it.
|
||||||
|
$this->rollback();
|
||||||
|
$this->beginTransaction();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes an application-level lock on the database.
|
||||||
|
*
|
||||||
|
* @return \PDOStatement The statement that needs to be executed later to release the lock
|
||||||
|
*
|
||||||
|
* @throws \DomainException When an unsupported PDO driver is used
|
||||||
|
*
|
||||||
|
* @todo implement missing advisory locks
|
||||||
|
* - for oci using DBMS_LOCK.REQUEST
|
||||||
|
* - for sqlsrv using sp_getapplock with LockOwner = Session
|
||||||
|
*/
|
||||||
|
private function doAdvisoryLock(string $sessionId): \PDOStatement
|
||||||
|
{
|
||||||
|
switch ($this->driver) {
|
||||||
|
case 'mysql':
|
||||||
|
// MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced.
|
||||||
|
$lockId = substr($sessionId, 0, 64);
|
||||||
|
// should we handle the return value? 0 on timeout, null on error
|
||||||
|
// we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
|
||||||
|
$stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
|
||||||
|
$stmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
|
||||||
|
$releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
|
||||||
|
|
||||||
|
return $releaseStmt;
|
||||||
|
case 'pgsql':
|
||||||
|
// Obtaining an exclusive session level advisory lock requires an integer key.
|
||||||
|
// When session.sid_bits_per_character > 4, the session id can contain non-hex-characters.
|
||||||
|
// So we cannot just use hexdec().
|
||||||
|
if (4 === \PHP_INT_SIZE) {
|
||||||
|
$sessionInt1 = $this->convertStringToInt($sessionId);
|
||||||
|
$sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4));
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
|
||||||
|
$stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
|
||||||
|
$releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
|
||||||
|
$releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
|
||||||
|
} else {
|
||||||
|
$sessionBigInt = $this->convertStringToInt($sessionId);
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
|
||||||
|
$stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
|
||||||
|
$releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $releaseStmt;
|
||||||
|
case 'sqlite':
|
||||||
|
throw new \DomainException('SQLite does not support advisory locks.');
|
||||||
|
default:
|
||||||
|
throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
|
||||||
|
*
|
||||||
|
* Keep in mind, PHP integers are signed.
|
||||||
|
*/
|
||||||
|
private function convertStringToInt(string $string): int
|
||||||
|
{
|
||||||
|
if (4 === \PHP_INT_SIZE) {
|
||||||
|
return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]);
|
||||||
|
$int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);
|
||||||
|
|
||||||
|
return $int2 + ($int1 << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a locking or nonlocking SQL query to read session information.
|
||||||
|
*
|
||||||
|
* @throws \DomainException When an unsupported PDO driver is used
|
||||||
|
*/
|
||||||
|
private function getSelectSql(): string
|
||||||
|
{
|
||||||
|
if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
|
||||||
|
$this->beginTransaction();
|
||||||
|
|
||||||
|
// selecting the time column should be removed in 6.0
|
||||||
|
switch ($this->driver) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'oci':
|
||||||
|
case 'pgsql':
|
||||||
|
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
|
||||||
|
case 'sqlsrv':
|
||||||
|
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id";
|
||||||
|
case 'sqlite':
|
||||||
|
// we already locked when starting transaction
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an insert statement supported by the database for writing session data.
|
||||||
|
*/
|
||||||
|
private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement
|
||||||
|
{
|
||||||
|
switch ($this->driver) {
|
||||||
|
case 'oci':
|
||||||
|
$data = fopen('php://memory', 'r+');
|
||||||
|
fwrite($data, $sessionData);
|
||||||
|
rewind($data);
|
||||||
|
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$data = $sessionData;
|
||||||
|
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||||
|
$stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an update statement supported by the database for writing session data.
|
||||||
|
*/
|
||||||
|
private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement
|
||||||
|
{
|
||||||
|
switch ($this->driver) {
|
||||||
|
case 'oci':
|
||||||
|
$data = fopen('php://memory', 'r+');
|
||||||
|
fwrite($data, $sessionData);
|
||||||
|
rewind($data);
|
||||||
|
$sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$data = $sessionData;
|
||||||
|
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||||
|
$stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
|
||||||
|
*/
|
||||||
|
private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement
|
||||||
|
{
|
||||||
|
switch (true) {
|
||||||
|
case 'mysql' === $this->driver:
|
||||||
|
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ".
|
||||||
|
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||||
|
break;
|
||||||
|
case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
|
||||||
|
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
|
||||||
|
// It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
|
||||||
|
$mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
|
||||||
|
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||||
|
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
|
||||||
|
break;
|
||||||
|
case 'sqlite' === $this->driver:
|
||||||
|
$mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)";
|
||||||
|
break;
|
||||||
|
case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
|
||||||
|
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ".
|
||||||
|
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mergeStmt = $this->pdo->prepare($mergeSql);
|
||||||
|
|
||||||
|
if ('sqlsrv' === $this->driver) {
|
||||||
|
$mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
|
||||||
|
$mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
|
||||||
|
$mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
|
||||||
|
$mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT);
|
||||||
|
$mergeStmt->bindValue(4, time(), \PDO::PARAM_INT);
|
||||||
|
$mergeStmt->bindParam(5, $data, \PDO::PARAM_LOB);
|
||||||
|
$mergeStmt->bindValue(6, time() + $maxlifetime, \PDO::PARAM_INT);
|
||||||
|
$mergeStmt->bindValue(6, time(), \PDO::PARAM_INT);
|
||||||
|
} else {
|
||||||
|
$mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
|
||||||
|
$mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||||
|
$mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
|
||||||
|
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mergeStmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a PDO instance.
|
||||||
|
*
|
||||||
|
* @return \PDO
|
||||||
|
*/
|
||||||
|
protected function getConnection()
|
||||||
|
{
|
||||||
|
if (null === $this->pdo) {
|
||||||
|
$this->connect($this->dsn ?: ini_get('session.save_path'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pdo;
|
||||||
|
}
|
||||||
|
}
|
120
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
vendored
Normal file
120
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
use Predis\Response\ErrorInterface;
|
||||||
|
use Symfony\Component\Cache\Traits\RedisClusterProxy;
|
||||||
|
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis based session storage handler based on the Redis class
|
||||||
|
* provided by the PHP redis extension.
|
||||||
|
*
|
||||||
|
* @author Dalibor Karlović <dalibor@flexolabs.io>
|
||||||
|
*/
|
||||||
|
class RedisSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
private $redis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Key prefix for shared environments
|
||||||
|
*/
|
||||||
|
private $prefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int Time to live in seconds
|
||||||
|
*/
|
||||||
|
private $ttl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of available options:
|
||||||
|
* * prefix: The prefix to use for the keys in order to avoid collision on the Redis server
|
||||||
|
* * ttl: The time to live in seconds.
|
||||||
|
*
|
||||||
|
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When unsupported client or options are passed
|
||||||
|
*/
|
||||||
|
public function __construct($redis, array $options = [])
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!$redis instanceof \Redis &&
|
||||||
|
!$redis instanceof \RedisArray &&
|
||||||
|
!$redis instanceof \RedisCluster &&
|
||||||
|
!$redis instanceof \Predis\ClientInterface &&
|
||||||
|
!$redis instanceof RedisProxy &&
|
||||||
|
!$redis instanceof RedisClusterProxy
|
||||||
|
) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redis = $redis;
|
||||||
|
$this->prefix = $options['prefix'] ?? 'sf_s';
|
||||||
|
$this->ttl = $options['ttl'] ?? (int) ini_get('session.gc_maxlifetime');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId): string
|
||||||
|
{
|
||||||
|
return $this->redis->get($this->prefix.$sessionId) ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data): bool
|
||||||
|
{
|
||||||
|
$result = $this->redis->setEx($this->prefix.$sessionId, $this->ttl, $data);
|
||||||
|
|
||||||
|
return $result && !$result instanceof ErrorInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId): bool
|
||||||
|
{
|
||||||
|
$this->redis->del($this->prefix.$sessionId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function close(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
return (bool) $this->redis->expire($this->prefix.$sessionId, $this->ttl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\DriverManager;
|
||||||
|
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||||
|
use Symfony\Component\Cache\Traits\RedisClusterProxy;
|
||||||
|
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class SessionHandlerFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN
|
||||||
|
*/
|
||||||
|
public static function createHandler($connection): AbstractSessionHandler
|
||||||
|
{
|
||||||
|
if (!\is_string($connection) && !\is_object($connection)) {
|
||||||
|
throw new \TypeError(sprintf('Argument 1 passed to %s() must be a string or a connection object, %s given.', __METHOD__, \gettype($connection)));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case $connection instanceof \Redis:
|
||||||
|
case $connection instanceof \RedisArray:
|
||||||
|
case $connection instanceof \RedisCluster:
|
||||||
|
case $connection instanceof \Predis\ClientInterface:
|
||||||
|
case $connection instanceof RedisProxy:
|
||||||
|
case $connection instanceof RedisClusterProxy:
|
||||||
|
return new RedisSessionHandler($connection);
|
||||||
|
|
||||||
|
case $connection instanceof \Memcached:
|
||||||
|
return new MemcachedSessionHandler($connection);
|
||||||
|
|
||||||
|
case $connection instanceof \PDO:
|
||||||
|
return new PdoSessionHandler($connection);
|
||||||
|
|
||||||
|
case !\is_string($connection):
|
||||||
|
throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', \get_class($connection)));
|
||||||
|
case 0 === strpos($connection, 'file://'):
|
||||||
|
return new StrictSessionHandler(new NativeFileSessionHandler(substr($connection, 7)));
|
||||||
|
|
||||||
|
case 0 === strpos($connection, 'redis://'):
|
||||||
|
case 0 === strpos($connection, 'rediss://'):
|
||||||
|
case 0 === strpos($connection, 'memcached://'):
|
||||||
|
if (!class_exists(AbstractAdapter::class)) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
|
||||||
|
}
|
||||||
|
$handlerClass = 0 === strpos($connection, 'memcached://') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
|
||||||
|
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
|
||||||
|
|
||||||
|
return new $handlerClass($connection);
|
||||||
|
|
||||||
|
case 0 === strpos($connection, 'pdo_oci://'):
|
||||||
|
if (!class_exists(DriverManager::class)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection));
|
||||||
|
}
|
||||||
|
$connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection();
|
||||||
|
// no break;
|
||||||
|
|
||||||
|
case 0 === strpos($connection, 'mssql://'):
|
||||||
|
case 0 === strpos($connection, 'mysql://'):
|
||||||
|
case 0 === strpos($connection, 'mysql2://'):
|
||||||
|
case 0 === strpos($connection, 'pgsql://'):
|
||||||
|
case 0 === strpos($connection, 'postgres://'):
|
||||||
|
case 0 === strpos($connection, 'postgresql://'):
|
||||||
|
case 0 === strpos($connection, 'sqlsrv://'):
|
||||||
|
case 0 === strpos($connection, 'sqlite://'):
|
||||||
|
case 0 === strpos($connection, 'sqlite3://'):
|
||||||
|
return new PdoSessionHandler($connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException(sprintf('Unsupported Connection: %s.', $connection));
|
||||||
|
}
|
||||||
|
}
|
103
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
vendored
Normal file
103
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class StrictSessionHandler extends AbstractSessionHandler
|
||||||
|
{
|
||||||
|
private $handler;
|
||||||
|
private $doDestroy;
|
||||||
|
|
||||||
|
public function __construct(\SessionHandlerInterface $handler)
|
||||||
|
{
|
||||||
|
if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
|
||||||
|
throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_class($handler), self::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->handler = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function open($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
parent::open($savePath, $sessionName);
|
||||||
|
|
||||||
|
return $this->handler->open($savePath, $sessionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doRead(string $sessionId)
|
||||||
|
{
|
||||||
|
return $this->handler->read($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
return $this->write($sessionId, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite(string $sessionId, string $data)
|
||||||
|
{
|
||||||
|
return $this->handler->write($sessionId, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function destroy($sessionId)
|
||||||
|
{
|
||||||
|
$this->doDestroy = true;
|
||||||
|
$destroyed = parent::destroy($sessionId);
|
||||||
|
|
||||||
|
return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doDestroy(string $sessionId)
|
||||||
|
{
|
||||||
|
$this->doDestroy = false;
|
||||||
|
|
||||||
|
return $this->handler->destroy($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
return $this->handler->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
return $this->handler->gc($maxlifetime);
|
||||||
|
}
|
||||||
|
}
|
166
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php
vendored
Normal file
166
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MetadataBag.php
vendored
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata container.
|
||||||
|
*
|
||||||
|
* Adds metadata to the session.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class MetadataBag implements SessionBagInterface
|
||||||
|
{
|
||||||
|
const CREATED = 'c';
|
||||||
|
const UPDATED = 'u';
|
||||||
|
const LIFETIME = 'l';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name = '__metadata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $storageKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unix timestamp.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $lastUsed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $updateThreshold;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $storageKey The key used to store bag in the session
|
||||||
|
* @param int $updateThreshold The time to wait between two UPDATED updates
|
||||||
|
*/
|
||||||
|
public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0)
|
||||||
|
{
|
||||||
|
$this->storageKey = $storageKey;
|
||||||
|
$this->updateThreshold = $updateThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialize(array &$array)
|
||||||
|
{
|
||||||
|
$this->meta = &$array;
|
||||||
|
|
||||||
|
if (isset($array[self::CREATED])) {
|
||||||
|
$this->lastUsed = $this->meta[self::UPDATED];
|
||||||
|
|
||||||
|
$timeStamp = time();
|
||||||
|
if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
|
||||||
|
$this->meta[self::UPDATED] = $timeStamp;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->stampCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lifetime that the session cookie was set with.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLifetime()
|
||||||
|
{
|
||||||
|
return $this->meta[self::LIFETIME];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stamps a new session's metadata.
|
||||||
|
*
|
||||||
|
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
|
||||||
|
* will leave the system settings unchanged, 0 sets the cookie
|
||||||
|
* to expire with browser session. Time is in seconds, and is
|
||||||
|
* not a Unix timestamp.
|
||||||
|
*/
|
||||||
|
public function stampNew(int $lifetime = null)
|
||||||
|
{
|
||||||
|
$this->stampCreated($lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStorageKey()
|
||||||
|
{
|
||||||
|
return $this->storageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the created timestamp metadata.
|
||||||
|
*
|
||||||
|
* @return int Unix timestamp
|
||||||
|
*/
|
||||||
|
public function getCreated()
|
||||||
|
{
|
||||||
|
return $this->meta[self::CREATED];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last used metadata.
|
||||||
|
*
|
||||||
|
* @return int Unix timestamp
|
||||||
|
*/
|
||||||
|
public function getLastUsed()
|
||||||
|
{
|
||||||
|
return $this->lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets name.
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stampCreated(int $lifetime = null): void
|
||||||
|
{
|
||||||
|
$timeStamp = time();
|
||||||
|
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
|
||||||
|
$this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime;
|
||||||
|
}
|
||||||
|
}
|
252
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
vendored
Normal file
252
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
vendored
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MockArraySessionStorage mocks the session for unit tests.
|
||||||
|
*
|
||||||
|
* No PHP session is actually started since a session can be initialized
|
||||||
|
* and shutdown only once per PHP execution cycle.
|
||||||
|
*
|
||||||
|
* When doing functional testing, you should use MockFileSessionStorage instead.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class MockArraySessionStorage implements SessionStorageInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $id = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $started = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $closed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetadataBag
|
||||||
|
*/
|
||||||
|
protected $metadataBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array|SessionBagInterface[]
|
||||||
|
*/
|
||||||
|
protected $bags = [];
|
||||||
|
|
||||||
|
public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->setMetadataBag($metaBag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSessionData(array $array)
|
||||||
|
{
|
||||||
|
$this->data = $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->id)) {
|
||||||
|
$this->id = $this->generateId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadSession();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function regenerate(bool $destroy = false, int $lifetime = null)
|
||||||
|
{
|
||||||
|
if (!$this->started) {
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metadataBag->stampNew($lifetime);
|
||||||
|
$this->id = $this->generateId();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setId(string $id)
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
throw new \LogicException('Cannot set session ID after the session has started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
if (!$this->started || $this->closed) {
|
||||||
|
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
|
||||||
|
}
|
||||||
|
// nothing to do since we don't persist the session data
|
||||||
|
$this->closed = false;
|
||||||
|
$this->started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
// clear out the bags
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
$bag->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear out the session
|
||||||
|
$this->data = [];
|
||||||
|
|
||||||
|
// reconnect the bags to the session
|
||||||
|
$this->loadSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function registerBag(SessionBagInterface $bag)
|
||||||
|
{
|
||||||
|
$this->bags[$bag->getName()] = $bag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getBag(string $name)
|
||||||
|
{
|
||||||
|
if (!isset($this->bags[$name])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->started) {
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->bags[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isStarted()
|
||||||
|
{
|
||||||
|
return $this->started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMetadataBag(MetadataBag $bag = null)
|
||||||
|
{
|
||||||
|
if (null === $bag) {
|
||||||
|
$bag = new MetadataBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metadataBag = $bag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the MetadataBag.
|
||||||
|
*
|
||||||
|
* @return MetadataBag
|
||||||
|
*/
|
||||||
|
public function getMetadataBag()
|
||||||
|
{
|
||||||
|
return $this->metadataBag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a session ID.
|
||||||
|
*
|
||||||
|
* This doesn't need to be particularly cryptographically secure since this is just
|
||||||
|
* a mock.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function generateId()
|
||||||
|
{
|
||||||
|
return hash('sha256', uniqid('ss_mock_', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadSession()
|
||||||
|
{
|
||||||
|
$bags = array_merge($this->bags, [$this->metadataBag]);
|
||||||
|
|
||||||
|
foreach ($bags as $bag) {
|
||||||
|
$key = $bag->getStorageKey();
|
||||||
|
$this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : [];
|
||||||
|
$bag->initialize($this->data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->started = true;
|
||||||
|
$this->closed = false;
|
||||||
|
}
|
||||||
|
}
|
148
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
vendored
Normal file
148
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MockFileSessionStorage is used to mock sessions for
|
||||||
|
* functional testing when done in a single PHP process.
|
||||||
|
*
|
||||||
|
* No PHP session is actually started since a session can be initialized
|
||||||
|
* and shutdown only once per PHP execution cycle and this class does
|
||||||
|
* not pollute any session related globals, including session_*() functions
|
||||||
|
* or session.* PHP ini directives.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class MockFileSessionStorage extends MockArraySessionStorage
|
||||||
|
{
|
||||||
|
private $savePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $savePath Path of directory to save session files
|
||||||
|
*/
|
||||||
|
public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
|
||||||
|
{
|
||||||
|
if (null === $savePath) {
|
||||||
|
$savePath = sys_get_temp_dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
|
||||||
|
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->savePath = $savePath;
|
||||||
|
|
||||||
|
parent::__construct($name, $metaBag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->id) {
|
||||||
|
$this->id = $this->generateId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->read();
|
||||||
|
|
||||||
|
$this->started = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function regenerate(bool $destroy = false, int $lifetime = null)
|
||||||
|
{
|
||||||
|
if (!$this->started) {
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($destroy) {
|
||||||
|
$this->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::regenerate($destroy, $lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
if (!$this->started) {
|
||||||
|
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->data;
|
||||||
|
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
if (empty($data[$key = $bag->getStorageKey()])) {
|
||||||
|
unset($data[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) {
|
||||||
|
unset($data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($data) {
|
||||||
|
file_put_contents($this->getFilePath(), serialize($data));
|
||||||
|
} else {
|
||||||
|
$this->destroy();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is needed for Silex, where the session object is re-used across requests
|
||||||
|
// in functional tests. In Symfony, the container is rebooted, so we don't have
|
||||||
|
// this issue
|
||||||
|
$this->started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a session from persistent storage.
|
||||||
|
* Deliberately leaves session data in memory intact.
|
||||||
|
*/
|
||||||
|
private function destroy(): void
|
||||||
|
{
|
||||||
|
if (is_file($this->getFilePath())) {
|
||||||
|
unlink($this->getFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate path to file.
|
||||||
|
*/
|
||||||
|
private function getFilePath(): string
|
||||||
|
{
|
||||||
|
return $this->savePath.'/'.$this->id.'.mocksess';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads session from storage and loads session.
|
||||||
|
*/
|
||||||
|
private function read(): void
|
||||||
|
{
|
||||||
|
$filePath = $this->getFilePath();
|
||||||
|
$this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : [];
|
||||||
|
|
||||||
|
$this->loadSession();
|
||||||
|
}
|
||||||
|
}
|
466
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
vendored
Normal file
466
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
vendored
Normal file
|
@ -0,0 +1,466 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionUtils;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This provides a base class for session attribute storage.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class NativeSessionStorage implements SessionStorageInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SessionBagInterface[]
|
||||||
|
*/
|
||||||
|
protected $bags = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $started = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $closed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var AbstractProxy|\SessionHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $saveHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetadataBag
|
||||||
|
*/
|
||||||
|
protected $metadataBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $emulateSameSite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on how you want the storage driver to behave you probably
|
||||||
|
* want to override this constructor entirely.
|
||||||
|
*
|
||||||
|
* List of options for $options array with their defaults.
|
||||||
|
*
|
||||||
|
* @see https://php.net/session.configuration for options
|
||||||
|
* but we omit 'session.' from the beginning of the keys for convenience.
|
||||||
|
*
|
||||||
|
* ("auto_start", is not supported as it tells PHP to start a session before
|
||||||
|
* PHP starts to execute user-land code. Setting during runtime has no effect).
|
||||||
|
*
|
||||||
|
* cache_limiter, "" (use "0" to prevent headers from being sent entirely).
|
||||||
|
* cache_expire, "0"
|
||||||
|
* cookie_domain, ""
|
||||||
|
* cookie_httponly, ""
|
||||||
|
* cookie_lifetime, "0"
|
||||||
|
* cookie_path, "/"
|
||||||
|
* cookie_secure, ""
|
||||||
|
* cookie_samesite, null
|
||||||
|
* gc_divisor, "100"
|
||||||
|
* gc_maxlifetime, "1440"
|
||||||
|
* gc_probability, "1"
|
||||||
|
* lazy_write, "1"
|
||||||
|
* name, "PHPSESSID"
|
||||||
|
* referer_check, ""
|
||||||
|
* serialize_handler, "php"
|
||||||
|
* use_strict_mode, "0"
|
||||||
|
* use_cookies, "1"
|
||||||
|
* use_only_cookies, "1"
|
||||||
|
* use_trans_sid, "0"
|
||||||
|
* upload_progress.enabled, "1"
|
||||||
|
* upload_progress.cleanup, "1"
|
||||||
|
* upload_progress.prefix, "upload_progress_"
|
||||||
|
* upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
|
||||||
|
* upload_progress.freq, "1%"
|
||||||
|
* upload_progress.min-freq, "1"
|
||||||
|
* url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
|
||||||
|
* sid_length, "32"
|
||||||
|
* sid_bits_per_character, "5"
|
||||||
|
* trans_sid_hosts, $_SERVER['HTTP_HOST']
|
||||||
|
* trans_sid_tags, "a=href,area=href,frame=src,form="
|
||||||
|
*
|
||||||
|
* @param AbstractProxy|\SessionHandlerInterface|null $handler
|
||||||
|
*/
|
||||||
|
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
|
||||||
|
{
|
||||||
|
if (!\extension_loaded('session')) {
|
||||||
|
throw new \LogicException('PHP extension "session" is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$options += [
|
||||||
|
'cache_limiter' => '',
|
||||||
|
'cache_expire' => 0,
|
||||||
|
'use_cookies' => 1,
|
||||||
|
'lazy_write' => 1,
|
||||||
|
'use_strict_mode' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
session_register_shutdown();
|
||||||
|
|
||||||
|
$this->setMetadataBag($metaBag);
|
||||||
|
$this->setOptions($options);
|
||||||
|
$this->setSaveHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the save handler instance.
|
||||||
|
*
|
||||||
|
* @return AbstractProxy|\SessionHandlerInterface
|
||||||
|
*/
|
||||||
|
public function getSaveHandler()
|
||||||
|
{
|
||||||
|
return $this->saveHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\PHP_SESSION_ACTIVE === session_status()) {
|
||||||
|
throw new \RuntimeException('Failed to start the session: already started by PHP.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
|
||||||
|
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok to try and start the session
|
||||||
|
if (!session_start()) {
|
||||||
|
throw new \RuntimeException('Failed to start the session');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->emulateSameSite) {
|
||||||
|
$originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
|
||||||
|
if (null !== $originalCookie) {
|
||||||
|
header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadSession();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->saveHandler->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setId(string $id)
|
||||||
|
{
|
||||||
|
$this->saveHandler->setId($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->saveHandler->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->saveHandler->setName($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function regenerate(bool $destroy = false, int $lifetime = null)
|
||||||
|
{
|
||||||
|
// Cannot regenerate the session ID for non-active sessions.
|
||||||
|
if (\PHP_SESSION_ACTIVE !== session_status()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers_sent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $lifetime) {
|
||||||
|
ini_set('session.cookie_lifetime', $lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($destroy) {
|
||||||
|
$this->metadataBag->stampNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
$isRegenerated = session_regenerate_id($destroy);
|
||||||
|
|
||||||
|
// The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
|
||||||
|
// @see https://bugs.php.net/70013
|
||||||
|
$this->loadSession();
|
||||||
|
|
||||||
|
if (null !== $this->emulateSameSite) {
|
||||||
|
$originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
|
||||||
|
if (null !== $originalCookie) {
|
||||||
|
header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $isRegenerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
// Store a copy so we can restore the bags in case the session was not left empty
|
||||||
|
$session = $_SESSION;
|
||||||
|
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
if (empty($_SESSION[$key = $bag->getStorageKey()])) {
|
||||||
|
unset($_SESSION[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
|
||||||
|
unset($_SESSION[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register error handler to add information about the current save handler
|
||||||
|
$previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
|
||||||
|
if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
|
||||||
|
$handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
|
||||||
|
$msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
session_write_close();
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
// Restore only if not empty
|
||||||
|
if ($_SESSION) {
|
||||||
|
$_SESSION = $session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closed = true;
|
||||||
|
$this->started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
// clear out the bags
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
$bag->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear out the session
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
// reconnect the bags to the session
|
||||||
|
$this->loadSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function registerBag(SessionBagInterface $bag)
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
throw new \LogicException('Cannot register a bag when the session is already started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bags[$bag->getName()] = $bag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getBag(string $name)
|
||||||
|
{
|
||||||
|
if (!isset($this->bags[$name])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->started && $this->saveHandler->isActive()) {
|
||||||
|
$this->loadSession();
|
||||||
|
} elseif (!$this->started) {
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->bags[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMetadataBag(MetadataBag $metaBag = null)
|
||||||
|
{
|
||||||
|
if (null === $metaBag) {
|
||||||
|
$metaBag = new MetadataBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metadataBag = $metaBag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the MetadataBag.
|
||||||
|
*
|
||||||
|
* @return MetadataBag
|
||||||
|
*/
|
||||||
|
public function getMetadataBag()
|
||||||
|
{
|
||||||
|
return $this->metadataBag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isStarted()
|
||||||
|
{
|
||||||
|
return $this->started;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets session.* ini variables.
|
||||||
|
*
|
||||||
|
* For convenience we omit 'session.' from the beginning of the keys.
|
||||||
|
* Explicitly ignores other ini keys.
|
||||||
|
*
|
||||||
|
* @param array $options Session ini directives [key => value]
|
||||||
|
*
|
||||||
|
* @see https://php.net/session.configuration
|
||||||
|
*/
|
||||||
|
public function setOptions(array $options)
|
||||||
|
{
|
||||||
|
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validOptions = array_flip([
|
||||||
|
'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
|
||||||
|
'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite',
|
||||||
|
'gc_divisor', 'gc_maxlifetime', 'gc_probability',
|
||||||
|
'lazy_write', 'name', 'referer_check',
|
||||||
|
'serialize_handler', 'use_strict_mode', 'use_cookies',
|
||||||
|
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
|
||||||
|
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
|
||||||
|
'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
|
||||||
|
'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($options as $key => $value) {
|
||||||
|
if (isset($validOptions[$key])) {
|
||||||
|
if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) {
|
||||||
|
// PHP < 7.3 does not support same_site cookies. We will emulate it in
|
||||||
|
// the start() method instead.
|
||||||
|
$this->emulateSameSite = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers session save handler as a PHP session handler.
|
||||||
|
*
|
||||||
|
* To use internal PHP session save handlers, override this method using ini_set with
|
||||||
|
* session.save_handler and session.save_path e.g.
|
||||||
|
*
|
||||||
|
* ini_set('session.save_handler', 'files');
|
||||||
|
* ini_set('session.save_path', '/tmp');
|
||||||
|
*
|
||||||
|
* or pass in a \SessionHandler instance which configures session.save_handler in the
|
||||||
|
* constructor, for a template see NativeFileSessionHandler or use handlers in
|
||||||
|
* composer package drak/native-session
|
||||||
|
*
|
||||||
|
* @see https://php.net/session-set-save-handler
|
||||||
|
* @see https://php.net/sessionhandlerinterface
|
||||||
|
* @see https://php.net/sessionhandler
|
||||||
|
* @see https://github.com/zikula/NativeSession
|
||||||
|
*
|
||||||
|
* @param AbstractProxy|\SessionHandlerInterface|null $saveHandler
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setSaveHandler($saveHandler = null)
|
||||||
|
{
|
||||||
|
if (!$saveHandler instanceof AbstractProxy &&
|
||||||
|
!$saveHandler instanceof \SessionHandlerInterface &&
|
||||||
|
null !== $saveHandler) {
|
||||||
|
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap $saveHandler in proxy and prevent double wrapping of proxy
|
||||||
|
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
|
||||||
|
$saveHandler = new SessionHandlerProxy($saveHandler);
|
||||||
|
} elseif (!$saveHandler instanceof AbstractProxy) {
|
||||||
|
$saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
|
||||||
|
}
|
||||||
|
$this->saveHandler = $saveHandler;
|
||||||
|
|
||||||
|
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->saveHandler instanceof SessionHandlerProxy) {
|
||||||
|
session_set_save_handler($this->saveHandler, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the session with attributes.
|
||||||
|
*
|
||||||
|
* After starting the session, PHP retrieves the session from whatever handlers
|
||||||
|
* are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
|
||||||
|
* PHP takes the return value from the read() handler, unserializes it
|
||||||
|
* and populates $_SESSION with the result automatically.
|
||||||
|
*/
|
||||||
|
protected function loadSession(array &$session = null)
|
||||||
|
{
|
||||||
|
if (null === $session) {
|
||||||
|
$session = &$_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bags = array_merge($this->bags, [$this->metadataBag]);
|
||||||
|
|
||||||
|
foreach ($bags as $bag) {
|
||||||
|
$key = $bag->getStorageKey();
|
||||||
|
$session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : [];
|
||||||
|
$bag->initialize($session[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->started = true;
|
||||||
|
$this->closed = false;
|
||||||
|
}
|
||||||
|
}
|
64
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
vendored
Normal file
64
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows session to be started by PHP and managed by Symfony.
|
||||||
|
*
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class PhpBridgeSessionStorage extends NativeSessionStorage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param AbstractProxy|\SessionHandlerInterface|null $handler
|
||||||
|
*/
|
||||||
|
public function __construct($handler = null, MetadataBag $metaBag = null)
|
||||||
|
{
|
||||||
|
if (!\extension_loaded('session')) {
|
||||||
|
throw new \LogicException('PHP extension "session" is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setMetadataBag($metaBag);
|
||||||
|
$this->setSaveHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if ($this->started) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadSession();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
// clear out the bags and nothing else that may be set
|
||||||
|
// since the purpose of this driver is to share a handler
|
||||||
|
foreach ($this->bags as $bag) {
|
||||||
|
$bag->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconnect the bags to the session
|
||||||
|
$this->loadSession();
|
||||||
|
}
|
||||||
|
}
|
118
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
vendored
Normal file
118
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
abstract class AbstractProxy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Flag if handler wraps an internal PHP session handler (using \SessionHandler).
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $wrapper = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $saveHandlerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the session.save_handler name.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getSaveHandlerName()
|
||||||
|
{
|
||||||
|
return $this->saveHandlerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this proxy handler and instance of \SessionHandlerInterface.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSessionHandlerInterface()
|
||||||
|
{
|
||||||
|
return $this instanceof \SessionHandlerInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWrapper()
|
||||||
|
{
|
||||||
|
return $this->wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has a session started?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActive()
|
||||||
|
{
|
||||||
|
return \PHP_SESSION_ACTIVE === session_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the session ID.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return session_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session ID.
|
||||||
|
*
|
||||||
|
* @throws \LogicException
|
||||||
|
*/
|
||||||
|
public function setId(string $id)
|
||||||
|
{
|
||||||
|
if ($this->isActive()) {
|
||||||
|
throw new \LogicException('Cannot change the ID of an active session');
|
||||||
|
}
|
||||||
|
|
||||||
|
session_id($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the session name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return session_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session name.
|
||||||
|
*
|
||||||
|
* @throws \LogicException
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
if ($this->isActive()) {
|
||||||
|
throw new \LogicException('Cannot change the name of an active session');
|
||||||
|
}
|
||||||
|
|
||||||
|
session_name($name);
|
||||||
|
}
|
||||||
|
}
|
101
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
vendored
Normal file
101
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
vendored
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
protected $handler;
|
||||||
|
|
||||||
|
public function __construct(\SessionHandlerInterface $handler)
|
||||||
|
{
|
||||||
|
$this->handler = $handler;
|
||||||
|
$this->wrapper = ($handler instanceof \SessionHandler);
|
||||||
|
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \SessionHandlerInterface
|
||||||
|
*/
|
||||||
|
public function getHandler()
|
||||||
|
{
|
||||||
|
return $this->handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// \SessionHandlerInterface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function open($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
return (bool) $this->handler->open($savePath, $sessionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
return (bool) $this->handler->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($sessionId)
|
||||||
|
{
|
||||||
|
return (string) $this->handler->read($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function write($sessionId, $data)
|
||||||
|
{
|
||||||
|
return (bool) $this->handler->write($sessionId, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function destroy($sessionId)
|
||||||
|
{
|
||||||
|
return (bool) $this->handler->destroy($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function gc($maxlifetime)
|
||||||
|
{
|
||||||
|
return (bool) $this->handler->gc($maxlifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateId($sessionId)
|
||||||
|
{
|
||||||
|
return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateTimestamp($sessionId, $data)
|
||||||
|
{
|
||||||
|
return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data);
|
||||||
|
}
|
||||||
|
}
|
131
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
vendored
Normal file
131
lam/lib/3rdParty/composer/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StorageInterface.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Drak <drak@zikula.org>
|
||||||
|
*/
|
||||||
|
interface SessionStorageInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Starts the session.
|
||||||
|
*
|
||||||
|
* @return bool True if started
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException if something goes wrong starting the session
|
||||||
|
*/
|
||||||
|
public function start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the session is started.
|
||||||
|
*
|
||||||
|
* @return bool True if started, false otherwise
|
||||||
|
*/
|
||||||
|
public function isStarted();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the session ID.
|
||||||
|
*
|
||||||
|
* @return string The session ID or empty
|
||||||
|
*/
|
||||||
|
public function getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session ID.
|
||||||
|
*/
|
||||||
|
public function setId(string $id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the session name.
|
||||||
|
*
|
||||||
|
* @return string The session name
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session name.
|
||||||
|
*/
|
||||||
|
public function setName(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerates id that represents this storage.
|
||||||
|
*
|
||||||
|
* This method must invoke session_regenerate_id($destroy) unless
|
||||||
|
* this interface is used for a storage object designed for unit
|
||||||
|
* or functional testing where a real PHP session would interfere
|
||||||
|
* with testing.
|
||||||
|
*
|
||||||
|
* Note regenerate+destroy should not clear the session data in memory
|
||||||
|
* only delete the session data from persistent storage.
|
||||||
|
*
|
||||||
|
* Care: When regenerating the session ID no locking is involved in PHP's
|
||||||
|
* session design. See https://bugs.php.net/61470 for a discussion.
|
||||||
|
* So you must make sure the regenerated session is saved BEFORE sending the
|
||||||
|
* headers with the new ID. Symfony's HttpKernel offers a listener for this.
|
||||||
|
* See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.
|
||||||
|
* Otherwise session data could get lost again for concurrent requests with the
|
||||||
|
* new ID. One result could be that you get logged out after just logging in.
|
||||||
|
*
|
||||||
|
* @param bool $destroy Destroy session when regenerating?
|
||||||
|
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
|
||||||
|
* will leave the system settings unchanged, 0 sets the cookie
|
||||||
|
* to expire with browser session. Time is in seconds, and is
|
||||||
|
* not a Unix timestamp.
|
||||||
|
*
|
||||||
|
* @return bool True if session regenerated, false if error
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException If an error occurs while regenerating this storage
|
||||||
|
*/
|
||||||
|
public function regenerate(bool $destroy = false, int $lifetime = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the session to be saved and closed.
|
||||||
|
*
|
||||||
|
* This method must invoke session_write_close() unless this interface is
|
||||||
|
* used for a storage object design for unit or functional testing where
|
||||||
|
* a real PHP session would interfere with testing, in which case
|
||||||
|
* it should actually persist the session data if required.
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException if the session is saved without being started, or if the session
|
||||||
|
* is already closed
|
||||||
|
*/
|
||||||
|
public function save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all session data in memory.
|
||||||
|
*/
|
||||||
|
public function clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a SessionBagInterface by name.
|
||||||
|
*
|
||||||
|
* @return SessionBagInterface
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the bag does not exist
|
||||||
|
*/
|
||||||
|
public function getBag(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a SessionBagInterface for use.
|
||||||
|
*/
|
||||||
|
public function registerBag(SessionBagInterface $bag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MetadataBag
|
||||||
|
*/
|
||||||
|
public function getMetadataBag();
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StreamedResponse represents a streamed HTTP response.
|
||||||
|
*
|
||||||
|
* A StreamedResponse uses a callback for its content.
|
||||||
|
*
|
||||||
|
* The callback should use the standard PHP functions like echo
|
||||||
|
* to stream the response back to the client. The flush() function
|
||||||
|
* can also be used if needed.
|
||||||
|
*
|
||||||
|
* @see flush()
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class StreamedResponse extends Response
|
||||||
|
{
|
||||||
|
protected $callback;
|
||||||
|
protected $streamed;
|
||||||
|
private $headersSent;
|
||||||
|
|
||||||
|
public function __construct(callable $callback = null, int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
parent::__construct(null, $status, $headers);
|
||||||
|
|
||||||
|
if (null !== $callback) {
|
||||||
|
$this->setCallback($callback);
|
||||||
|
}
|
||||||
|
$this->streamed = false;
|
||||||
|
$this->headersSent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for chainability.
|
||||||
|
*
|
||||||
|
* @param callable|null $callback A valid PHP callback or null to set it later
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($callback = null, int $status = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
return new static($callback, $status, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the PHP callback associated with this Response.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setCallback(callable $callback)
|
||||||
|
{
|
||||||
|
$this->callback = $callback;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* This method only sends the headers once.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function sendHeaders()
|
||||||
|
{
|
||||||
|
if ($this->headersSent) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->headersSent = true;
|
||||||
|
|
||||||
|
return parent::sendHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* This method only sends the content once.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function sendContent()
|
||||||
|
{
|
||||||
|
if ($this->streamed) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->streamed = true;
|
||||||
|
|
||||||
|
if (null === $this->callback) {
|
||||||
|
throw new \LogicException('The Response callback must not be null.');
|
||||||
|
}
|
||||||
|
|
||||||
|
($this->callback)();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \LogicException when the content is not null
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setContent(?string $content)
|
||||||
|
{
|
||||||
|
if (null !== $content) {
|
||||||
|
throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->streamed = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
55
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
vendored
Normal file
55
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
final class RequestAttributeValueSame extends Constraint
|
||||||
|
{
|
||||||
|
private $name;
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $value)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($request): bool
|
||||||
|
{
|
||||||
|
return $this->value === $request->attributes->get($this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($request): string
|
||||||
|
{
|
||||||
|
return 'the Request '.$this->toString();
|
||||||
|
}
|
||||||
|
}
|
85
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
vendored
Normal file
85
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Cookie;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseCookieValueSame extends Constraint
|
||||||
|
{
|
||||||
|
private $name;
|
||||||
|
private $value;
|
||||||
|
private $path;
|
||||||
|
private $domain;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $value, string $path = '/', string $domain = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->value = $value;
|
||||||
|
$this->path = $path;
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
$str = sprintf('has cookie "%s"', $this->name);
|
||||||
|
if ('/' !== $this->path) {
|
||||||
|
$str .= sprintf(' with path "%s"', $this->path);
|
||||||
|
}
|
||||||
|
if ($this->domain) {
|
||||||
|
$str .= sprintf(' for domain "%s"', $this->domain);
|
||||||
|
}
|
||||||
|
$str .= sprintf(' with value "%s"', $this->value);
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
$cookie = $this->getCookie($response);
|
||||||
|
if (!$cookie) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->value === $cookie->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCookie(Response $response): ?Cookie
|
||||||
|
{
|
||||||
|
$cookies = $response->headers->getCookies();
|
||||||
|
|
||||||
|
$filteredCookies = array_filter($cookies, function (Cookie $cookie) {
|
||||||
|
return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain;
|
||||||
|
});
|
||||||
|
|
||||||
|
return reset($filteredCookies) ?: null;
|
||||||
|
}
|
||||||
|
}
|
77
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
vendored
Normal file
77
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Cookie;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseHasCookie extends Constraint
|
||||||
|
{
|
||||||
|
private $name;
|
||||||
|
private $path;
|
||||||
|
private $domain;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $path = '/', string $domain = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->path = $path;
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
$str = sprintf('has cookie "%s"', $this->name);
|
||||||
|
if ('/' !== $this->path) {
|
||||||
|
$str .= sprintf(' with path "%s"', $this->path);
|
||||||
|
}
|
||||||
|
if ($this->domain) {
|
||||||
|
$str .= sprintf(' for domain "%s"', $this->domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return null !== $this->getCookie($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCookie(Response $response): ?Cookie
|
||||||
|
{
|
||||||
|
$cookies = $response->headers->getCookies();
|
||||||
|
|
||||||
|
$filteredCookies = array_filter($cookies, function (Cookie $cookie) {
|
||||||
|
return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain;
|
||||||
|
});
|
||||||
|
|
||||||
|
return reset($filteredCookies) ?: null;
|
||||||
|
}
|
||||||
|
}
|
53
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
vendored
Normal file
53
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseHasHeader extends Constraint
|
||||||
|
{
|
||||||
|
private $headerName;
|
||||||
|
|
||||||
|
public function __construct(string $headerName)
|
||||||
|
{
|
||||||
|
$this->headerName = $headerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf('has header "%s"', $this->headerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return $response->headers->has($this->headerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
}
|
55
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
vendored
Normal file
55
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseHeaderSame extends Constraint
|
||||||
|
{
|
||||||
|
private $headerName;
|
||||||
|
private $expectedValue;
|
||||||
|
|
||||||
|
public function __construct(string $headerName, string $expectedValue)
|
||||||
|
{
|
||||||
|
$this->headerName = $headerName;
|
||||||
|
$this->expectedValue = $expectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return $this->expectedValue === $response->headers->get($this->headerName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
}
|
56
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
vendored
Normal file
56
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseIsRedirected extends Constraint
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return 'is redirected';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return $response->isRedirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function additionalFailureDescription($response): string
|
||||||
|
{
|
||||||
|
return (string) $response;
|
||||||
|
}
|
||||||
|
}
|
56
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
vendored
Normal file
56
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseIsSuccessful extends Constraint
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return 'is successful';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return $response->isSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function additionalFailureDescription($response): string
|
||||||
|
{
|
||||||
|
return (string) $response;
|
||||||
|
}
|
||||||
|
}
|
63
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
vendored
Normal file
63
lam/lib/3rdParty/composer/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation\Test\Constraint;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Constraint\Constraint;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
final class ResponseStatusCodeSame extends Constraint
|
||||||
|
{
|
||||||
|
private $statusCode;
|
||||||
|
|
||||||
|
public function __construct(int $statusCode)
|
||||||
|
{
|
||||||
|
$this->statusCode = $statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return 'status code is '.$this->statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function matches($response): bool
|
||||||
|
{
|
||||||
|
return $this->statusCode === $response->getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function failureDescription($response): string
|
||||||
|
{
|
||||||
|
return 'the Response '.$this->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function additionalFailureDescription($response): string
|
||||||
|
{
|
||||||
|
return (string) $response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpFoundation;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\RequestContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper service for manipulating URLs within and outside the request scope.
|
||||||
|
*
|
||||||
|
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
|
||||||
|
*/
|
||||||
|
final class UrlHelper
|
||||||
|
{
|
||||||
|
private $requestStack;
|
||||||
|
private $requestContext;
|
||||||
|
|
||||||
|
public function __construct(RequestStack $requestStack, RequestContext $requestContext = null)
|
||||||
|
{
|
||||||
|
$this->requestStack = $requestStack;
|
||||||
|
$this->requestContext = $requestContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAbsoluteUrl(string $path): string
|
||||||
|
{
|
||||||
|
if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $request = $this->requestStack->getMasterRequest()) {
|
||||||
|
return $this->getAbsoluteUrlFromContext($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('#' === $path[0]) {
|
||||||
|
$path = $request->getRequestUri().$path;
|
||||||
|
} elseif ('?' === $path[0]) {
|
||||||
|
$path = $request->getPathInfo().$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$path || '/' !== $path[0]) {
|
||||||
|
$prefix = $request->getPathInfo();
|
||||||
|
$last = \strlen($prefix) - 1;
|
||||||
|
if ($last !== $pos = strrpos($prefix, '/')) {
|
||||||
|
$prefix = substr($prefix, 0, $pos).'/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->getUriForPath($prefix.$path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->getSchemeAndHttpHost().$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelativePath(string $path): string
|
||||||
|
{
|
||||||
|
if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $request = $this->requestStack->getMasterRequest()) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->getRelativeUriForPath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAbsoluteUrlFromContext(string $path): string
|
||||||
|
{
|
||||||
|
if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheme = $this->requestContext->getScheme();
|
||||||
|
$port = '';
|
||||||
|
|
||||||
|
if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) {
|
||||||
|
$port = ':'.$this->requestContext->getHttpPort();
|
||||||
|
} elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) {
|
||||||
|
$port = ':'.$this->requestContext->getHttpsPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('#' === $path[0]) {
|
||||||
|
$queryString = $this->requestContext->getQueryString();
|
||||||
|
$path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path;
|
||||||
|
} elseif ('?' === $path[0]) {
|
||||||
|
$path = $this->requestContext->getPathInfo().$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('/' !== $path[0]) {
|
||||||
|
$path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scheme.'://'.$host.$port.$path;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "symfony/http-foundation",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Symfony HttpFoundation Component",
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5",
|
||||||
|
"symfony/mime": "^4.4|^5.0",
|
||||||
|
"symfony/polyfill-mbstring": "~1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"predis/predis": "~1.0",
|
||||||
|
"symfony/expression-language": "^4.4|^5.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
/Tests export-ignore
|
||||||
|
/phpunit.xml.dist export-ignore
|
||||||
|
/.gitignore export-ignore
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailValidator;
|
||||||
|
use Egulias\EmailValidator\Validation\RFCValidation;
|
||||||
|
use Symfony\Component\Mime\Encoder\IdnAddressEncoder;
|
||||||
|
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Mime\Exception\LogicException;
|
||||||
|
use Symfony\Component\Mime\Exception\RfcComplianceException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
final class Address
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A regex that matches a structure like 'Name <email@address.com>'.
|
||||||
|
* It matches anything between the first < and last > as email address.
|
||||||
|
* This allows to use a single string to construct an Address, which can be convenient to use in
|
||||||
|
* config, and allows to have more readable config.
|
||||||
|
* This does not try to cover all edge cases for address.
|
||||||
|
*/
|
||||||
|
private const FROM_STRING_PATTERN = '~(?<displayName>[^<]*)<(?<addrSpec>.*)>[^>]*~';
|
||||||
|
|
||||||
|
private static $validator;
|
||||||
|
private static $encoder;
|
||||||
|
|
||||||
|
private $address;
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
public function __construct(string $address, string $name = '')
|
||||||
|
{
|
||||||
|
if (!class_exists(EmailValidator::class)) {
|
||||||
|
throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === self::$validator) {
|
||||||
|
self::$validator = new EmailValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->address = trim($address);
|
||||||
|
$this->name = trim(str_replace(["\n", "\r"], '', $name));
|
||||||
|
|
||||||
|
if (!self::$validator->isValid($this->address, new RFCValidation())) {
|
||||||
|
throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAddress(): string
|
||||||
|
{
|
||||||
|
return $this->address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEncodedAddress(): string
|
||||||
|
{
|
||||||
|
if (null === self::$encoder) {
|
||||||
|
self::$encoder = new IdnAddressEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$encoder->encodeString($this->address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return ($n = $this->getName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string $address
|
||||||
|
*/
|
||||||
|
public static function create($address): self
|
||||||
|
{
|
||||||
|
if ($address instanceof self) {
|
||||||
|
return $address;
|
||||||
|
}
|
||||||
|
if (\is_string($address)) {
|
||||||
|
return new self($address);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s") given).', \is_object($address) ? \get_class($address) : \gettype($address)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param (Address|string)[] $addresses
|
||||||
|
*
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public static function createArray(array $addresses): array
|
||||||
|
{
|
||||||
|
$addrs = [];
|
||||||
|
foreach ($addresses as $address) {
|
||||||
|
$addrs[] = self::create($address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $addrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromString(string $string): self
|
||||||
|
{
|
||||||
|
if (false === strpos($string, '<')) {
|
||||||
|
return new self($string, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, static::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface BodyRendererInterface
|
||||||
|
{
|
||||||
|
public function render(Message $message): void;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
4.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* [BC BREAK] Removed `NamedAddress` (`Address` now supports a name)
|
||||||
|
* Added PHPUnit constraints
|
||||||
|
* Added `AbstractPart::asDebugString()`
|
||||||
|
* Added `Address::fromString()`
|
||||||
|
|
||||||
|
4.3.3
|
||||||
|
-----
|
||||||
|
|
||||||
|
* [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`.
|
||||||
|
|
||||||
|
4.3.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Introduced the component as experimental
|
|
@ -0,0 +1,221 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Xavier De Cock <xdecock@gmail.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class CharacterStream
|
||||||
|
{
|
||||||
|
/** Pre-computed for optimization */
|
||||||
|
private const UTF8_LENGTH_MAP = [
|
||||||
|
"\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
|
||||||
|
"\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
|
||||||
|
"\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
|
||||||
|
"\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
|
||||||
|
"\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
|
||||||
|
"\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
|
||||||
|
"\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
|
||||||
|
"\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
|
||||||
|
"\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
|
||||||
|
"\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
|
||||||
|
"\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
|
||||||
|
"\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
|
||||||
|
"\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
|
||||||
|
"\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
|
||||||
|
"\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
|
||||||
|
"\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
|
||||||
|
"\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
|
||||||
|
"\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
|
||||||
|
"\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
|
||||||
|
"\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
|
||||||
|
"\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
|
||||||
|
"\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
|
||||||
|
"\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
|
||||||
|
"\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
|
||||||
|
"\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
|
||||||
|
"\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
|
||||||
|
"\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
|
||||||
|
"\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
|
||||||
|
"\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
|
||||||
|
"\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
|
||||||
|
"\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
|
||||||
|
"\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
private $data = '';
|
||||||
|
private $dataSize = 0;
|
||||||
|
private $map = [];
|
||||||
|
private $charCount = 0;
|
||||||
|
private $currentPos = 0;
|
||||||
|
private $fixedWidth = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|string $input
|
||||||
|
*/
|
||||||
|
public function __construct($input, ?string $charset = 'utf-8')
|
||||||
|
{
|
||||||
|
$charset = strtolower(trim($charset)) ?: 'utf-8';
|
||||||
|
if ('utf-8' === $charset || 'utf8' === $charset) {
|
||||||
|
$this->fixedWidth = 0;
|
||||||
|
$this->map = ['p' => [], 'i' => []];
|
||||||
|
} else {
|
||||||
|
switch ($charset) {
|
||||||
|
// 16 bits
|
||||||
|
case 'ucs2':
|
||||||
|
case 'ucs-2':
|
||||||
|
case 'utf16':
|
||||||
|
case 'utf-16':
|
||||||
|
$this->fixedWidth = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 32 bits
|
||||||
|
case 'ucs4':
|
||||||
|
case 'ucs-4':
|
||||||
|
case 'utf32':
|
||||||
|
case 'utf-32':
|
||||||
|
$this->fixedWidth = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
|
||||||
|
// koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii
|
||||||
|
// and fallback
|
||||||
|
default:
|
||||||
|
$this->fixedWidth = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (\is_resource($input)) {
|
||||||
|
$blocks = 512;
|
||||||
|
if (stream_get_meta_data($input)['seekable'] ?? false) {
|
||||||
|
rewind($input);
|
||||||
|
}
|
||||||
|
while (false !== $read = fread($input, $blocks)) {
|
||||||
|
$this->write($read);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->write($input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(int $length): ?string
|
||||||
|
{
|
||||||
|
if ($this->currentPos >= $this->charCount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length;
|
||||||
|
if ($this->fixedWidth > 0) {
|
||||||
|
$len = $length * $this->fixedWidth;
|
||||||
|
$ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len);
|
||||||
|
$this->currentPos += $length;
|
||||||
|
} else {
|
||||||
|
$end = $this->currentPos + $length;
|
||||||
|
$end = $end > $this->charCount ? $this->charCount : $end;
|
||||||
|
$ret = '';
|
||||||
|
$start = 0;
|
||||||
|
if ($this->currentPos > 0) {
|
||||||
|
$start = $this->map['p'][$this->currentPos - 1];
|
||||||
|
}
|
||||||
|
$to = $start;
|
||||||
|
for (; $this->currentPos < $end; ++$this->currentPos) {
|
||||||
|
if (isset($this->map['i'][$this->currentPos])) {
|
||||||
|
$ret .= substr($this->data, $start, $to - $start).'?';
|
||||||
|
$start = $this->map['p'][$this->currentPos];
|
||||||
|
} else {
|
||||||
|
$to = $this->map['p'][$this->currentPos];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ret .= substr($this->data, $start, $to - $start);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readBytes(int $length): ?array
|
||||||
|
{
|
||||||
|
if (null !== $read = $this->read($length)) {
|
||||||
|
return array_map('ord', str_split($read, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPointer(int $charOffset): void
|
||||||
|
{
|
||||||
|
if ($this->charCount < $charOffset) {
|
||||||
|
$charOffset = $this->charCount;
|
||||||
|
}
|
||||||
|
$this->currentPos = $charOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write(string $chars): void
|
||||||
|
{
|
||||||
|
$ignored = '';
|
||||||
|
$this->data .= $chars;
|
||||||
|
if ($this->fixedWidth > 0) {
|
||||||
|
$strlen = \strlen($chars);
|
||||||
|
$ignoredL = $strlen % $this->fixedWidth;
|
||||||
|
$ignored = $ignoredL ? substr($chars, -$ignoredL) : '';
|
||||||
|
$this->charCount += ($strlen - $ignoredL) / $this->fixedWidth;
|
||||||
|
} else {
|
||||||
|
$this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored);
|
||||||
|
}
|
||||||
|
$this->dataSize = \strlen($this->data) - \strlen($ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int
|
||||||
|
{
|
||||||
|
$strlen = \strlen($string);
|
||||||
|
$charPos = \count($this->map['p']);
|
||||||
|
$foundChars = 0;
|
||||||
|
$invalid = false;
|
||||||
|
for ($i = 0; $i < $strlen; ++$i) {
|
||||||
|
$char = $string[$i];
|
||||||
|
$size = self::UTF8_LENGTH_MAP[$char];
|
||||||
|
if (0 == $size) {
|
||||||
|
/* char is invalid, we must wait for a resync */
|
||||||
|
$invalid = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($invalid) {
|
||||||
|
/* We mark the chars as invalid and start a new char */
|
||||||
|
$this->map['p'][$charPos + $foundChars] = $startOffset + $i;
|
||||||
|
$this->map['i'][$charPos + $foundChars] = true;
|
||||||
|
++$foundChars;
|
||||||
|
$invalid = false;
|
||||||
|
}
|
||||||
|
if (($i + $size) > $strlen) {
|
||||||
|
$ignoredChars = substr($string, $i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for ($j = 1; $j < $size; ++$j) {
|
||||||
|
$char = $string[$i + $j];
|
||||||
|
if ($char > "\x7F" && $char < "\xC0") {
|
||||||
|
// Valid - continue parsing
|
||||||
|
} else {
|
||||||
|
/* char is invalid, we must wait for a resync */
|
||||||
|
$invalid = true;
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Ok we got a complete char here */
|
||||||
|
$this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size;
|
||||||
|
$i += $j - 1;
|
||||||
|
++$foundChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $foundChars;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\Crypto;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Mime\Part\SMimePart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class SMime
|
||||||
|
{
|
||||||
|
protected function normalizeFilePath(string $path): string
|
||||||
|
{
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new RuntimeException(sprintf('File does not exist: %s.', $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'file://'.str_replace('\\', '/', realpath($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function iteratorToFile(iterable $iterator, $stream): void
|
||||||
|
{
|
||||||
|
foreach ($iterator as $chunk) {
|
||||||
|
fwrite($stream, $chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart
|
||||||
|
{
|
||||||
|
rewind($stream);
|
||||||
|
|
||||||
|
$headers = '';
|
||||||
|
|
||||||
|
while (!feof($stream)) {
|
||||||
|
$buffer = fread($stream, 78);
|
||||||
|
$headers .= $buffer;
|
||||||
|
|
||||||
|
// Detect ending of header list
|
||||||
|
if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) {
|
||||||
|
$headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd)));
|
||||||
|
|
||||||
|
fseek($stream, $headersPosEnd + \strlen($headerBodySeparator));
|
||||||
|
|
||||||
|
return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type']));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getStreamIterator($stream): iterable
|
||||||
|
{
|
||||||
|
while (!feof($stream)) {
|
||||||
|
yield fread($stream, 16372);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMessageHeaders(string $headerData): array
|
||||||
|
{
|
||||||
|
$headers = [];
|
||||||
|
$headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData)));
|
||||||
|
$currentHeaderName = '';
|
||||||
|
|
||||||
|
// Transform header lines into an associative array
|
||||||
|
foreach ($headerLines as $headerLine) {
|
||||||
|
// Empty lines between headers indicate a new mime-entity
|
||||||
|
if ('' === $headerLine) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle headers that span multiple lines
|
||||||
|
if (false === strpos($headerLine, ':')) {
|
||||||
|
$headers[$currentHeaderName] .= ' '.trim($headerLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = explode(':', $headerLine, 2);
|
||||||
|
$currentHeaderName = strtolower($header[0]);
|
||||||
|
$headers[$currentHeaderName] = trim($header[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getParametersFromHeader(string $header): array
|
||||||
|
{
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
preg_match_all('/(?P<name>[a-z-0-9]+)=(?P<value>"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches);
|
||||||
|
|
||||||
|
foreach ($matches['value'] as $pos => $paramValue) {
|
||||||
|
$params[$matches['name'][$pos]] = trim($paramValue, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\Crypto;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Mime\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||||
|
*/
|
||||||
|
final class SMimeEncrypter extends SMime
|
||||||
|
{
|
||||||
|
private $certs;
|
||||||
|
private $cipher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s)
|
||||||
|
* @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php
|
||||||
|
*/
|
||||||
|
public function __construct($certificate, int $cipher = null)
|
||||||
|
{
|
||||||
|
if (!\extension_loaded('openssl')) {
|
||||||
|
throw new \LogicException('PHP extension "openssl" is required to use SMime.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($certificate)) {
|
||||||
|
$this->certs = array_map([$this, 'normalizeFilePath'], $certificate);
|
||||||
|
} else {
|
||||||
|
$this->certs = $this->normalizeFilePath($certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cipher = $cipher ?? OPENSSL_CIPHER_AES_256_CBC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function encrypt(Message $message): Message
|
||||||
|
{
|
||||||
|
$bufferFile = tmpfile();
|
||||||
|
$outputFile = tmpfile();
|
||||||
|
|
||||||
|
$this->iteratorToFile($message->toIterable(), $bufferFile);
|
||||||
|
|
||||||
|
if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) {
|
||||||
|
throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime');
|
||||||
|
$mimePart->getHeaders()
|
||||||
|
->addTextHeader('Content-Transfer-Encoding', 'base64')
|
||||||
|
->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m'])
|
||||||
|
;
|
||||||
|
|
||||||
|
return new Message($message->getHeaders(), $mimePart);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\Crypto;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Mime\Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||||
|
*/
|
||||||
|
final class SMimeSigner extends SMime
|
||||||
|
{
|
||||||
|
private $signCertificate;
|
||||||
|
private $signPrivateKey;
|
||||||
|
private $signOptions;
|
||||||
|
private $extraCerts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $privateKeyPassphrase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $certificate The path of the file containing the signing certificate (in PEM format)
|
||||||
|
* @param string $privateKey The path of the file containing the private key (in PEM format)
|
||||||
|
* @param string|null $privateKeyPassphrase A passphrase of the private key (if any)
|
||||||
|
* @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate
|
||||||
|
* @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php)
|
||||||
|
*/
|
||||||
|
public function __construct(string $certificate, string $privateKey, string $privateKeyPassphrase = null, string $extraCerts = null, int $signOptions = null)
|
||||||
|
{
|
||||||
|
if (!\extension_loaded('openssl')) {
|
||||||
|
throw new \LogicException('PHP extension "openssl" is required to use SMime.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->signCertificate = $this->normalizeFilePath($certificate);
|
||||||
|
|
||||||
|
if (null !== $privateKeyPassphrase) {
|
||||||
|
$this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase];
|
||||||
|
} else {
|
||||||
|
$this->signPrivateKey = $this->normalizeFilePath($privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->signOptions = $signOptions ?? PKCS7_DETACHED;
|
||||||
|
$this->extraCerts = $extraCerts ? realpath($extraCerts) : null;
|
||||||
|
$this->privateKeyPassphrase = $privateKeyPassphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sign(Message $message): Message
|
||||||
|
{
|
||||||
|
$bufferFile = tmpfile();
|
||||||
|
$outputFile = tmpfile();
|
||||||
|
|
||||||
|
$this->iteratorToFile($message->getBody()->toIterable(), $bufferFile);
|
||||||
|
|
||||||
|
if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) {
|
||||||
|
throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed'));
|
||||||
|
}
|
||||||
|
}
|
46
lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
vendored
Normal file
46
lam/lib/3rdParty/composer/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers custom mime types guessers.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class AddMimeTypeGuesserPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
private $mimeTypesService;
|
||||||
|
private $mimeTypeGuesserTag;
|
||||||
|
|
||||||
|
public function __construct(string $mimeTypesService = 'mime_types', string $mimeTypeGuesserTag = 'mime.mime_type_guesser')
|
||||||
|
{
|
||||||
|
$this->mimeTypesService = $mimeTypesService;
|
||||||
|
$this->mimeTypeGuesserTag = $mimeTypeGuesserTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if ($container->has($this->mimeTypesService)) {
|
||||||
|
$definition = $container->findDefinition($this->mimeTypesService);
|
||||||
|
foreach ($container->findTaggedServiceIds($this->mimeTypeGuesserTag, true) as $id => $attributes) {
|
||||||
|
$definition->addMethodCall('registerGuesser', [new Reference($id)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,599 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\LogicException;
|
||||||
|
use Symfony\Component\Mime\Part\AbstractPart;
|
||||||
|
use Symfony\Component\Mime\Part\DataPart;
|
||||||
|
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
|
||||||
|
use Symfony\Component\Mime\Part\Multipart\MixedPart;
|
||||||
|
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||||
|
use Symfony\Component\Mime\Part\TextPart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Email extends Message
|
||||||
|
{
|
||||||
|
const PRIORITY_HIGHEST = 1;
|
||||||
|
const PRIORITY_HIGH = 2;
|
||||||
|
const PRIORITY_NORMAL = 3;
|
||||||
|
const PRIORITY_LOW = 4;
|
||||||
|
const PRIORITY_LOWEST = 5;
|
||||||
|
|
||||||
|
private const PRIORITY_MAP = [
|
||||||
|
self::PRIORITY_HIGHEST => 'Highest',
|
||||||
|
self::PRIORITY_HIGH => 'High',
|
||||||
|
self::PRIORITY_NORMAL => 'Normal',
|
||||||
|
self::PRIORITY_LOW => 'Low',
|
||||||
|
self::PRIORITY_LOWEST => 'Lowest',
|
||||||
|
];
|
||||||
|
|
||||||
|
private $text;
|
||||||
|
private $textCharset;
|
||||||
|
private $html;
|
||||||
|
private $htmlCharset;
|
||||||
|
private $attachments = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function subject(string $subject)
|
||||||
|
{
|
||||||
|
return $this->setHeaderBody('Text', 'Subject', $subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubject(): ?string
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Subject');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function date(\DateTimeInterface $dateTime)
|
||||||
|
{
|
||||||
|
return $this->setHeaderBody('Date', 'Date', $dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDate(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Date');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string $address
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function returnPath($address)
|
||||||
|
{
|
||||||
|
return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReturnPath(): ?Address
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Return-Path');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string $address
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function sender($address)
|
||||||
|
{
|
||||||
|
return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSender(): ?Address
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Sender');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addFrom(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->addListAddressHeaderBody('From', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function from(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->setListAddressHeaderBody('From', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public function getFrom(): array
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('From') ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addReplyTo(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->addListAddressHeaderBody('Reply-To', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function replyTo(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->setListAddressHeaderBody('Reply-To', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public function getReplyTo(): array
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Reply-To') ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addTo(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->addListAddressHeaderBody('To', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function to(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->setListAddressHeaderBody('To', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public function getTo(): array
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('To') ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addCc(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->addListAddressHeaderBody('Cc', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function cc(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->setListAddressHeaderBody('Cc', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public function getCc(): array
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Cc') ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addBcc(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->addListAddressHeaderBody('Bcc', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Address|string ...$addresses
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function bcc(...$addresses)
|
||||||
|
{
|
||||||
|
return $this->setListAddressHeaderBody('Bcc', $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Address[]
|
||||||
|
*/
|
||||||
|
public function getBcc(): array
|
||||||
|
{
|
||||||
|
return $this->getHeaders()->getHeaderBody('Bcc') ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the priority of this message.
|
||||||
|
*
|
||||||
|
* The value is an integer where 1 is the highest priority and 5 is the lowest.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function priority(int $priority)
|
||||||
|
{
|
||||||
|
if ($priority > 5) {
|
||||||
|
$priority = 5;
|
||||||
|
} elseif ($priority < 1) {
|
||||||
|
$priority = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the priority of this message.
|
||||||
|
*
|
||||||
|
* The returned value is an integer where 1 is the highest priority and 5
|
||||||
|
* is the lowest.
|
||||||
|
*/
|
||||||
|
public function getPriority(): int
|
||||||
|
{
|
||||||
|
list($priority) = sscanf($this->getHeaders()->getHeaderBody('X-Priority'), '%[1-5]');
|
||||||
|
|
||||||
|
return $priority ?? 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|string $body
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function text($body, string $charset = 'utf-8')
|
||||||
|
{
|
||||||
|
$this->text = $body;
|
||||||
|
$this->textCharset = $charset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|string|null
|
||||||
|
*/
|
||||||
|
public function getTextBody()
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTextCharset(): ?string
|
||||||
|
{
|
||||||
|
return $this->textCharset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|string|null $body
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function html($body, string $charset = 'utf-8')
|
||||||
|
{
|
||||||
|
$this->html = $body;
|
||||||
|
$this->htmlCharset = $charset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|string|null
|
||||||
|
*/
|
||||||
|
public function getHtmlBody()
|
||||||
|
{
|
||||||
|
return $this->html;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHtmlCharset(): ?string
|
||||||
|
{
|
||||||
|
return $this->htmlCharset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|string $body
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function attach($body, string $name = null, string $contentType = null)
|
||||||
|
{
|
||||||
|
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function attachFromPath(string $path, string $name = null, string $contentType = null)
|
||||||
|
{
|
||||||
|
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|string $body
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function embed($body, string $name = null, string $contentType = null)
|
||||||
|
{
|
||||||
|
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function embedFromPath(string $path, string $name = null, string $contentType = null)
|
||||||
|
{
|
||||||
|
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function attachPart(DataPart $part)
|
||||||
|
{
|
||||||
|
$this->attachments[] = ['part' => $part];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DataPart[]
|
||||||
|
*/
|
||||||
|
public function getAttachments(): array
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach ($this->attachments as $attachment) {
|
||||||
|
$parts[] = $this->createDataPart($attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody(): AbstractPart
|
||||||
|
{
|
||||||
|
if (null !== $body = parent::getBody()) {
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->generateBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensureValidity()
|
||||||
|
{
|
||||||
|
if (null === $this->text && null === $this->html && !$this->attachments) {
|
||||||
|
throw new LogicException('A message must have a text or an HTML part or attachments.');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::ensureValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an AbstractPart based on the raw body of a message.
|
||||||
|
*
|
||||||
|
* The most "complex" part generated by this method is when there is text and HTML bodies
|
||||||
|
* with related images for the HTML part and some attachments:
|
||||||
|
*
|
||||||
|
* multipart/mixed
|
||||||
|
* |
|
||||||
|
* |------------> multipart/related
|
||||||
|
* | |
|
||||||
|
* | |------------> multipart/alternative
|
||||||
|
* | | |
|
||||||
|
* | | ------------> text/plain (with content)
|
||||||
|
* | | |
|
||||||
|
* | | ------------> text/html (with content)
|
||||||
|
* | |
|
||||||
|
* | ------------> image/png (with content)
|
||||||
|
* |
|
||||||
|
* ------------> application/pdf (with content)
|
||||||
|
*/
|
||||||
|
private function generateBody(): AbstractPart
|
||||||
|
{
|
||||||
|
$this->ensureValidity();
|
||||||
|
|
||||||
|
[$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts();
|
||||||
|
|
||||||
|
$part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
|
||||||
|
if (null !== $htmlPart) {
|
||||||
|
if (null !== $part) {
|
||||||
|
$part = new AlternativePart($part, $htmlPart);
|
||||||
|
} else {
|
||||||
|
$part = $htmlPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inlineParts) {
|
||||||
|
$part = new RelatedPart($part, ...$inlineParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($attachmentParts) {
|
||||||
|
if ($part) {
|
||||||
|
$part = new MixedPart($part, ...$attachmentParts);
|
||||||
|
} else {
|
||||||
|
$part = new MixedPart(...$attachmentParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $part;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareParts(): ?array
|
||||||
|
{
|
||||||
|
$names = [];
|
||||||
|
$htmlPart = null;
|
||||||
|
$html = $this->html;
|
||||||
|
if (null !== $this->html) {
|
||||||
|
if (\is_resource($html)) {
|
||||||
|
if (stream_get_meta_data($html)['seekable'] ?? false) {
|
||||||
|
rewind($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = stream_get_contents($html);
|
||||||
|
}
|
||||||
|
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
|
||||||
|
preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names);
|
||||||
|
$names = array_filter(array_unique(array_merge($names[2], $names[3])));
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachmentParts = $inlineParts = [];
|
||||||
|
foreach ($this->attachments as $attachment) {
|
||||||
|
foreach ($names as $name) {
|
||||||
|
if (isset($attachment['part'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($name !== $attachment['name']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($inlineParts[$name])) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$attachment['inline'] = true;
|
||||||
|
$inlineParts[$name] = $part = $this->createDataPart($attachment);
|
||||||
|
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
$attachmentParts[] = $this->createDataPart($attachment);
|
||||||
|
}
|
||||||
|
if (null !== $htmlPart) {
|
||||||
|
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$htmlPart, $attachmentParts, array_values($inlineParts)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createDataPart(array $attachment): DataPart
|
||||||
|
{
|
||||||
|
if (isset($attachment['part'])) {
|
||||||
|
return $attachment['part'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attachment['body'])) {
|
||||||
|
$part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
|
||||||
|
} else {
|
||||||
|
$part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
|
||||||
|
}
|
||||||
|
if ($attachment['inline']) {
|
||||||
|
$part->asInline();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $part;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
private function setHeaderBody(string $type, string $name, $body): object
|
||||||
|
{
|
||||||
|
$this->getHeaders()->setHeaderBody($type, $name, $body);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addListAddressHeaderBody(string $name, array $addresses)
|
||||||
|
{
|
||||||
|
if (!$header = $this->getHeaders()->get($name)) {
|
||||||
|
return $this->setListAddressHeaderBody($name, $addresses);
|
||||||
|
}
|
||||||
|
$header->addAddresses(Address::createArray($addresses));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setListAddressHeaderBody(string $name, array $addresses)
|
||||||
|
{
|
||||||
|
$addresses = Address::createArray($addresses);
|
||||||
|
$headers = $this->getHeaders();
|
||||||
|
if ($header = $headers->get($name)) {
|
||||||
|
$header->setAddresses($addresses);
|
||||||
|
} else {
|
||||||
|
$headers->addMailboxListHeader($name, $addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function __serialize(): array
|
||||||
|
{
|
||||||
|
if (\is_resource($this->text)) {
|
||||||
|
if (stream_get_meta_data($this->text)['seekable'] ?? false) {
|
||||||
|
rewind($this->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->text = stream_get_contents($this->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_resource($this->html)) {
|
||||||
|
if (stream_get_meta_data($this->html)['seekable'] ?? false) {
|
||||||
|
rewind($this->html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->html = stream_get_contents($this->html);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->attachments as $i => $attachment) {
|
||||||
|
if (isset($attachment['body']) && \is_resource($attachment['body'])) {
|
||||||
|
if (stream_get_meta_data($attachment['body'])['seekable'] ?? false) {
|
||||||
|
rewind($attachment['body']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->attachments[$i]['body'] = stream_get_contents($attachment['body']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function __unserialize(array $data): void
|
||||||
|
{
|
||||||
|
[$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
|
||||||
|
|
||||||
|
parent::__unserialize($parentData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\Encoder;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\AddressEncoderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Schmidt
|
||||||
|
*/
|
||||||
|
interface AddressEncoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Encodes an email address.
|
||||||
|
*
|
||||||
|
* @throws AddressEncoderException if the email cannot be represented in
|
||||||
|
* the encoding implemented by this class
|
||||||
|
*/
|
||||||
|
public function encodeString(string $address): string;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Mime\Encoder;
|
||||||
|
|
||||||
|
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface
|
||||||
|
{
|
||||||
|
public function encodeByteStream($stream, int $maxLineLength = 0): iterable
|
||||||
|
{
|
||||||
|
if (!\is_resource($stream)) {
|
||||||
|
throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [
|
||||||
|
'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength,
|
||||||
|
'line-break-chars' => "\r\n",
|
||||||
|
]);
|
||||||
|
if (!\is_resource($filter)) {
|
||||||
|
throw new RuntimeException('Unable to set the base64 content encoder to the filter.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream_get_meta_data($stream)['seekable'] ?? false) {
|
||||||
|
rewind($stream);
|
||||||
|
}
|
||||||
|
while (!feof($stream)) {
|
||||||
|
yield fread($stream, 8192);
|
||||||
|
}
|
||||||
|
stream_filter_remove($filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'base64';
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue