$deferred
+ * @param bool $toggle
+ * @param int $method
+ * @return void
+ */
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ $error = null;
+ \set_error_handler(function ($_, $errstr) use (&$error) {
+ $error = \str_replace(array("\r", "\n"), ' ', $errstr);
+
+ // remove useless function name from error message
+ if (($pos = \strpos($error, "): ")) !== false) {
+ $error = \substr($error, $pos + 3);
+ }
+ });
+
+ $result = \stream_socket_enable_crypto($socket, $toggle, $method);
+
+ \restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve(null);
+ } else if (false === $result) {
+ // overwrite callback arguments for PHP7+ only, so they do not show
+ // up in the Exception trace and do not cause a possible cyclic reference.
+ $d = $deferred;
+ $deferred = null;
+
+ if (\feof($socket) || $error === null) {
+ // EOF or failed without error => connection closed during handshake
+ $d->reject(new \UnexpectedValueException(
+ 'Connection lost during TLS handshake (ECONNRESET)',
+ \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104
+ ));
+ } else {
+ // handshake failed with error message
+ $d->reject(new \UnexpectedValueException(
+ $error
+ ));
+ }
+ } else {
+ // need more data, will retry
+ }
+ }
+}
diff --git a/www/libs/vendor/react/socket/src/TcpConnector.php b/www/libs/vendor/react/socket/src/TcpConnector.php
new file mode 100644
index 00000000..9d2599e8
--- /dev/null
+++ b/www/libs/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,173 @@
+loop = $loop ?: Loop::get();
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = \parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ ));
+ }
+
+ $ip = \trim($parts['host'], '[]');
+ if (@\inet_pton($ip) === false) {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ ));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ \parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ // @codeCoverageIgnoreStart
+ if (\PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $stream = @\stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT,
+ \stream_context_create($context)
+ );
+
+ if (false === $stream) {
+ return Promise\reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ }
+
+ // wait for connection
+ $loop = $this->loop;
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === \stream_socket_get_name($stream, true)) {
+ // If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
+ // @codeCoverageIgnoreStart
+ if (\function_exists('socket_import_stream')) {
+ // actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
+ $socket = \socket_import_stream($stream);
+ $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
+ $errstr = \socket_strerror($errno);
+ } elseif (\PHP_OS === 'Linux') {
+ // Linux reports socket errno and errstr again when trying to write to the dead socket.
+ // Suppress error reporting to get error message below and close dead socket before rejecting.
+ // This is only known to work on Linux, Mac and Windows are known to not support this.
+ $errno = 0;
+ $errstr = '';
+ \set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
+ // Match errstr from PHP's warning message.
+ // fwrite(): send of 1 bytes failed with errno=111 Connection refused
+ \preg_match('/errno=(\d+) (.+)/', $error, $m);
+ $errno = isset($m[1]) ? (int) $m[1] : 0;
+ $errstr = isset($m[2]) ? $m[2] : $error;
+ });
+
+ \fwrite($stream, \PHP_EOL);
+
+ \restore_error_handler();
+ } else {
+ // Not on Linux and ext-sockets not available? Too bad.
+ $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
+ $errstr = 'Connection refused?';
+ }
+ // @codeCoverageIgnoreEnd
+
+ \fclose($stream);
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream, $uri) {
+ $loop->removeWriteStream($stream);
+ \fclose($stream);
+
+ // @codeCoverageIgnoreStart
+ // legacy PHP 5.3 sometimes requires a second close call (see tests)
+ if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) {
+ \fclose($stream);
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' cancelled during TCP/IP handshake (ECONNABORTED)',
+ \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
+ );
+ });
+ }
+}
diff --git a/www/libs/vendor/react/socket/src/TcpServer.php b/www/libs/vendor/react/socket/src/TcpServer.php
new file mode 100644
index 00000000..01b2b46d
--- /dev/null
+++ b/www/libs/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,262 @@
+on('connection', function (React\Socket\ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(8080);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer(0);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('192.168.0.1:8080');
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('[::1]:8080');
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new React\Socket\TcpServer('127.0.0.1');
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new React\Socket\TcpServer(8080);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new React\Socket\TcpServer(8080);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this object. You can use a `null` value
+ * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+ * This value SHOULD NOT be given unless you're sure you want to explicitly use a
+ * given event loop instance.
+ *
+ * Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new React\Socket\TcpServer('[::1]:8080', null, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](https://www.php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ * The `backlog` context option defaults to `511` unless given explicitly.
+ *
+ * @param string|int $uri
+ * @param ?LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, $loop = null, array $context = array())
+ {
+ if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
+ throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
+ }
+
+ $this->loop = $loop ?: Loop::get();
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (\strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (\substr($uri, -2) === ':0') {
+ $parts = \parse_url(\substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = \parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new \InvalidArgumentException(
+ 'Invalid URI "' . $uri . '" given (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ );
+ }
+
+ if (@\inet_pton(\trim($parts['host'], '[]')) === false) {
+ throw new \InvalidArgumentException(
+ 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ );
+ }
+
+ $this->master = @\stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
+ \stream_context_create(array('socket' => $context + array('backlog' => 511)))
+ );
+ if (false === $this->master) {
+ if ($errno === 0) {
+ // PHP does not seem to report errno, so match errno from errstr
+ // @link https://3v4l.org/3qOBl
+ $errno = SocketServer::errno($errstr);
+ }
+
+ throw new \RuntimeException(
+ 'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ );
+ }
+ \stream_set_blocking($this->master, false);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!\is_resource($this->master)) {
+ return null;
+ }
+
+ $address = \stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = \strrpos($address, ':');
+ if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
+ $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !\is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ try {
+ $newSocket = SocketServer::accept($master);
+ } catch (\RuntimeException $e) {
+ $that->emit('error', array($e));
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!\is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ \fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/www/libs/vendor/react/socket/src/TimeoutConnector.php b/www/libs/vendor/react/socket/src/TimeoutConnector.php
new file mode 100644
index 00000000..9ef252f7
--- /dev/null
+++ b/www/libs/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,79 @@
+connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop ?: Loop::get();
+ }
+
+ public function connect($uri)
+ {
+ $promise = $this->connector->connect($uri);
+
+ $loop = $this->loop;
+ $time = $this->timeout;
+ return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) {
+ $timer = null;
+ $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the pending promise
+ $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) {
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)',
+ \defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110
+ ));
+
+ // Cancel pending connection to clean up any underlying resources and references.
+ // Avoid garbage references in call stack by passing pending promise by reference.
+ assert(\method_exists($promise, 'cancel'));
+ $promise->cancel();
+ $promise = null;
+ });
+ }, function () use (&$promise) {
+ // Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above.
+ // Avoid garbage references in call stack by passing pending promise by reference.
+ assert(\method_exists($promise, 'cancel'));
+ $promise->cancel();
+ $promise = null;
+ });
+ }
+}
diff --git a/www/libs/vendor/react/socket/src/UnixConnector.php b/www/libs/vendor/react/socket/src/UnixConnector.php
new file mode 100644
index 00000000..95f932cb
--- /dev/null
+++ b/www/libs/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,58 @@
+loop = $loop ?: Loop::get();
+ }
+
+ public function connect($path)
+ {
+ if (\strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (\substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new \InvalidArgumentException(
+ 'Given URI "' . $path . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ ));
+ }
+
+ $resource = @\stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new \RuntimeException(
+ 'Unable to connect to unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ ));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/www/libs/vendor/react/socket/src/UnixServer.php b/www/libs/vendor/react/socket/src/UnixServer.php
new file mode 100644
index 00000000..27b014d1
--- /dev/null
+++ b/www/libs/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,162 @@
+loop = $loop ?: Loop::get();
+
+ if (\strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (\substr($path, 0, 7) !== 'unix://') {
+ throw new \InvalidArgumentException(
+ 'Given URI "' . $path . '" is invalid (EINVAL)',
+ \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
+ );
+ }
+
+ $errno = 0;
+ $errstr = '';
+ \set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
+ // PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
+ // This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
+ // Parse PHP warning message containing unknown error, HHVM reports proper info at least.
+ if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) {
+ $errstr = isset($match[3]) ? $match['3'] : $match[1];
+ $errno = isset($match[2]) ? (int)$match[2] : 0;
+ }
+ });
+
+ $this->master = \stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
+ \stream_context_create(array('socket' => $context))
+ );
+
+ \restore_error_handler();
+
+ if (false === $this->master) {
+ throw new \RuntimeException(
+ 'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
+ $errno
+ );
+ }
+ \stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!\is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . \stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ try {
+ $newSocket = SocketServer::accept($master);
+ } catch (\RuntimeException $e) {
+ $that->emit('error', array($e));
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!\is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ \fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/www/libs/vendor/react/stream/CHANGELOG.md b/www/libs/vendor/react/stream/CHANGELOG.md
new file mode 100644
index 00000000..639db658
--- /dev/null
+++ b/www/libs/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,460 @@
+# Changelog
+
+## 1.4.0 (2024-06-11)
+
+* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
+ (#179 by @clue)
+
+* Feature: Full PHP 8.3 compatibility.
+ (#172 by @clue)
+
+* Fix: Fix `drain` event of `ThroughStream` to handle potential race condition.
+ (#171 by @clue)
+
+## 1.3.0 (2023-06-16)
+
+* Feature: Full PHP 8.1 and PHP 8.2 compatibility.
+ (#160 by @SimonFrings, #165 by @clue and #169 by @WyriHaximus)
+
+* Feature: Avoid unneeded syscall when creating non-blocking `DuplexResourceStream`.
+ (#164 by @clue)
+
+* Minor documentation improvements.
+ (#161 by @mrsimonbennett, #162 by @SimonFrings and #166 by @nhedger)
+
+* Improve test suite and project setup and report failed assertions.
+ (#168 and #170 by @clue and #163 by @SimonFrings)
+
+## 1.2.0 (2021-07-11)
+
+A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
+
+* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
+ (#159 by @clue)
+
+ ```php
+ // old (still supported)
+ $stream = new ReadableResourceStream($resource, $loop);
+ $stream = new WritabeResourceStream($resource, $loop);
+ $stream = new DuplexResourceStream($resource, $loop);
+
+ // new (using default loop)
+ $stream = new ReadableResourceStream($resource);
+ $stream = new WritabeResourceStream($resource);
+ $stream = new DuplexResourceStream($resource);
+ ```
+
+* Improve test suite, use GitHub actions for continuous integration (CI),
+ update PHPUnit config, run tests on PHP 8 and add full core team to the license.
+ (#153, #156 and #157 by @SimonFrings and #154 by @WyriHaximus)
+
+## 1.1.1 (2020-05-04)
+
+* Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only).
+ (#150 by @clue)
+
+* Minor code style improvements to fix phpstan analysis warnings and
+ add `.gitattributes` to exclude dev files from exports.
+ (#140 by @flow-control and #144 by @reedy)
+
+* Improve test suite to run tests on PHP 7.4 and simplify test matrix.
+ (#147 by @clue)
+
+## 1.1.0 (2019-01-01)
+
+* Improvement: Increase performance by optimizing global function and constant look ups.
+ (#137 by @WyriHaximus)
+
+* Travis: Test against PHP 7.3.
+ (#138 by @WyriHaximus)
+
+* Fix: Ignore empty reads.
+ (#139 by @WyriHaximus)
+
+## 1.0.0 (2018-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Strict definition for event callback functions
+ (#101 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/www/libs/vendor/react/stream/LICENSE b/www/libs/vendor/react/stream/LICENSE
new file mode 100644
index 00000000..d6f8901f
--- /dev/null
+++ b/www/libs/vendor/react/stream/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+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.
diff --git a/www/libs/vendor/react/stream/README.md b/www/libs/vendor/react/stream/README.md
new file mode 100644
index 00000000..9c0468a6
--- /dev/null
+++ b/www/libs/vendor/react/stream/README.md
@@ -0,0 +1,1249 @@
+# Stream
+
+[![CI status](https://github.com/reactphp/stream/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/stream/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/react/stream?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+Loop::addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+Loop::addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+pass the event loop instance to use for this object. You can use a `null` value
+here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+This value SHOULD NOT be given unless you're sure you want to explicitly use a
+given event loop instance.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, null, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a
+ `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop).
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+pass the event loop instance to use for this object. You can use a `null` value
+here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+This value SHOULD NOT be given unless you're sure you want to explicitly use a
+given event loop instance.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, null, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, null, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a
+ `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop).
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+pass the event loop instance to use for this object. You can use a `null` value
+here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+This value SHOULD NOT be given unless you're sure you want to explicitly use a
+given event loop instance.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, null, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, null, 8192);
+$stream = new DuplexResourceStream($conn, null, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a
+ `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop).
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN);
+$stdout = new WritableResourceStream(STDOUT);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'));
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'));
+
+$source->pipe($dest);
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+composer require react/stream:^1.4
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/www/libs/vendor/react/stream/composer.json b/www/libs/vendor/react/stream/composer.json
new file mode 100644
index 00000000..09d8b71e
--- /dev/null
+++ b/www/libs/vendor/react/stream/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.2",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests/"
+ }
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/CompositeStream.php b/www/libs/vendor/react/stream/src/CompositeStream.php
new file mode 100644
index 00000000..dde091de
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,83 @@
+readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ $this->close();
+ return;
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/DuplexResourceStream.php b/www/libs/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100644
index 00000000..d6de55c0
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,240 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/DuplexStreamInterface.php b/www/libs/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100644
index 00000000..631ce31e
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+ Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/www/libs/vendor/react/stream/src/ReadableResourceStream.php b/www/libs/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100644
index 00000000..823360a6
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,188 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($this->stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/ReadableStreamInterface.php b/www/libs/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100644
index 00000000..fa3d59cd
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * Loop::addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/www/libs/vendor/react/stream/src/ThroughStream.php b/www/libs/vendor/react/stream/src/ThroughStream.php
new file mode 100644
index 00000000..3b4fbb78
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,195 @@
+on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !\is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ // only allow pause if still readable, false otherwise
+ $this->paused = $this->readable;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+
+ // emit drain event if previous write was paused (throttled)
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = \call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ // emit drain event on next resume if currently paused (throttled)
+ if ($this->paused) {
+ $this->drain = true;
+ }
+
+ // continue writing if still writable and not paused (throttled), false otherwise
+ return $this->writable && !$this->paused;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = false;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = false;
+ $this->drain = false;
+
+ $this->closed = true;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/Util.php b/www/libs/vendor/react/stream/src/Util.php
new file mode 100644
index 00000000..056b0377
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+ NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, \func_get_args());
+ });
+ }
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/WritableResourceStream.php b/www/libs/vendor/react/stream/src/WritableResourceStream.php
new file mode 100644
index 00000000..e3a7e74d
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,178 @@
+stream = $stream;
+ $this->loop = $loop ?: Loop::get();
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ \set_error_handler(function ($_, $errstr) use (&$error) {
+ $error = $errstr;
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = \fwrite($this->stream, $this->data);
+ } else {
+ $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ \restore_error_handler();
+
+ // Only report errors if *nothing* could be sent and an error has been raised.
+ // Ignore non-fatal warnings if *some* data could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if (($sent === 0 || $sent === false) && $error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) \substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/www/libs/vendor/react/stream/src/WritableStreamInterface.php b/www/libs/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100644
index 00000000..e2625928
--- /dev/null
+++ b/www/libs/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * Loop::addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/www/libs/vendor/symfony/deprecation-contracts/CHANGELOG.md b/www/libs/vendor/symfony/deprecation-contracts/CHANGELOG.md
new file mode 100644
index 00000000..7932e261
--- /dev/null
+++ b/www/libs/vendor/symfony/deprecation-contracts/CHANGELOG.md
@@ -0,0 +1,5 @@
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
diff --git a/www/libs/vendor/symfony/deprecation-contracts/LICENSE b/www/libs/vendor/symfony/deprecation-contracts/LICENSE
new file mode 100644
index 00000000..0ed3a246
--- /dev/null
+++ b/www/libs/vendor/symfony/deprecation-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+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.
diff --git a/www/libs/vendor/symfony/deprecation-contracts/README.md b/www/libs/vendor/symfony/deprecation-contracts/README.md
new file mode 100644
index 00000000..9814864c
--- /dev/null
+++ b/www/libs/vendor/symfony/deprecation-contracts/README.md
@@ -0,0 +1,26 @@
+Symfony Deprecation Contracts
+=============================
+
+A generic function and convention to trigger deprecation notices.
+
+This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
+
+By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
+the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
+
+The function requires at least 3 arguments:
+ - the name of the Composer package that is triggering the deprecation
+ - the version of the package that introduced the deprecation
+ - the message of the deprecation
+ - more arguments can be provided: they will be inserted in the message using `printf()` formatting
+
+Example:
+```php
+trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
+```
+
+This will generate the following message:
+`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
+
+While not recommended, the deprecation notices can be completely ignored by declaring an empty
+`function trigger_deprecation() {}` in your application.
diff --git a/www/libs/vendor/symfony/deprecation-contracts/composer.json b/www/libs/vendor/symfony/deprecation-contracts/composer.json
new file mode 100644
index 00000000..ceb6c079
--- /dev/null
+++ b/www/libs/vendor/symfony/deprecation-contracts/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "symfony/deprecation-contracts",
+ "type": "library",
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1"
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/deprecation-contracts/function.php b/www/libs/vendor/symfony/deprecation-contracts/function.php
new file mode 100644
index 00000000..2d56512b
--- /dev/null
+++ b/www/libs/vendor/symfony/deprecation-contracts/function.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (!function_exists('trigger_deprecation')) {
+ /**
+ * Triggers a silenced deprecation notice.
+ *
+ * @param string $package The name of the Composer package that is triggering the deprecation
+ * @param string $version The version of the package that introduced the deprecation
+ * @param string $message The message of the deprecation
+ * @param mixed ...$args Values to insert in the message using printf() formatting
+ *
+ * @author Nicolas Grekas
+ */
+ function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void
+ {
+ @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/AcceptHeader.php b/www/libs/vendor/symfony/http-foundation/AcceptHeader.php
new file mode 100644
index 00000000..853c000e
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/AcceptHeader.php
@@ -0,0 +1,150 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(AcceptHeaderItem::class);
+
+/**
+ * Represents an Accept-* header.
+ *
+ * An accept header is compound with a list of items,
+ * sorted by descending quality.
+ *
+ * @author Jean-François Simon
+ */
+class AcceptHeader
+{
+ /**
+ * @var AcceptHeaderItem[]
+ */
+ private array $items = [];
+
+ private bool $sorted = true;
+
+ /**
+ * @param AcceptHeaderItem[] $items
+ */
+ public function __construct(array $items)
+ {
+ foreach ($items as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeader instance from a string.
+ */
+ public static function fromString(?string $headerValue): self
+ {
+ $parts = HeaderUtils::split($headerValue ?? '', ',;=');
+
+ return new self(array_map(function ($subParts) {
+ static $index = 0;
+ $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.
+ */
+ public function __toString(): string
+ {
+ return implode(',', $this->items);
+ }
+
+ /**
+ * Tests if header has given value.
+ */
+ public function has(string $value): bool
+ {
+ return isset($this->items[$value]);
+ }
+
+ /**
+ * Returns given value's item, if exists.
+ */
+ public function get(string $value): ?AcceptHeaderItem
+ {
+ return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null;
+ }
+
+ /**
+ * Adds an item.
+ *
+ * @return $this
+ */
+ public function add(AcceptHeaderItem $item): static
+ {
+ $this->items[$item->getValue()] = $item;
+ $this->sorted = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns all items.
+ *
+ * @return AcceptHeaderItem[]
+ */
+ public function all(): array
+ {
+ $this->sort();
+
+ return $this->items;
+ }
+
+ /**
+ * Filters items on their value using given regex.
+ */
+ public function filter(string $pattern): self
+ {
+ return new self(array_filter($this->items, fn (AcceptHeaderItem $item) => preg_match($pattern, $item->getValue())));
+ }
+
+ /**
+ * Returns first item.
+ */
+ public function first(): ?AcceptHeaderItem
+ {
+ $this->sort();
+
+ return $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;
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/AcceptHeaderItem.php b/www/libs/vendor/symfony/http-foundation/AcceptHeaderItem.php
new file mode 100644
index 00000000..35ecd4ea
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/AcceptHeaderItem.php
@@ -0,0 +1,159 @@
+
+ *
+ * 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
+ */
+class AcceptHeaderItem
+{
+ private string $value;
+ private float $quality = 1.0;
+ private int $index = 0;
+ private array $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.
+ */
+ public static function fromString(?string $itemValue): self
+ {
+ $parts = HeaderUtils::split($itemValue ?? '', ';=');
+
+ $part = array_shift($parts);
+ $attributes = HeaderUtils::combine($parts);
+
+ return new self($part[0], $attributes);
+ }
+
+ /**
+ * Returns header value's string representation.
+ */
+ public function __toString(): string
+ {
+ $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): static
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item value.
+ */
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the item quality.
+ *
+ * @return $this
+ */
+ public function setQuality(float $quality): static
+ {
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item quality.
+ */
+ public function getQuality(): float
+ {
+ return $this->quality;
+ }
+
+ /**
+ * Set the item index.
+ *
+ * @return $this
+ */
+ public function setIndex(int $index): static
+ {
+ $this->index = $index;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item index.
+ */
+ public function getIndex(): int
+ {
+ return $this->index;
+ }
+
+ /**
+ * Tests if an attribute exists.
+ */
+ public function hasAttribute(string $name): bool
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * Returns an attribute by its name.
+ */
+ public function getAttribute(string $name, mixed $default = null): mixed
+ {
+ return $this->attributes[$name] ?? $default;
+ }
+
+ /**
+ * Returns all attributes.
+ */
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @return $this
+ */
+ public function setAttribute(string $name, string $value): static
+ {
+ if ('q' === $name) {
+ $this->quality = (float) $value;
+ } else {
+ $this->attributes[$name] = $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/BinaryFileResponse.php b/www/libs/vendor/symfony/http-foundation/BinaryFileResponse.php
new file mode 100644
index 00000000..41a244b8
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/BinaryFileResponse.php
@@ -0,0 +1,385 @@
+
+ *
+ * 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
+ * @author stealth35
+ * @author Igor Wiedler
+ * @author Jordan Alliot
+ * @author Sergey Linnik
+ */
+class BinaryFileResponse extends Response
+{
+ protected static $trustXSendfileTypeHeader = false;
+
+ /**
+ * @var File
+ */
+ protected $file;
+ protected $offset = 0;
+ protected $maxlen = -1;
+ protected $deleteFileAfterSend = false;
+ protected $chunkSize = 16 * 1024;
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code (200 "OK" by default)
+ * @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(\SplFileInfo|string $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();
+ }
+ }
+
+ /**
+ * Sets the file to stream.
+ *
+ * @return $this
+ *
+ * @throws FileException
+ */
+ public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static
+ {
+ 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.
+ */
+ public function getFile(): File
+ {
+ return $this->file;
+ }
+
+ /**
+ * Sets the response stream chunk size.
+ *
+ * @return $this
+ */
+ public function setChunkSize(int $chunkSize): static
+ {
+ if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
+ throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
+ }
+
+ $this->chunkSize = $chunkSize;
+
+ return $this;
+ }
+
+ /**
+ * Automatically sets the Last-Modified header according the file modification date.
+ *
+ * @return $this
+ */
+ public function setAutoLastModified(): static
+ {
+ $this->setLastModified(\DateTimeImmutable::createFromFormat('U', $this->file->getMTime()));
+
+ return $this;
+ }
+
+ /**
+ * Automatically sets the ETag header according to the checksum of the file.
+ *
+ * @return $this
+ */
+ public function setAutoEtag(): static
+ {
+ $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 = ''): static
+ {
+ if ('' === $filename) {
+ $filename = $this->file->getFilename();
+ }
+
+ if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($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;
+ }
+
+ public function prepare(Request $request): static
+ {
+ if ($this->isInformational() || $this->isEmpty()) {
+ parent::prepare($request);
+
+ $this->maxlen = 0;
+
+ return $this;
+ }
+
+ if (!$this->headers->has('Content-Type')) {
+ $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
+ }
+
+ parent::prepare($request);
+
+ $this->offset = 0;
+ $this->maxlen = -1;
+
+ if (false === $fileSize = $this->file->getSize()) {
+ return $this;
+ }
+ $this->headers->remove('Transfer-Encoding');
+ $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 https://github.com/rack/rack/blob/main/lib/rack/sendfile.rb
+ // @link https://mattbrictson.com/blog/accelerated-rails-downloads
+ if (!$request->headers->has('X-Accel-Mapping')) {
+ throw new \LogicException('The "X-Accel-Mapping" header must be set when "X-Sendfile-Type" is set to "X-Accel-Redirect".');
+ }
+ $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping'), ',=');
+ foreach ($parts as $part) {
+ [$pathPrefix, $location] = $part;
+ if (str_starts_with($path, $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') && $request->isMethod('GET')) {
+ // Process the range headers.
+ if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
+ $range = $request->headers->get('Range');
+
+ if (str_starts_with($range, 'bytes=')) {
+ [$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0];
+
+ $end = ('' === $end) ? $fileSize - 1 : (int) $end;
+
+ if ('' === $start) {
+ $start = $fileSize - $end;
+ $end = $fileSize - 1;
+ } else {
+ $start = (int) $start;
+ }
+
+ if ($start <= $end) {
+ $end = min($end, $fileSize - 1);
+ if ($start < 0 || $start > $end) {
+ $this->setStatusCode(416);
+ $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+ } elseif ($end - $start < $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);
+ }
+ }
+ }
+ }
+ }
+
+ if ($request->isMethod('HEAD')) {
+ $this->maxlen = 0;
+ }
+
+ 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;
+ }
+
+ public function sendContent(): static
+ {
+ try {
+ if (!$this->isSuccessful()) {
+ return $this;
+ }
+
+ if (0 === $this->maxlen) {
+ return $this;
+ }
+
+ $out = fopen('php://output', 'w');
+ $file = fopen($this->file->getPathname(), 'r');
+
+ ignore_user_abort(true);
+
+ if (0 !== $this->offset) {
+ fseek($file, $this->offset);
+ }
+
+ $length = $this->maxlen;
+ while ($length && !feof($file)) {
+ $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length;
+
+ if (false === $data = fread($file, $read)) {
+ break;
+ }
+ while ('' !== $data) {
+ $read = fwrite($out, $data);
+ if (false === $read || connection_aborted()) {
+ break 2;
+ }
+ if (0 < $length) {
+ $length -= $read;
+ }
+ $data = substr($data, $read);
+ }
+ }
+
+ fclose($out);
+ fclose($file);
+ } finally {
+ if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
+ unlink($this->file->getPathname());
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @throws \LogicException when the content is not null
+ */
+ public function setContent(?string $content): static
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
+ }
+
+ return $this;
+ }
+
+ public function getContent(): string|false
+ {
+ return false;
+ }
+
+ /**
+ * Trust X-Sendfile-Type header.
+ *
+ * @return void
+ */
+ public static function trustXSendfileTypeHeader()
+ {
+ self::$trustXSendfileTypeHeader = true;
+ }
+
+ /**
+ * If this is set to true, the file will be unlinked after the request is sent
+ * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
+ *
+ * @return $this
+ */
+ public function deleteFileAfterSend(bool $shouldDelete = true): static
+ {
+ $this->deleteFileAfterSend = $shouldDelete;
+
+ return $this;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/CHANGELOG.md b/www/libs/vendor/symfony/http-foundation/CHANGELOG.md
new file mode 100644
index 00000000..3f09854a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/CHANGELOG.md
@@ -0,0 +1,355 @@
+CHANGELOG
+=========
+
+6.4
+---
+
+ * Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable`
+ * Support root-level `Generator` in `StreamedJsonResponse`
+ * Add `UriSigner` from the HttpKernel component
+ * Add `partitioned` flag to `Cookie` (CHIPS Cookie)
+ * Add argument `bool $flush = true` to `Response::send()`
+* Make `MongoDbSessionHandler` instantiable with the mongodb extension directly
+
+6.3
+---
+
+ * Calling `ParameterBag::getDigit()`, `getAlnum()`, `getAlpha()` on an `array` throws a `UnexpectedValueException` instead of a `TypeError`
+ * Add `ParameterBag::getString()` to convert a parameter into string and throw an exception if the value is invalid
+ * Add `ParameterBag::getEnum()`
+ * Create migration for session table when pdo handler is used
+ * Add support for Relay PHP extension for Redis
+ * The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code)
+ * Add `IpUtils::isPrivateIp()`
+ * Add `Request::getPayload(): InputBag`
+ * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`,
+ * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set
+
+6.2
+---
+
+ * Add `StreamedJsonResponse` class for efficient JSON streaming
+ * The HTTP cache store uses the `xxh128` algorithm
+ * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments
+ * Add request matchers under the `Symfony\Component\HttpFoundation\RequestMatcher` namespace
+ * Deprecate `RequestMatcher` in favor of `ChainRequestMatcher`
+ * Deprecate `Symfony\Component\HttpFoundation\ExpressionRequestMatcher` in favor of `Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher`
+
+6.1
+---
+
+ * Add stale while revalidate and stale if error cache header
+ * Allow dynamic session "ttl" when using a remote storage
+ * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead
+
+6.0
+---
+
+ * Remove the `NamespacedAttributeBag` class
+ * Removed `Response::create()`, `JsonResponse::create()`,
+ `RedirectResponse::create()`, `StreamedResponse::create()` and
+ `BinaryFileResponse::create()` methods (use `__construct()` instead)
+ * Not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead
+ * Not passing a `Closure` together with `FILTER_CALLBACK` to `InputBag::filter()` throws an `\InvalidArgumentException`; wrap your filter in a closure instead
+ * Removed the `Request::HEADER_X_FORWARDED_ALL` constant, use either `Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO` or `Request::HEADER_X_FORWARDED_AWS_ELB` or `Request::HEADER_X_FORWARDED_TRAEFIK`constants instead
+ * Rename `RequestStack::getMasterRequest()` to `getMainRequest()`
+ * Not passing `FILTER_REQUIRE_ARRAY` or `FILTER_FORCE_ARRAY` flags to `InputBag::filter()` when filtering an array will throw `BadRequestException`
+ * Removed the `Request::HEADER_X_FORWARDED_ALL` constant
+ * Retrieving non-scalar values using `InputBag::get()` will throw `BadRequestException` (use `InputBad::all()` instead to retrieve an array)
+ * Passing non-scalar default value as the second argument `InputBag::get()` will throw `\InvalidArgumentException`
+ * Passing non-scalar, non-array value as the second argument `InputBag::set()` will throw `\InvalidArgumentException`
+ * Passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()` is not supported anymore.
+
+5.4
+---
+
+ * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead.
+ * Add the `litespeed_finish_request` method to work with Litespeed
+ * Deprecate `upload_progress.*` and `url_rewriter.tags` session options
+ * Allow setting session options via DSN
+
+5.3
+---
+
+ * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes
+ * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException`
+ * Add the `RequestStack::getSession` method
+ * Deprecate the `NamespacedAttributeBag` class
+ * Add `ResponseFormatSame` PHPUnit constraint
+ * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement
+
+5.2.0
+-----
+
+ * added support for `X-Forwarded-Prefix` header
+ * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names
+ * added `File::getContent()`
+ * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()`
+ * added `Request::toArray()` to parse a JSON request body to an array
+ * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter`
+ * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead.
+ * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead.
+ * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead
+
+5.1.0
+-----
+
+ * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`,
+ `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`,
+ `Cookie::withRaw`, `Cookie::withSameSite`
+ * Deprecate `Response::create()`, `JsonResponse::create()`,
+ `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use
+ `__construct()` instead)
+ * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference
+ according to [RFC 8674](https://tools.ietf.org/html/rfc8674)
+ * made the Mime component an optional dependency
+ * added `MarshallingSessionHandler`, `IdentityMarshaller`
+ * made `Session` accept a callback to report when the session is being used
+ * Add support for all core cache control directives
+ * Added `Symfony\Component\HttpFoundation\InputBag`
+ * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values
+
+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
+ * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column,
+ make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to
+ update your database.
+ * `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.
diff --git a/www/libs/vendor/symfony/http-foundation/ChainRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/ChainRequestMatcher.php
new file mode 100644
index 00000000..29486fc8
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/ChainRequestMatcher.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ChainRequestMatcher verifies that all checks match against a Request instance.
+ *
+ * @author Fabien Potencier
+ */
+class ChainRequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @param iterable $matchers
+ */
+ public function __construct(private iterable $matchers)
+ {
+ }
+
+ public function matches(Request $request): bool
+ {
+ foreach ($this->matchers as $matcher) {
+ if (!$matcher->matches($request)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Cookie.php b/www/libs/vendor/symfony/http-foundation/Cookie.php
new file mode 100644
index 00000000..4a3b7360
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Cookie.php
@@ -0,0 +1,412 @@
+
+ *
+ * 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
+ */
+class Cookie
+{
+ public const SAMESITE_NONE = 'none';
+ public const SAMESITE_LAX = 'lax';
+ public const SAMESITE_STRICT = 'strict';
+
+ protected $name;
+ protected $value;
+ protected $domain;
+ protected $expire;
+ protected $path;
+ protected $secure;
+ protected $httpOnly;
+
+ private bool $raw;
+ private ?string $sameSite = null;
+ private bool $partitioned = false;
+ private bool $secureDefault = false;
+
+ private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
+ private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
+ private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
+
+ /**
+ * Creates cookie from raw header string.
+ */
+ public static function fromString(string $cookie, bool $decode = false): static
+ {
+ $data = [
+ 'expires' => 0,
+ 'path' => '/',
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'raw' => !$decode,
+ 'samesite' => null,
+ 'partitioned' => false,
+ ];
+
+ $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;
+ $data['expires'] = self::expiresTimestamp($data['expires']);
+
+ if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) {
+ $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'], $data['partitioned']);
+ }
+
+ /**
+ * @see self::__construct
+ *
+ * @param self::SAMESITE_*|''|null $sameSite
+ * @param bool $partitioned
+ */
+ public static function create(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX /* , bool $partitioned = false */): self
+ {
+ $partitioned = 9 < \func_num_args() ? func_get_arg(9) : false;
+
+ return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite, $partitioned);
+ }
+
+ /**
+ * @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|null $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 self::SAMESITE_*|''|null $sameSite Whether the cookie will be available for cross-site requests
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $name, ?string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false)
+ {
+ // from PHP source code
+ if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
+ throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+ }
+
+ if (empty($name)) {
+ throw new \InvalidArgumentException('The cookie name cannot be empty.');
+ }
+
+ $this->name = $name;
+ $this->value = $value;
+ $this->domain = $domain;
+ $this->expire = self::expiresTimestamp($expire);
+ $this->path = empty($path) ? '/' : $path;
+ $this->secure = $secure;
+ $this->httpOnly = $httpOnly;
+ $this->raw = $raw;
+ $this->sameSite = $this->withSameSite($sameSite)->sameSite;
+ $this->partitioned = $partitioned;
+ }
+
+ /**
+ * Creates a cookie copy with a new value.
+ */
+ public function withValue(?string $value): static
+ {
+ $cookie = clone $this;
+ $cookie->value = $value;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy with a new domain that the cookie is available to.
+ */
+ public function withDomain(?string $domain): static
+ {
+ $cookie = clone $this;
+ $cookie->domain = $domain;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy with a new time the cookie expires.
+ */
+ public function withExpires(int|string|\DateTimeInterface $expire = 0): static
+ {
+ $cookie = clone $this;
+ $cookie->expire = self::expiresTimestamp($expire);
+
+ return $cookie;
+ }
+
+ /**
+ * Converts expires formats to a unix timestamp.
+ */
+ private static function expiresTimestamp(int|string|\DateTimeInterface $expire = 0): int
+ {
+ // 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.');
+ }
+ }
+
+ return 0 < $expire ? (int) $expire : 0;
+ }
+
+ /**
+ * Creates a cookie copy with a new path on the server in which the cookie will be available on.
+ */
+ public function withPath(string $path): static
+ {
+ $cookie = clone $this;
+ $cookie->path = '' === $path ? '/' : $path;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client.
+ */
+ public function withSecure(bool $secure = true): static
+ {
+ $cookie = clone $this;
+ $cookie->secure = $secure;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy that be accessible only through the HTTP protocol.
+ */
+ public function withHttpOnly(bool $httpOnly = true): static
+ {
+ $cookie = clone $this;
+ $cookie->httpOnly = $httpOnly;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy that uses no url encoding.
+ */
+ public function withRaw(bool $raw = true): static
+ {
+ if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) {
+ throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name));
+ }
+
+ $cookie = clone $this;
+ $cookie->raw = $raw;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy with SameSite attribute.
+ *
+ * @param self::SAMESITE_*|''|null $sameSite
+ */
+ public function withSameSite(?string $sameSite): static
+ {
+ 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.');
+ }
+
+ $cookie = clone $this;
+ $cookie->sameSite = $sameSite;
+
+ return $cookie;
+ }
+
+ /**
+ * Creates a cookie copy that is tied to the top-level site in cross-site context.
+ */
+ public function withPartitioned(bool $partitioned = true): static
+ {
+ $cookie = clone $this;
+ $cookie->partitioned = $partitioned;
+
+ return $cookie;
+ }
+
+ /**
+ * Returns the cookie as a string.
+ */
+ public function __toString(): string
+ {
+ if ($this->isRaw()) {
+ $str = $this->getName();
+ } else {
+ $str = str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $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 ($this->isSecure()) {
+ $str .= '; secure';
+ }
+
+ if ($this->isHttpOnly()) {
+ $str .= '; httponly';
+ }
+
+ if (null !== $this->getSameSite()) {
+ $str .= '; samesite='.$this->getSameSite();
+ }
+
+ if ($this->isPartitioned()) {
+ $str .= '; partitioned';
+ }
+
+ return $str;
+ }
+
+ /**
+ * Gets the name of the cookie.
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the value of the cookie.
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Gets the domain that the cookie is available to.
+ */
+ public function getDomain(): ?string
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Gets the time the cookie expires.
+ */
+ public function getExpiresTime(): int
+ {
+ return $this->expire;
+ }
+
+ /**
+ * Gets the max-age attribute.
+ */
+ public function getMaxAge(): int
+ {
+ $maxAge = $this->expire - time();
+
+ return 0 >= $maxAge ? 0 : $maxAge;
+ }
+
+ /**
+ * Gets the path on the server in which the cookie will be available on.
+ */
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ /**
+ * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
+ */
+ public function isSecure(): bool
+ {
+ return $this->secure ?? $this->secureDefault;
+ }
+
+ /**
+ * Checks whether the cookie will be made accessible only through the HTTP protocol.
+ */
+ public function isHttpOnly(): bool
+ {
+ return $this->httpOnly;
+ }
+
+ /**
+ * Whether this cookie is about to be cleared.
+ */
+ public function isCleared(): bool
+ {
+ return 0 !== $this->expire && $this->expire < time();
+ }
+
+ /**
+ * Checks if the cookie value should be sent with no url encoding.
+ */
+ public function isRaw(): bool
+ {
+ return $this->raw;
+ }
+
+ /**
+ * Checks whether the cookie should be tied to the top-level site in cross-site context.
+ */
+ public function isPartitioned(): bool
+ {
+ return $this->partitioned;
+ }
+
+ /**
+ * @return self::SAMESITE_*|null
+ */
+ public function getSameSite(): ?string
+ {
+ 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;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/BadRequestException.php b/www/libs/vendor/symfony/http-foundation/Exception/BadRequestException.php
new file mode 100644
index 00000000..505e1cfd
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/BadRequestException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Raised when a user sends a malformed request.
+ */
+class BadRequestException extends UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/www/libs/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
new file mode 100644
index 00000000..77aa0e1e
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * The HTTP request contains headers with conflicting information.
+ *
+ * @author Magnus Nordlander
+ */
+class ConflictingHeadersException extends UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/JsonException.php b/www/libs/vendor/symfony/http-foundation/Exception/JsonException.php
new file mode 100644
index 00000000..6d1e0aec
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/JsonException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Thrown by Request::toArray() when the content cannot be JSON-decoded.
+ *
+ * @author Tobias Nyholm
+ */
+final class JsonException extends UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/www/libs/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
new file mode 100644
index 00000000..478d0dc7
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Interface for Request exceptions.
+ *
+ * Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
+ */
+interface RequestExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php b/www/libs/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php
new file mode 100644
index 00000000..80a21bf1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php
@@ -0,0 +1,27 @@
+
+ *
+ * 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 session does not exist. This happens in the following cases:
+ * - the session is not enabled
+ * - attempt to read a session outside a request context (ie. cli script).
+ *
+ * @author Jérémy Derussé
+ */
+class SessionNotFoundException extends \LogicException implements RequestExceptionInterface
+{
+ public function __construct(string $message = 'There is currently no session available.', int $code = 0, ?\Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/www/libs/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
new file mode 100644
index 00000000..4818ef2c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace 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
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php b/www/libs/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php
new file mode 100644
index 00000000..c3e6c9d6
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Exception/UnexpectedValueException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+class UnexpectedValueException extends \UnexpectedValueException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
new file mode 100644
index 00000000..fe65e920
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -0,0 +1,56 @@
+
+ *
+ * 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\Expression;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher as NewExpressionRequestMatcher;
+
+trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s" class is deprecated, use "%s" instead.', ExpressionRequestMatcher::class, NewExpressionRequestMatcher::class);
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier
+ *
+ * @deprecated since Symfony 6.2, use "Symfony\Component\HttpFoundation\RequestMatcher\ExpressionRequestMatcher" instead
+ */
+class ExpressionRequestMatcher extends RequestMatcher
+{
+ private ExpressionLanguage $language;
+ private Expression|string $expression;
+
+ /**
+ * @return void
+ */
+ public function setExpression(ExpressionLanguage $language, Expression|string $expression)
+ {
+ $this->language = $language;
+ $this->expression = $expression;
+ }
+
+ public function matches(Request $request): bool
+ {
+ if (!isset($this->language)) {
+ throw new \LogicException('Unable to match the request as the expression language is not available. Try running "composer require symfony/expression-language".');
+ }
+
+ 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);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
new file mode 100644
index 00000000..136d2a9f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+class AccessDeniedException extends FileException
+{
+ public function __construct(string $path)
+ {
+ parent::__construct(sprintf('The file %s could not be accessed', $path));
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php
new file mode 100644
index 00000000..c49f53a6
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class CannotWriteFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php
new file mode 100644
index 00000000..ed83499c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class ExtensionFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/FileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/FileException.php
new file mode 100644
index 00000000..fad5133e
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/FileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred in the component File.
+ *
+ * @author Bernhard Schussek
+ */
+class FileException extends \RuntimeException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
new file mode 100644
index 00000000..31bdf68f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+class FileNotFoundException extends FileException
+{
+ public function __construct(string $path)
+ {
+ parent::__construct(sprintf('The file "%s" does not exist', $path));
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php
new file mode 100644
index 00000000..8741be08
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class FormSizeFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php
new file mode 100644
index 00000000..c8fde610
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class IniSizeFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/NoFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/NoFileException.php
new file mode 100644
index 00000000..4b48cc77
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/NoFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class NoFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
new file mode 100644
index 00000000..bdead2d9
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class NoTmpDirFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/PartialFileException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/PartialFileException.php
new file mode 100644
index 00000000..4641efb5
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/PartialFileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile.
+ *
+ * @author Florent Mata
+ */
+class PartialFileException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
new file mode 100644
index 00000000..905bd596
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+class UnexpectedTypeException extends FileException
+{
+ public function __construct(mixed $value, string $expectedType)
+ {
+ parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value)));
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Exception/UploadException.php b/www/libs/vendor/symfony/http-foundation/File/Exception/UploadException.php
new file mode 100644
index 00000000..7074e765
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Exception/UploadException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred during file upload.
+ *
+ * @author Bernhard Schussek
+ */
+class UploadException extends FileException
+{
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/File.php b/www/libs/vendor/symfony/http-foundation/File/File.php
new file mode 100644
index 00000000..34ca5a53
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/File.php
@@ -0,0 +1,141 @@
+
+ *
+ * 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
+ */
+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.
+ *
+ * @see MimeTypes
+ * @see getMimeType()
+ */
+ public function guessExtension(): ?string
+ {
+ if (!class_exists(MimeTypes::class)) {
+ throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".');
+ }
+
+ 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.
+ *
+ * @see MimeTypes
+ */
+ public function getMimeType(): ?string
+ {
+ if (!class_exists(MimeTypes::class)) {
+ throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".');
+ }
+
+ return MimeTypes::getDefault()->guessMimeType($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @throws FileException if the target file could not be created
+ */
+ public function move(string $directory, ?string $name = null): self
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ try {
+ $renamed = rename($this->getPathname(), $target);
+ } finally {
+ 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;
+ }
+
+ public function getContent(): string
+ {
+ $content = file_get_contents($this->getPathname());
+
+ if (false === $content) {
+ throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname()));
+ }
+
+ return $content;
+ }
+
+ protected function getTargetFile(string $directory, ?string $name = null): self
+ {
+ 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.
+ */
+ protected function getName(string $name): string
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/Stream.php b/www/libs/vendor/symfony/http-foundation/File/Stream.php
new file mode 100644
index 00000000..2c156b2e
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/Stream.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+class Stream extends File
+{
+ public function getSize(): int|false
+ {
+ return false;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php b/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php
new file mode 100644
index 00000000..f475d028
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/File/UploadedFile.php
@@ -0,0 +1,269 @@
+
+ *
+ * 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
+ * @author Florian Eckerstorfer
+ * @author Fabien Potencier
+ */
+class UploadedFile extends File
+{
+ private bool $test;
+ private string $originalName;
+ private string $mimeType;
+ private int $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.
+ * This should not be considered as a safe value to use for a file name on your servers.
+ */
+ public function getClientOriginalName(): string
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * Returns the original file extension.
+ *
+ * It is extracted from the original file name that was uploaded.
+ * This should not be considered as a safe value to use for a file name on your servers.
+ */
+ public function getClientOriginalExtension(): string
+ {
+ 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).
+ *
+ * @see getMimeType()
+ */
+ public function getClientMimeType(): string
+ {
+ 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).
+ *
+ * @see guessExtension()
+ * @see getClientMimeType()
+ */
+ public function guessClientExtension(): ?string
+ {
+ if (!class_exists(MimeTypes::class)) {
+ throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".');
+ }
+
+ 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.
+ */
+ public function getError(): int
+ {
+ return $this->error;
+ }
+
+ /**
+ * Returns whether the file has been uploaded with HTTP and no error occurred.
+ */
+ public function isValid(): bool
+ {
+ $isOk = \UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @throws FileException if, for any reason, the file could not have been moved
+ */
+ public function move(string $directory, ?string $name = null): File
+ {
+ 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; });
+ try {
+ $moved = move_uploaded_file($this->getPathname(), $target);
+ } finally {
+ 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|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX)
+ */
+ public static function getMaxFilesize(): int|float
+ {
+ $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);
+ }
+
+ private static function parseFilesize(string $size): int|float
+ {
+ if ('' === $size) {
+ return 0;
+ }
+
+ $size = strtolower($size);
+
+ $max = ltrim($size, '+');
+ if (str_starts_with($max, '0x')) {
+ $max = \intval($max, 16);
+ } elseif (str_starts_with($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.
+ */
+ public function getErrorMessage(): string
+ {
+ 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 = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.';
+
+ return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/FileBag.php b/www/libs/vendor/symfony/http-foundation/FileBag.php
new file mode 100644
index 00000000..b74a02e2
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/FileBag.php
@@ -0,0 +1,136 @@
+
+ *
+ * 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
+ * @author Bulat Shakirzyanov
+ */
+class FileBag extends ParameterBag
+{
+ private const FILE_KEYS = ['error', 'name', 'size', 'tmp_name', 'type'];
+
+ /**
+ * @param array|UploadedFile[] $parameters An array of HTTP files
+ */
+ public function __construct(array $parameters = [])
+ {
+ $this->replace($parameters);
+ }
+
+ /**
+ * @return void
+ */
+ public function replace(array $files = [])
+ {
+ $this->parameters = [];
+ $this->add($files);
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $key, mixed $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));
+ }
+
+ /**
+ * @return void
+ */
+ public function add(array $files = [])
+ {
+ foreach ($files as $key => $file) {
+ $this->set($key, $file);
+ }
+ }
+
+ /**
+ * Converts uploaded files to UploadedFile instances.
+ *
+ * @return UploadedFile[]|UploadedFile|null
+ */
+ protected function convertFileInformation(array|UploadedFile $file): array|UploadedFile|null
+ {
+ if ($file instanceof UploadedFile) {
+ return $file;
+ }
+
+ $file = $this->fixPhpFilesArray($file);
+ $keys = array_keys($file);
+ sort($keys);
+
+ if (self::FILE_KEYS == $keys) {
+ 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(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $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.
+ */
+ protected function fixPhpFilesArray(array $data): array
+ {
+ // Remove extra key added by PHP 8.1.
+ unset($data['full_path']);
+ $keys = array_keys($data);
+ sort($keys);
+
+ if (self::FILE_KEYS != $keys || !isset($data['name']) || !\is_array($data['name'])) {
+ return $data;
+ }
+
+ $files = $data;
+ foreach (self::FILE_KEYS 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;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/HeaderBag.php b/www/libs/vendor/symfony/http-foundation/HeaderBag.php
new file mode 100644
index 00000000..4dd777f1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/HeaderBag.php
@@ -0,0 +1,290 @@
+
+ *
+ * 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
+ *
+ * @implements \IteratorAggregate>
+ */
+class HeaderBag implements \IteratorAggregate, \Countable, \Stringable
+{
+ protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ protected const LOWER = '-abcdefghijklmnopqrstuvwxyz';
+
+ /**
+ * @var array>
+ */
+ protected $headers = [];
+ protected $cacheControl = [];
+
+ public function __construct(array $headers = [])
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns the headers as a string.
+ */
+ public function __toString(): string
+ {
+ 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 ($key is null ? array> : list)
+ */
+ public function all(?string $key = null): array
+ {
+ if (null !== $key) {
+ return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? [];
+ }
+
+ return $this->headers;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return string[]
+ */
+ public function keys(): array
+ {
+ return array_keys($this->all());
+ }
+
+ /**
+ * Replaces the current HTTP headers by a new set.
+ *
+ * @return void
+ */
+ public function replace(array $headers = [])
+ {
+ $this->headers = [];
+ $this->add($headers);
+ }
+
+ /**
+ * Adds new headers the current HTTP headers set.
+ *
+ * @return void
+ */
+ public function add(array $headers)
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns the first header by name or the default one.
+ */
+ public function get(string $key, ?string $default = null): ?string
+ {
+ $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[]|null $values The value or an array of values
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ *
+ * @return void
+ */
+ public function set(string $key, string|array|null $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.
+ */
+ public function has(string $key): bool
+ {
+ return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all());
+ }
+
+ /**
+ * Returns true if the given HTTP header contains the given value.
+ */
+ public function contains(string $key, string $value): bool
+ {
+ return \in_array($value, $this->all($key));
+ }
+
+ /**
+ * Removes a header.
+ *
+ * @return void
+ */
+ 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 \DateTimeImmutable|null
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ */
+ public function getDate(string $key, ?\DateTimeInterface $default = null): ?\DateTimeInterface
+ {
+ if (null === $value = $this->get($key)) {
+ return null !== $default ? \DateTimeImmutable::createFromInterface($default) : null;
+ }
+
+ if (false === $date = \DateTimeImmutable::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.
+ *
+ * @return void
+ */
+ public function addCacheControlDirective(string $key, bool|string $value = true)
+ {
+ $this->cacheControl[$key] = $value;
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns true if the Cache-Control directive is defined.
+ */
+ public function hasCacheControlDirective(string $key): bool
+ {
+ return \array_key_exists($key, $this->cacheControl);
+ }
+
+ /**
+ * Returns a Cache-Control directive value by name.
+ */
+ public function getCacheControlDirective(string $key): bool|string|null
+ {
+ return $this->cacheControl[$key] ?? null;
+ }
+
+ /**
+ * Removes a Cache-Control directive.
+ *
+ * @return void
+ */
+ public function removeCacheControlDirective(string $key)
+ {
+ unset($this->cacheControl[$key]);
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns an iterator for headers.
+ *
+ * @return \ArrayIterator>
+ */
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->headers);
+ }
+
+ /**
+ * Returns the number of headers.
+ */
+ public function count(): int
+ {
+ return \count($this->headers);
+ }
+
+ /**
+ * @return string
+ */
+ protected function getCacheControlHeader()
+ {
+ ksort($this->cacheControl);
+
+ return HeaderUtils::toString($this->cacheControl, ',');
+ }
+
+ /**
+ * Parses a Cache-Control HTTP header.
+ */
+ protected function parseCacheControl(string $header): array
+ {
+ $parts = HeaderUtils::split($header, ',=');
+
+ return HeaderUtils::combine($parts);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/HeaderUtils.php b/www/libs/vendor/symfony/http-foundation/HeaderUtils.php
new file mode 100644
index 00000000..110896e1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/HeaderUtils.php
@@ -0,0 +1,298 @@
+
+ *
+ * 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
+ */
+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
+ {
+ if ('' === $separators) {
+ throw new \InvalidArgumentException('At least one separator must be specified.');
+ }
+
+ $quotedSeparators = preg_quote($separators, '/');
+
+ preg_match_all('
+ /
+ (?!\s)
+ (?:
+ # quoted-string
+ "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
+ |
+ # token
+ [^"'.$quotedSeparators.']+
+ )+
+ (?['.$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 verbatim.
+ */
+ public static function unquote(string $s): string
+ {
+ return preg_replace('/\\\\(.)|"/', '$1', $s);
+ }
+
+ /**
+ * Generates an 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
+ *
+ * @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 (str_contains($filenameFallback, '%')) {
+ throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+ }
+
+ // path separators aren't allowed in either.
+ if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($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, ';');
+ }
+
+ /**
+ * Like parse_str(), but preserves dots in variable names.
+ */
+ public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array
+ {
+ $q = [];
+
+ foreach (explode($separator, $query) as $v) {
+ if (false !== $i = strpos($v, "\0")) {
+ $v = substr($v, 0, $i);
+ }
+
+ if (false === $i = strpos($v, '=')) {
+ $k = urldecode($v);
+ $v = '';
+ } else {
+ $k = urldecode(substr($v, 0, $i));
+ $v = substr($v, $i);
+ }
+
+ if (false !== $i = strpos($k, "\0")) {
+ $k = substr($k, 0, $i);
+ }
+
+ $k = ltrim($k, ' ');
+
+ if ($ignoreBrackets) {
+ $q[$k][] = urldecode(substr($v, 1));
+
+ continue;
+ }
+
+ if (false === $i = strpos($k, '[')) {
+ $q[] = bin2hex($k).$v;
+ } else {
+ $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v;
+ }
+ }
+
+ if ($ignoreBrackets) {
+ return $q;
+ }
+
+ parse_str(implode('&', $q), $q);
+
+ $query = [];
+
+ foreach ($q as $k => $v) {
+ if (false !== $i = strpos($k, '_')) {
+ $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
+ } else {
+ $query[hex2bin($k)] = $v;
+ }
+ }
+
+ return $query;
+ }
+
+ private static function groupParts(array $matches, string $separators, bool $first = true): array
+ {
+ $separator = $separators[0];
+ $separators = substr($separators, 1) ?: '';
+ $i = 0;
+
+ if ('' === $separators && !$first) {
+ $parts = [''];
+
+ foreach ($matches as $match) {
+ if (!$i && isset($match['separator'])) {
+ $i = 1;
+ $parts[1] = '';
+ } else {
+ $parts[$i] .= self::unquote($match[0]);
+ }
+ }
+
+ return $parts;
+ }
+
+ $parts = [];
+ $partMatches = [];
+
+ foreach ($matches as $match) {
+ if (($match['separator'] ?? null) === $separator) {
+ ++$i;
+ } else {
+ $partMatches[$i][] = $match;
+ }
+ }
+
+ foreach ($partMatches as $matches) {
+ if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) {
+ $parts[] = $unquoted;
+ } elseif ($groupedParts = self::groupParts($matches, $separators, false)) {
+ $parts[] = $groupedParts;
+ }
+ }
+
+ return $parts;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/InputBag.php b/www/libs/vendor/symfony/http-foundation/InputBag.php
new file mode 100644
index 00000000..5acf35fe
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/InputBag.php
@@ -0,0 +1,140 @@
+
+ *
+ * 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\Exception\BadRequestException;
+use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException;
+
+/**
+ * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE.
+ *
+ * @author Saif Eddin Gmati
+ */
+final class InputBag extends ParameterBag
+{
+ /**
+ * Returns a scalar input value by name.
+ *
+ * @param string|int|float|bool|null $default The default value if the input key does not exist
+ */
+ public function get(string $key, mixed $default = null): string|int|float|bool|null
+ {
+ if (null !== $default && !\is_scalar($default) && !$default instanceof \Stringable) {
+ throw new \InvalidArgumentException(sprintf('Expected a scalar value as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($default)));
+ }
+
+ $value = parent::get($key, $this);
+
+ if (null !== $value && $this !== $value && !\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new BadRequestException(sprintf('Input value "%s" contains a non-scalar value.', $key));
+ }
+
+ return $this === $value ? $default : $value;
+ }
+
+ /**
+ * Replaces the current input values by a new set.
+ */
+ public function replace(array $inputs = []): void
+ {
+ $this->parameters = [];
+ $this->add($inputs);
+ }
+
+ /**
+ * Adds input values.
+ */
+ public function add(array $inputs = []): void
+ {
+ foreach ($inputs as $input => $value) {
+ $this->set($input, $value);
+ }
+ }
+
+ /**
+ * Sets an input by name.
+ *
+ * @param string|int|float|bool|array|null $value
+ */
+ public function set(string $key, mixed $value): void
+ {
+ if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) {
+ throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value)));
+ }
+
+ $this->parameters[$key] = $value;
+ }
+
+ /**
+ * Returns the parameter value converted to an enum.
+ *
+ * @template T of \BackedEnum
+ *
+ * @param class-string $class
+ * @param ?T $default
+ *
+ * @return ?T
+ */
+ public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
+ {
+ try {
+ return parent::getEnum($key, $class, $default);
+ } catch (UnexpectedValueException $e) {
+ throw new BadRequestException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns the parameter value converted to string.
+ */
+ public function getString(string $key, string $default = ''): string
+ {
+ // Shortcuts the parent method because the validation on scalar is already done in get().
+ return (string) $this->get($key, $default);
+ }
+
+ public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
+ {
+ $value = $this->has($key) ? $this->all()[$key] : $default;
+
+ // Always turn $options into an array - this allows filter_var option shortcuts.
+ if (!\is_array($options) && $options) {
+ $options = ['flags' => $options];
+ }
+
+ if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
+ throw new BadRequestException(sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
+ }
+
+ if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
+ throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+ }
+
+ $options['flags'] ??= 0;
+ $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
+ $options['flags'] |= \FILTER_NULL_ON_FAILURE;
+
+ $value = filter_var($value, $filter, $options);
+
+ if (null !== $value || $nullOnFailure) {
+ return $value;
+ }
+
+ $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
+ $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter';
+ $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with';
+
+ trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw a "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, BadRequestException::class);
+
+ return false;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/IpUtils.php b/www/libs/vendor/symfony/http-foundation/IpUtils.php
new file mode 100644
index 00000000..ceab620c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/IpUtils.php
@@ -0,0 +1,241 @@
+
+ *
+ * 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
+ */
+class IpUtils
+{
+ public const PRIVATE_SUBNETS = [
+ '127.0.0.0/8', // RFC1700 (Loopback)
+ '10.0.0.0/8', // RFC1918
+ '192.168.0.0/16', // RFC1918
+ '172.16.0.0/12', // RFC1918
+ '169.254.0.0/16', // RFC3927
+ '0.0.0.0/8', // RFC5735
+ '240.0.0.0/4', // RFC1112
+ '::1/128', // Loopback
+ 'fc00::/7', // Unique Local Address
+ 'fe80::/10', // Link Local Address
+ '::ffff:0:0/96', // IPv4 translations
+ '::/128', // Unspecified address
+ ];
+
+ private static array $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)
+ */
+ public static function checkIp(string $requestIp, string|array $ips): bool
+ {
+ 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): bool
+ {
+ $cacheKey = $requestIp.'-'.$ip.'-v4';
+ if (null !== $cacheValue = self::getCacheResult($cacheKey)) {
+ return $cacheValue;
+ }
+
+ if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
+ return self::setCacheResult($cacheKey, false);
+ }
+
+ if (str_contains($ip, '/')) {
+ [$address, $netmask] = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return self::setCacheResult($cacheKey, false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4));
+ }
+
+ if ($netmask < 0 || $netmask > 32) {
+ return self::setCacheResult($cacheKey, false);
+ }
+ } else {
+ $address = $ip;
+ $netmask = 32;
+ }
+
+ if (false === ip2long($address)) {
+ return self::setCacheResult($cacheKey, false);
+ }
+
+ return self::setCacheResult($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
+ *
+ * @see https://github.com/dsp/v6tools
+ *
+ * @param string $ip IPv6 address or subnet in CIDR notation
+ *
+ * @throws \RuntimeException When IPV6 support is not enabled
+ */
+ public static function checkIp6(string $requestIp, string $ip): bool
+ {
+ $cacheKey = $requestIp.'-'.$ip.'-v6';
+ if (null !== $cacheValue = self::getCacheResult($cacheKey)) {
+ return $cacheValue;
+ }
+
+ 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".');
+ }
+
+ // Check to see if we were given a IP4 $requestIp or $ip by mistake
+ if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+ return self::setCacheResult($cacheKey, false);
+ }
+
+ if (str_contains($ip, '/')) {
+ [$address, $netmask] = explode('/', $ip, 2);
+
+ if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+ return self::setCacheResult($cacheKey, false);
+ }
+
+ if ('0' === $netmask) {
+ return (bool) unpack('n*', @inet_pton($address));
+ }
+
+ if ($netmask < 1 || $netmask > 128) {
+ return self::setCacheResult($cacheKey, false);
+ }
+ } else {
+ if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
+ return self::setCacheResult($cacheKey, false);
+ }
+
+ $address = $ip;
+ $netmask = 128;
+ }
+
+ $bytesAddr = unpack('n*', @inet_pton($address));
+ $bytesTest = unpack('n*', @inet_pton($requestIp));
+
+ if (!$bytesAddr || !$bytesTest) {
+ return self::setCacheResult($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::setCacheResult($cacheKey, false);
+ }
+ }
+
+ return self::setCacheResult($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 (str_starts_with($ip, '[') && str_ends_with($ip, ']')) {
+ $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;
+ }
+
+ /**
+ * Checks if an IPv4 or IPv6 address is contained in the list of private IP subnets.
+ */
+ public static function isPrivateIp(string $requestIp): bool
+ {
+ return self::checkIp($requestIp, self::PRIVATE_SUBNETS);
+ }
+
+ private static function getCacheResult(string $cacheKey): ?bool
+ {
+ if (isset(self::$checkedIps[$cacheKey])) {
+ // Move the item last in cache (LRU)
+ $value = self::$checkedIps[$cacheKey];
+ unset(self::$checkedIps[$cacheKey]);
+ self::$checkedIps[$cacheKey] = $value;
+
+ return self::$checkedIps[$cacheKey];
+ }
+
+ return null;
+ }
+
+ private static function setCacheResult(string $cacheKey, bool $result): bool
+ {
+ if (1000 < \count(self::$checkedIps)) {
+ // stop memory leak if there are many keys
+ self::$checkedIps = \array_slice(self::$checkedIps, 500, null, true);
+ }
+
+ return self::$checkedIps[$cacheKey] = $result;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/JsonResponse.php b/www/libs/vendor/symfony/http-foundation/JsonResponse.php
new file mode 100644
index 00000000..93c5751f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/JsonResponse.php
@@ -0,0 +1,190 @@
+
+ *
+ * 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
+ */
+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
+ public const DEFAULT_ENCODING_OPTIONS = 15;
+
+ protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
+
+ /**
+ * @param bool $json If the data is already a JSON string
+ */
+ public function __construct(mixed $data = null, int $status = 200, array $headers = [], bool $json = false)
+ {
+ parent::__construct('', $status, $headers);
+
+ if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) {
+ throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
+ }
+
+ $data ??= new \ArrayObject();
+
+ $json ? $this->setJson($data) : $this->setData($data);
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return JsonResponse::fromJsonString('{"key": "value"}')
+ * ->setSharedMaxAge(300);
+ *
+ * @param string $data The JSON response string
+ * @param int $status The response status code (200 "OK" by default)
+ * @param array $headers An array of response headers
+ */
+ public static function fromJsonString(string $data, int $status = 200, array $headers = []): static
+ {
+ 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): static
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ 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
+ $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
+ */
+ public function setJson(string $json): static
+ {
+ $this->data = $json;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets the data to be sent as JSON.
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setData(mixed $data = []): static
+ {
+ try {
+ $data = json_encode($data, $this->encodingOptions);
+ } catch (\Exception $e) {
+ if ('Exception' === $e::class && str_starts_with($e->getMessage(), 'Failed calling ')) {
+ throw $e->getPrevious() ?: $e;
+ }
+ throw $e;
+ }
+
+ if (\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.
+ */
+ public function getEncodingOptions(): int
+ {
+ return $this->encodingOptions;
+ }
+
+ /**
+ * Sets options used while encoding data to JSON.
+ *
+ * @return $this
+ */
+ public function setEncodingOptions(int $encodingOptions): static
+ {
+ $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(): static
+ {
+ 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);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/LICENSE b/www/libs/vendor/symfony/http-foundation/LICENSE
new file mode 100644
index 00000000..0138f8f0
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+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.
diff --git a/www/libs/vendor/symfony/http-foundation/ParameterBag.php b/www/libs/vendor/symfony/http-foundation/ParameterBag.php
new file mode 100644
index 00000000..48fa4b23
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/ParameterBag.php
@@ -0,0 +1,258 @@
+
+ *
+ * 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\Exception\BadRequestException;
+use Symfony\Component\HttpFoundation\Exception\UnexpectedValueException;
+
+/**
+ * ParameterBag is a container for key/value pairs.
+ *
+ * @author Fabien Potencier
+ *
+ * @implements \IteratorAggregate
+ */
+class ParameterBag implements \IteratorAggregate, \Countable
+{
+ /**
+ * Parameter storage.
+ */
+ protected $parameters;
+
+ public function __construct(array $parameters = [])
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Returns the parameters.
+ *
+ * @param string|null $key The name of the parameter to return or null to get them all
+ */
+ public function all(?string $key = null): array
+ {
+ if (null === $key) {
+ return $this->parameters;
+ }
+
+ if (!\is_array($value = $this->parameters[$key] ?? [])) {
+ throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value)));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Returns the parameter keys.
+ */
+ public function keys(): array
+ {
+ return array_keys($this->parameters);
+ }
+
+ /**
+ * Replaces the current parameters by a new set.
+ *
+ * @return void
+ */
+ public function replace(array $parameters = [])
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Adds parameters.
+ *
+ * @return void
+ */
+ public function add(array $parameters = [])
+ {
+ $this->parameters = array_replace($this->parameters, $parameters);
+ }
+
+ public function get(string $key, mixed $default = null): mixed
+ {
+ return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $key, mixed $value)
+ {
+ $this->parameters[$key] = $value;
+ }
+
+ /**
+ * Returns true if the parameter is defined.
+ */
+ public function has(string $key): bool
+ {
+ return \array_key_exists($key, $this->parameters);
+ }
+
+ /**
+ * Removes a parameter.
+ *
+ * @return void
+ */
+ public function remove(string $key)
+ {
+ unset($this->parameters[$key]);
+ }
+
+ /**
+ * Returns the alphabetic characters of the parameter value.
+ */
+ public function getAlpha(string $key, string $default = ''): string
+ {
+ return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default));
+ }
+
+ /**
+ * Returns the alphabetic characters and digits of the parameter value.
+ */
+ public function getAlnum(string $key, string $default = ''): string
+ {
+ return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default));
+ }
+
+ /**
+ * Returns the digits of the parameter value.
+ */
+ public function getDigits(string $key, string $default = ''): string
+ {
+ return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default));
+ }
+
+ /**
+ * Returns the parameter as string.
+ */
+ public function getString(string $key, string $default = ''): string
+ {
+ $value = $this->get($key, $default);
+ if (!\is_scalar($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key));
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * Returns the parameter value converted to integer.
+ */
+ public function getInt(string $key, int $default = 0): int
+ {
+ // In 7.0 remove the fallback to 0, in case of failure an exception will be thrown
+ return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]) ?: 0;
+ }
+
+ /**
+ * Returns the parameter value converted to boolean.
+ */
+ public function getBoolean(string $key, bool $default = false): bool
+ {
+ return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]);
+ }
+
+ /**
+ * Returns the parameter value converted to an enum.
+ *
+ * @template T of \BackedEnum
+ *
+ * @param class-string $class
+ * @param ?T $default
+ *
+ * @return ?T
+ */
+ public function getEnum(string $key, string $class, ?\BackedEnum $default = null): ?\BackedEnum
+ {
+ $value = $this->get($key);
+
+ if (null === $value) {
+ return $default;
+ }
+
+ try {
+ return $class::from($value);
+ } catch (\ValueError|\TypeError $e) {
+ throw new UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Filter key.
+ *
+ * @param int $filter FILTER_* constant
+ * @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants
+ *
+ * @see https://php.net/filter-var
+ */
+ public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
+ {
+ $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;
+ }
+
+ if (\is_object($value) && !$value instanceof \Stringable) {
+ throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key));
+ }
+
+ if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
+ throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
+ }
+
+ $options['flags'] ??= 0;
+ $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
+ $options['flags'] |= \FILTER_NULL_ON_FAILURE;
+
+ $value = filter_var($value, $filter, $options);
+
+ if (null !== $value || $nullOnFailure) {
+ return $value;
+ }
+
+ $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
+ $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter';
+ $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with';
+
+ trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw an "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, UnexpectedValueException::class);
+
+ return false;
+ }
+
+ /**
+ * Returns an iterator for parameters.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->parameters);
+ }
+
+ /**
+ * Returns the number of parameters.
+ */
+ public function count(): int
+ {
+ return \count($this->parameters);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/README.md b/www/libs/vendor/symfony/http-foundation/README.md
new file mode 100644
index 00000000..5cf90074
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/README.md
@@ -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.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)
diff --git a/www/libs/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/www/libs/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php
new file mode 100644
index 00000000..550090f9
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RateLimiter;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\RateLimiter\LimiterInterface;
+use Symfony\Component\RateLimiter\Policy\NoLimiter;
+use Symfony\Component\RateLimiter\RateLimit;
+
+/**
+ * An implementation of PeekableRequestRateLimiterInterface that
+ * fits most use-cases.
+ *
+ * @author Wouter de Jong
+ */
+abstract class AbstractRequestRateLimiter implements PeekableRequestRateLimiterInterface
+{
+ public function consume(Request $request): RateLimit
+ {
+ return $this->doConsume($request, 1);
+ }
+
+ public function peek(Request $request): RateLimit
+ {
+ return $this->doConsume($request, 0);
+ }
+
+ private function doConsume(Request $request, int $tokens): RateLimit
+ {
+ $limiters = $this->getLimiters($request);
+ if (0 === \count($limiters)) {
+ $limiters = [new NoLimiter()];
+ }
+
+ $minimalRateLimit = null;
+ foreach ($limiters as $limiter) {
+ $rateLimit = $limiter->consume($tokens);
+
+ $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit;
+ }
+
+ return $minimalRateLimit;
+ }
+
+ public function reset(Request $request): void
+ {
+ foreach ($this->getLimiters($request) as $limiter) {
+ $limiter->reset();
+ }
+ }
+
+ /**
+ * @return LimiterInterface[] a set of limiters using keys extracted from the request
+ */
+ abstract protected function getLimiters(Request $request): array;
+
+ private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit
+ {
+ if ($first->isAccepted() !== $second->isAccepted()) {
+ return $first->isAccepted() ? $second : $first;
+ }
+
+ $firstRemainingTokens = $first->getRemainingTokens();
+ $secondRemainingTokens = $second->getRemainingTokens();
+
+ if ($firstRemainingTokens === $secondRemainingTokens) {
+ return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first;
+ }
+
+ return $firstRemainingTokens > $secondRemainingTokens ? $second : $first;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php b/www/libs/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php
new file mode 100644
index 00000000..63471af2
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RateLimiter/PeekableRequestRateLimiterInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RateLimiter;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\RateLimiter\RateLimit;
+
+/**
+ * A request limiter which allows peeking ahead.
+ *
+ * This is valuable to reduce the cache backend load in scenarios
+ * like a login when we only want to consume a token on login failure,
+ * and where the majority of requests will be successful and thus not
+ * need to consume a token.
+ *
+ * This way we can peek ahead before allowing the request through, and
+ * only consume if the request failed (1 backend op). This is compared
+ * to always consuming and then resetting the limit if the request
+ * is successful (2 backend ops).
+ *
+ * @author Jordi Boggiano
+ */
+interface PeekableRequestRateLimiterInterface extends RequestRateLimiterInterface
+{
+ public function peek(Request $request): RateLimit;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php b/www/libs/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php
new file mode 100644
index 00000000..4c87a40a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RateLimiter;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\RateLimiter\RateLimit;
+
+/**
+ * A special type of limiter that deals with requests.
+ *
+ * This allows to limit on different types of information
+ * from the requests.
+ *
+ * @author Wouter de Jong
+ */
+interface RequestRateLimiterInterface
+{
+ public function consume(Request $request): RateLimit;
+
+ public function reset(Request $request): void;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RedirectResponse.php b/www/libs/vendor/symfony/http-foundation/RedirectResponse.php
new file mode 100644
index 00000000..408629e3
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RedirectResponse.php
@@ -0,0 +1,92 @@
+
+ *
+ * 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
+ */
+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 HTTP status code (302 "Found" 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');
+ }
+ }
+
+ /**
+ * Returns the target URL.
+ */
+ public function getTargetUrl(): string
+ {
+ return $this->targetUrl;
+ }
+
+ /**
+ * Sets the redirect target of this response.
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTargetUrl(string $url): static
+ {
+ if ('' === $url) {
+ throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
+ }
+
+ $this->targetUrl = $url;
+
+ $this->setContent(
+ sprintf('
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s.
+
+', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
+
+ $this->headers->set('Location', $url);
+ $this->headers->set('Content-Type', 'text/html; charset=utf-8');
+
+ return $this;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Request.php b/www/libs/vendor/symfony/http-foundation/Request.php
new file mode 100644
index 00000000..92201413
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Request.php
@@ -0,0 +1,2131 @@
+
+ *
+ * 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\Exception\BadRequestException;
+use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Exception\JsonException;
+use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(AcceptHeader::class);
+class_exists(FileBag::class);
+class_exists(HeaderBag::class);
+class_exists(HeaderUtils::class);
+class_exists(InputBag::class);
+class_exists(ParameterBag::class);
+class_exists(ServerBag::class);
+
+/**
+ * Request represents an HTTP request.
+ *
+ * The methods dealing with URL accept / return a raw path (% encoded):
+ * * getBasePath
+ * * getBaseUrl
+ * * getPathInfo
+ * * getRequestUri
+ * * getUri
+ * * getUriForPath
+ *
+ * @author Fabien Potencier
+ */
+class Request
+{
+ public const HEADER_FORWARDED = 0b000001; // When using RFC 7239
+ public const HEADER_X_FORWARDED_FOR = 0b000010;
+ public const HEADER_X_FORWARDED_HOST = 0b000100;
+ public const HEADER_X_FORWARDED_PROTO = 0b001000;
+ public const HEADER_X_FORWARDED_PORT = 0b010000;
+ public const HEADER_X_FORWARDED_PREFIX = 0b100000;
+
+ public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host
+ public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy
+
+ public const METHOD_HEAD = 'HEAD';
+ public const METHOD_GET = 'GET';
+ public const METHOD_POST = 'POST';
+ public const METHOD_PUT = 'PUT';
+ public const METHOD_PATCH = 'PATCH';
+ public const METHOD_DELETE = 'DELETE';
+ public const METHOD_PURGE = 'PURGE';
+ public const METHOD_OPTIONS = 'OPTIONS';
+ public const METHOD_TRACE = 'TRACE';
+ public const METHOD_CONNECT = 'CONNECT';
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedProxies = [];
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHostPatterns = [];
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHosts = [];
+
+ protected static $httpMethodParameterOverride = false;
+
+ /**
+ * Custom parameters.
+ *
+ * @var ParameterBag
+ */
+ public $attributes;
+
+ /**
+ * Request body parameters ($_POST).
+ *
+ * @see getPayload() for portability between content types
+ *
+ * @var InputBag
+ */
+ public $request;
+
+ /**
+ * Query string parameters ($_GET).
+ *
+ * @var InputBag
+ */
+ public $query;
+
+ /**
+ * Server and execution environment parameters ($_SERVER).
+ *
+ * @var ServerBag
+ */
+ public $server;
+
+ /**
+ * Uploaded files ($_FILES).
+ *
+ * @var FileBag
+ */
+ public $files;
+
+ /**
+ * Cookies ($_COOKIE).
+ *
+ * @var InputBag
+ */
+ public $cookies;
+
+ /**
+ * Headers (taken from the $_SERVER).
+ *
+ * @var HeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string|resource|false|null
+ */
+ protected $content;
+
+ /**
+ * @var string[]|null
+ */
+ protected $languages;
+
+ /**
+ * @var string[]|null
+ */
+ protected $charsets;
+
+ /**
+ * @var string[]|null
+ */
+ protected $encodings;
+
+ /**
+ * @var string[]|null
+ */
+ protected $acceptableContentTypes;
+
+ /**
+ * @var string|null
+ */
+ protected $pathInfo;
+
+ /**
+ * @var string|null
+ */
+ protected $requestUri;
+
+ /**
+ * @var string|null
+ */
+ protected $baseUrl;
+
+ /**
+ * @var string|null
+ */
+ protected $basePath;
+
+ /**
+ * @var string|null
+ */
+ protected $method;
+
+ /**
+ * @var string|null
+ */
+ protected $format;
+
+ /**
+ * @var SessionInterface|callable():SessionInterface|null
+ */
+ protected $session;
+
+ /**
+ * @var string|null
+ */
+ protected $locale;
+
+ /**
+ * @var string
+ */
+ protected $defaultLocale = 'en';
+
+ /**
+ * @var array|null
+ */
+ protected static $formats;
+
+ protected static $requestFactory;
+
+ private ?string $preferredFormat = null;
+ private bool $isHostValid = true;
+ private bool $isForwardedValid = true;
+ private bool $isSafeContentPreferred;
+
+ private array $trustedValuesCache = [];
+
+ private static int $trustedHeaderSet = -1;
+
+ private const FORWARDED_PARAMS = [
+ self::HEADER_X_FORWARDED_FOR => 'for',
+ self::HEADER_X_FORWARDED_HOST => 'host',
+ self::HEADER_X_FORWARDED_PROTO => 'proto',
+ self::HEADER_X_FORWARDED_PORT => 'host',
+ ];
+
+ /**
+ * Names for headers that can be trusted when
+ * using trusted proxies.
+ *
+ * The FORWARDED header is the standard as of rfc7239.
+ *
+ * The other headers are non-standard, but widely used
+ * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+ */
+ private const TRUSTED_HEADERS = [
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
+ self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
+ self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
+ ];
+
+ /** @var bool */
+ private $isIisRewrite = false;
+
+ /**
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
+ {
+ $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets the parameters for this request.
+ *
+ * This method also re-initializes all properties.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ *
+ * @return void
+ */
+ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
+ {
+ $this->request = new InputBag($request);
+ $this->query = new InputBag($query);
+ $this->attributes = new ParameterBag($attributes);
+ $this->cookies = new InputBag($cookies);
+ $this->files = new FileBag($files);
+ $this->server = new ServerBag($server);
+ $this->headers = new HeaderBag($this->server->getHeaders());
+
+ $this->content = $content;
+ $this->languages = null;
+ $this->charsets = null;
+ $this->encodings = null;
+ $this->acceptableContentTypes = null;
+ $this->pathInfo = null;
+ $this->requestUri = null;
+ $this->baseUrl = null;
+ $this->basePath = null;
+ $this->method = null;
+ $this->format = null;
+ }
+
+ /**
+ * Creates a new request with values from PHP's super globals.
+ */
+ public static function createFromGlobals(): static
+ {
+ $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);
+
+ if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded')
+ && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
+ ) {
+ parse_str($request->getContent(), $data);
+ $request->request = new InputBag($data);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Creates a Request based on a given URI and configuration.
+ *
+ * The information contained in the URI always take precedence
+ * over the other information (server and parameters).
+ *
+ * @param string $uri The URI
+ * @param string $method The HTTP method
+ * @param array $parameters The query (GET) or request (POST) parameters
+ * @param array $cookies The request cookies ($_COOKIE)
+ * @param array $files The request files ($_FILES)
+ * @param array $server The server parameters ($_SERVER)
+ * @param string|resource|null $content The raw body data
+ *
+ * @throws BadRequestException When the URI is invalid
+ */
+ public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null): static
+ {
+ $server = array_replace([
+ 'SERVER_NAME' => 'localhost',
+ 'SERVER_PORT' => 80,
+ 'HTTP_HOST' => 'localhost',
+ 'HTTP_USER_AGENT' => 'Symfony',
+ 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
+ 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '',
+ 'SCRIPT_FILENAME' => '',
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'REQUEST_TIME' => time(),
+ 'REQUEST_TIME_FLOAT' => microtime(true),
+ ], $server);
+
+ $server['PATH_INFO'] = '';
+ $server['REQUEST_METHOD'] = strtoupper($method);
+
+ if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
+ throw new BadRequestException('Invalid URI.');
+ }
+
+ if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
+ throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
+ }
+ if (\strlen($uri) !== strcspn($uri, "\r\n\t")) {
+ throw new BadRequestException('Invalid URI: A URI cannot contain CR/LF/TAB characters.');
+ }
+ if ('' !== $uri && (\ord($uri[0]) <= 32 || \ord($uri[-1]) <= 32)) {
+ throw new BadRequestException('Invalid URI: A URI must not start nor end with ASCII control characters or spaces.');
+ }
+
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] .= ':'.$components['port'];
+ }
+
+ if (isset($components['user'])) {
+ $server['PHP_AUTH_USER'] = $components['user'];
+ }
+
+ if (isset($components['pass'])) {
+ $server['PHP_AUTH_PW'] = $components['pass'];
+ }
+
+ if (!isset($components['path'])) {
+ $components['path'] = '/';
+ }
+
+ switch (strtoupper($method)) {
+ case 'POST':
+ case 'PUT':
+ case 'DELETE':
+ if (!isset($server['CONTENT_TYPE'])) {
+ $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ }
+ // no break
+ case 'PATCH':
+ $request = $parameters;
+ $query = [];
+ break;
+ default:
+ $request = [];
+ $query = $parameters;
+ break;
+ }
+
+ $queryString = '';
+ if (isset($components['query'])) {
+ parse_str(html_entity_decode($components['query']), $qs);
+
+ if ($query) {
+ $query = array_replace($qs, $query);
+ $queryString = http_build_query($query, '', '&');
+ } else {
+ $query = $qs;
+ $queryString = $components['query'];
+ }
+ } elseif ($query) {
+ $queryString = http_build_query($query, '', '&');
+ }
+
+ $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
+ $server['QUERY_STRING'] = $queryString;
+
+ return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets a callable able to create a Request instance.
+ *
+ * This is mainly useful when you need to override the Request class
+ * to keep BC with an existing system. It should not be used for any
+ * other purpose.
+ *
+ * @return void
+ */
+ public static function setFactory(?callable $callable)
+ {
+ self::$requestFactory = $callable;
+ }
+
+ /**
+ * Clones a request and overrides some of its parameters.
+ *
+ * @param array|null $query The GET parameters
+ * @param array|null $request The POST parameters
+ * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array|null $cookies The COOKIE parameters
+ * @param array|null $files The FILES parameters
+ * @param array|null $server The SERVER parameters
+ */
+ public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null): static
+ {
+ $dup = clone $this;
+ if (null !== $query) {
+ $dup->query = new InputBag($query);
+ }
+ if (null !== $request) {
+ $dup->request = new InputBag($request);
+ }
+ if (null !== $attributes) {
+ $dup->attributes = new ParameterBag($attributes);
+ }
+ if (null !== $cookies) {
+ $dup->cookies = new InputBag($cookies);
+ }
+ if (null !== $files) {
+ $dup->files = new FileBag($files);
+ }
+ if (null !== $server) {
+ $dup->server = new ServerBag($server);
+ $dup->headers = new HeaderBag($dup->server->getHeaders());
+ }
+ $dup->languages = null;
+ $dup->charsets = null;
+ $dup->encodings = null;
+ $dup->acceptableContentTypes = null;
+ $dup->pathInfo = null;
+ $dup->requestUri = null;
+ $dup->baseUrl = null;
+ $dup->basePath = null;
+ $dup->method = null;
+ $dup->format = null;
+
+ if (!$dup->get('_format') && $this->get('_format')) {
+ $dup->attributes->set('_format', $this->get('_format'));
+ }
+
+ if (!$dup->getRequestFormat(null)) {
+ $dup->setRequestFormat($this->getRequestFormat(null));
+ }
+
+ return $dup;
+ }
+
+ /**
+ * Clones the current request.
+ *
+ * Note that the session is not cloned as duplicated requests
+ * are most of the time sub-requests of the main one.
+ */
+ public function __clone()
+ {
+ $this->query = clone $this->query;
+ $this->request = clone $this->request;
+ $this->attributes = clone $this->attributes;
+ $this->cookies = clone $this->cookies;
+ $this->files = clone $this->files;
+ $this->server = clone $this->server;
+ $this->headers = clone $this->headers;
+ }
+
+ public function __toString(): string
+ {
+ $content = $this->getContent();
+
+ $cookieHeader = '';
+ $cookies = [];
+
+ foreach ($this->cookies as $k => $v) {
+ $cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v";
+ }
+
+ if ($cookies) {
+ $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+ }
+
+ return
+ sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+ $this->headers.
+ $cookieHeader."\r\n".
+ $content;
+ }
+
+ /**
+ * Overrides the PHP global variables according to this request instance.
+ *
+ * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
+ * $_FILES is never overridden, see rfc1867
+ *
+ * @return void
+ */
+ public function overrideGlobals()
+ {
+ $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
+
+ $_GET = $this->query->all();
+ $_POST = $this->request->all();
+ $_SERVER = $this->server->all();
+ $_COOKIE = $this->cookies->all();
+
+ foreach ($this->headers->all() as $key => $value) {
+ $key = strtoupper(str_replace('-', '_', $key));
+ if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) {
+ $_SERVER[$key] = implode(', ', $value);
+ } else {
+ $_SERVER['HTTP_'.$key] = implode(', ', $value);
+ }
+ }
+
+ $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
+
+ $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order');
+ $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
+
+ $_REQUEST = [[]];
+
+ foreach (str_split($requestOrder) as $order) {
+ $_REQUEST[] = $request[$order];
+ }
+
+ $_REQUEST = array_merge(...$_REQUEST);
+ }
+
+ /**
+ * Sets a list of trusted proxies.
+ *
+ * You should only list the reverse proxies that you manage directly.
+ *
+ * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR']
+ * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+ *
+ * @return void
+ */
+ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet)
+ {
+ self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) {
+ if ('REMOTE_ADDR' !== $proxy) {
+ $proxies[] = $proxy;
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+ $proxies[] = $_SERVER['REMOTE_ADDR'];
+ }
+
+ return $proxies;
+ }, []);
+ self::$trustedHeaderSet = $trustedHeaderSet;
+ }
+
+ /**
+ * Gets the list of trusted proxies.
+ *
+ * @return string[]
+ */
+ public static function getTrustedProxies(): array
+ {
+ return self::$trustedProxies;
+ }
+
+ /**
+ * Gets the set of trusted headers from trusted proxies.
+ *
+ * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
+ */
+ public static function getTrustedHeaderSet(): int
+ {
+ return self::$trustedHeaderSet;
+ }
+
+ /**
+ * Sets a list of trusted host patterns.
+ *
+ * You should only list the hosts you manage using regexs.
+ *
+ * @param array $hostPatterns A list of trusted host patterns
+ *
+ * @return void
+ */
+ public static function setTrustedHosts(array $hostPatterns)
+ {
+ self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns);
+ // we need to reset trusted hosts on trusted host patterns change
+ self::$trustedHosts = [];
+ }
+
+ /**
+ * Gets the list of trusted host patterns.
+ *
+ * @return string[]
+ */
+ public static function getTrustedHosts(): array
+ {
+ return self::$trustedHostPatterns;
+ }
+
+ /**
+ * Normalizes a query string.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized,
+ * have consistent escaping and unneeded delimiters are removed.
+ */
+ public static function normalizeQueryString(?string $qs): string
+ {
+ if ('' === ($qs ?? '')) {
+ return '';
+ }
+
+ $qs = HeaderUtils::parseQuery($qs);
+ ksort($qs);
+
+ return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Enables support for the _method request parameter to determine the intended HTTP method.
+ *
+ * Be warned that enabling this feature might lead to CSRF issues in your code.
+ * Check that you are using CSRF tokens when required.
+ * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
+ * and used to send a "PUT" or "DELETE" request via the _method request parameter.
+ * If these methods are not protected against CSRF, this presents a possible vulnerability.
+ *
+ * The HTTP method can only be overridden when the real HTTP method is POST.
+ *
+ * @return void
+ */
+ public static function enableHttpMethodParameterOverride()
+ {
+ self::$httpMethodParameterOverride = true;
+ }
+
+ /**
+ * Checks whether support for the _method request parameter is enabled.
+ */
+ public static function getHttpMethodParameterOverride(): bool
+ {
+ return self::$httpMethodParameterOverride;
+ }
+
+ /**
+ * Gets a "parameter" value from any bag.
+ *
+ * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
+ * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
+ * public property instead (attributes, query, request).
+ *
+ * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST
+ *
+ * @internal use explicit input sources instead
+ */
+ public function get(string $key, mixed $default = null): mixed
+ {
+ if ($this !== $result = $this->attributes->get($key, $this)) {
+ return $result;
+ }
+
+ if ($this->query->has($key)) {
+ return $this->query->all()[$key];
+ }
+
+ if ($this->request->has($key)) {
+ return $this->request->all()[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets the Session.
+ *
+ * @throws SessionNotFoundException When session is not set properly
+ */
+ public function getSession(): SessionInterface
+ {
+ $session = $this->session;
+ if (!$session instanceof SessionInterface && null !== $session) {
+ $this->setSession($session = $session());
+ }
+
+ if (null === $session) {
+ throw new SessionNotFoundException('Session has not been set.');
+ }
+
+ return $session;
+ }
+
+ /**
+ * Whether the request contains a Session which was started in one of the
+ * previous requests.
+ */
+ public function hasPreviousSession(): bool
+ {
+ // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
+ return $this->hasSession() && $this->cookies->has($this->getSession()->getName());
+ }
+
+ /**
+ * Whether the request contains a Session object.
+ *
+ * This method does not give any information about the state of the session object,
+ * like whether the session is started or not. It is just a way to check if this Request
+ * is associated with a Session instance.
+ *
+ * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory`
+ */
+ public function hasSession(bool $skipIfUninitialized = false): bool
+ {
+ return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface);
+ }
+
+ /**
+ * @return void
+ */
+ public function setSession(SessionInterface $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * @internal
+ *
+ * @param callable(): SessionInterface $factory
+ */
+ public function setSessionFactory(callable $factory): void
+ {
+ $this->session = $factory(...);
+ }
+
+ /**
+ * Returns the client IP addresses.
+ *
+ * In the returned array the most trusted IP address is first, and the
+ * least trusted one last. The "real" client IP address is the last one,
+ * but this is also the least trusted one. Trusted proxies are stripped.
+ *
+ * Use this method carefully; you should use getClientIp() instead.
+ *
+ * @see getClientIp()
+ */
+ public function getClientIps(): array
+ {
+ $ip = $this->server->get('REMOTE_ADDR');
+
+ if (!$this->isFromTrustedProxy()) {
+ return [$ip];
+ }
+
+ return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip];
+ }
+
+ /**
+ * Returns the client IP address.
+ *
+ * This method can read the client IP address from the "X-Forwarded-For" header
+ * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
+ * header value is a comma+space separated list of IP addresses, the left-most
+ * being the original client, and each successive proxy that passed the request
+ * adding the IP address where it received the request from.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-For",
+ * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
+ *
+ * @see getClientIps()
+ * @see https://wikipedia.org/wiki/X-Forwarded-For
+ */
+ public function getClientIp(): ?string
+ {
+ $ipAddresses = $this->getClientIps();
+
+ return $ipAddresses[0];
+ }
+
+ /**
+ * Returns current script name.
+ */
+ public function getScriptName(): string
+ {
+ return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
+ }
+
+ /**
+ * Returns the path being requested relative to the executed script.
+ *
+ * The path info always starts with a /.
+ *
+ * Suppose this request is instantiated from /mysite on localhost:
+ *
+ * * http://localhost/mysite returns an empty string
+ * * http://localhost/mysite/about returns '/about'
+ * * http://localhost/mysite/enco%20ded returns '/enco%20ded'
+ * * http://localhost/mysite/about?var=1 returns '/about'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getPathInfo(): string
+ {
+ return $this->pathInfo ??= $this->preparePathInfo();
+ }
+
+ /**
+ * Returns the root path from which this request is executed.
+ *
+ * Suppose that an index.php file instantiates this request object:
+ *
+ * * http://localhost/index.php returns an empty string
+ * * http://localhost/index.php/page returns an empty string
+ * * http://localhost/web/index.php returns '/web'
+ * * http://localhost/we%20b/index.php returns '/we%20b'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getBasePath(): string
+ {
+ return $this->basePath ??= $this->prepareBasePath();
+ }
+
+ /**
+ * Returns the root URL from which this request is executed.
+ *
+ * The base URL never ends with a /.
+ *
+ * This is similar to getBasePath(), except that it also includes the
+ * script filename (e.g. index.php) if one exists.
+ *
+ * @return string The raw URL (i.e. not urldecoded)
+ */
+ public function getBaseUrl(): string
+ {
+ $trustedPrefix = '';
+
+ // the proxy prefix must be prepended to any prefix being needed at the webserver level
+ if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) {
+ $trustedPrefix = rtrim($trustedPrefixValues[0], '/');
+ }
+
+ return $trustedPrefix.$this->getBaseUrlReal();
+ }
+
+ /**
+ * Returns the real base URL received by the webserver from which this request is executed.
+ * The URL does not include trusted reverse proxy prefix.
+ *
+ * @return string The raw URL (i.e. not urldecoded)
+ */
+ private function getBaseUrlReal(): string
+ {
+ return $this->baseUrl ??= $this->prepareBaseUrl();
+ }
+
+ /**
+ * Gets the request's scheme.
+ */
+ public function getScheme(): string
+ {
+ return $this->isSecure() ? 'https' : 'http';
+ }
+
+ /**
+ * Returns the port on which the request is made.
+ *
+ * This method can read the client port from the "X-Forwarded-Port" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Port" header must contain the client port.
+ *
+ * @return int|string|null Can be a string if fetched from the server bag
+ */
+ public function getPort(): int|string|null
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) {
+ $host = $host[0];
+ } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ return $this->server->get('SERVER_PORT');
+ }
+
+ if ('[' === $host[0]) {
+ $pos = strpos($host, ':', strrpos($host, ']'));
+ } else {
+ $pos = strrpos($host, ':');
+ }
+
+ if (false !== $pos && $port = substr($host, $pos + 1)) {
+ return (int) $port;
+ }
+
+ return 'https' === $this->getScheme() ? 443 : 80;
+ }
+
+ /**
+ * Returns the user.
+ */
+ public function getUser(): ?string
+ {
+ return $this->headers->get('PHP_AUTH_USER');
+ }
+
+ /**
+ * Returns the password.
+ */
+ public function getPassword(): ?string
+ {
+ return $this->headers->get('PHP_AUTH_PW');
+ }
+
+ /**
+ * Gets the user info.
+ *
+ * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server
+ */
+ public function getUserInfo(): ?string
+ {
+ $userinfo = $this->getUser();
+
+ $pass = $this->getPassword();
+ if ('' != $pass) {
+ $userinfo .= ":$pass";
+ }
+
+ return $userinfo;
+ }
+
+ /**
+ * Returns the HTTP host being requested.
+ *
+ * The port name will be appended to the host if it's non-standard.
+ */
+ public function getHttpHost(): string
+ {
+ $scheme = $this->getScheme();
+ $port = $this->getPort();
+
+ if (('http' === $scheme && 80 == $port) || ('https' === $scheme && 443 == $port)) {
+ return $this->getHost();
+ }
+
+ return $this->getHost().':'.$port;
+ }
+
+ /**
+ * Returns the requested URI (path and query string).
+ *
+ * @return string The raw URI (i.e. not URI decoded)
+ */
+ public function getRequestUri(): string
+ {
+ return $this->requestUri ??= $this->prepareRequestUri();
+ }
+
+ /**
+ * Gets the scheme and HTTP host.
+ *
+ * If the URL was called with basic authentication, the user
+ * and the password are not added to the generated string.
+ */
+ public function getSchemeAndHttpHost(): string
+ {
+ return $this->getScheme().'://'.$this->getHttpHost();
+ }
+
+ /**
+ * Generates a normalized URI (URL) for the Request.
+ *
+ * @see getQueryString()
+ */
+ public function getUri(): string
+ {
+ if (null !== $qs = $this->getQueryString()) {
+ $qs = '?'.$qs;
+ }
+
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
+ }
+
+ /**
+ * Generates a normalized URI for the given path.
+ *
+ * @param string $path A path to use instead of the current one
+ */
+ public function getUriForPath(string $path): string
+ {
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
+ }
+
+ /**
+ * Returns the path as relative reference from the current Request path.
+ *
+ * Only the URIs path component (no schema, host etc.) is relevant and must be given.
+ * Both paths must be absolute and not contain relative parts.
+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+ * Furthermore, they can be used to reduce the link size in documents.
+ *
+ * Example target paths, given a base path of "/a/b/c/d":
+ * - "/a/b/c/d" -> ""
+ * - "/a/b/c/" -> "./"
+ * - "/a/b/" -> "../"
+ * - "/a/b/c/other" -> "other"
+ * - "/a/x/y" -> "../../x/y"
+ */
+ public function getRelativeUriForPath(string $path): string
+ {
+ // be sure that we are dealing with an absolute path
+ if (!isset($path[0]) || '/' !== $path[0]) {
+ return $path;
+ }
+
+ if ($path === $basePath = $this->getPathInfo()) {
+ return '';
+ }
+
+ $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+ $targetDirs = explode('/', substr($path, 1));
+ array_pop($sourceDirs);
+ $targetFile = array_pop($targetDirs);
+
+ foreach ($sourceDirs as $i => $dir) {
+ if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+ unset($sourceDirs[$i], $targetDirs[$i]);
+ } else {
+ break;
+ }
+ }
+
+ $targetDirs[] = $targetFile;
+ $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs);
+
+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+ // (see https://tools.ietf.org/html/rfc3986#section-4.2).
+ return !isset($path[0]) || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
+ /**
+ * Generates the normalized query string for the Request.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized
+ * and have consistent escaping.
+ */
+ public function getQueryString(): ?string
+ {
+ $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
+
+ return '' === $qs ? null : $qs;
+ }
+
+ /**
+ * Checks whether the request is secure or not.
+ *
+ * This method can read the client protocol from the "X-Forwarded-Proto" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
+ */
+ public function isSecure(): bool
+ {
+ if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) {
+ return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
+ }
+
+ $https = $this->server->get('HTTPS');
+
+ return !empty($https) && 'off' !== strtolower($https);
+ }
+
+ /**
+ * Returns the host name.
+ *
+ * This method can read the client host name from the "X-Forwarded-Host" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Host" header must contain the client host name.
+ *
+ * @throws SuspiciousOperationException when the host name is invalid or not trusted
+ */
+ public function getHost(): string
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ if (!$host = $this->server->get('SERVER_NAME')) {
+ $host = $this->server->get('SERVER_ADDR', '');
+ }
+ }
+
+ // trim and remove port number from host
+ // host is lowercase as per RFC 952/2181
+ $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+ // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+ // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+ // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
+ if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+ }
+
+ if (\count(self::$trustedHostPatterns) > 0) {
+ // to avoid host header injection attacks, you should provide a list of trusted host patterns
+
+ if (\in_array($host, self::$trustedHosts)) {
+ return $host;
+ }
+
+ foreach (self::$trustedHostPatterns as $pattern) {
+ if (preg_match($pattern, $host)) {
+ self::$trustedHosts[] = $host;
+
+ return $host;
+ }
+ }
+
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+ }
+
+ return $host;
+ }
+
+ /**
+ * Sets the request method.
+ *
+ * @return void
+ */
+ public function setMethod(string $method)
+ {
+ $this->method = null;
+ $this->server->set('REQUEST_METHOD', $method);
+ }
+
+ /**
+ * Gets the request "intended" method.
+ *
+ * If the X-HTTP-Method-Override header is set, and if the method is a POST,
+ * then it is used to determine the "real" intended HTTP method.
+ *
+ * The _method request parameter can also be used to determine the HTTP method,
+ * but only if enableHttpMethodParameterOverride() has been called.
+ *
+ * The method is always an uppercased string.
+ *
+ * @see getRealMethod()
+ */
+ public function getMethod(): string
+ {
+ if (null !== $this->method) {
+ return $this->method;
+ }
+
+ $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+
+ if ('POST' !== $this->method) {
+ return $this->method;
+ }
+
+ $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
+
+ if (!$method && self::$httpMethodParameterOverride) {
+ $method = $this->request->get('_method', $this->query->get('_method', 'POST'));
+ }
+
+ if (!\is_string($method)) {
+ return $this->method;
+ }
+
+ $method = strtoupper($method);
+
+ if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) {
+ return $this->method = $method;
+ }
+
+ if (!preg_match('/^[A-Z]++$/D', $method)) {
+ throw new SuspiciousOperationException('Invalid HTTP method override.');
+ }
+
+ return $this->method = $method;
+ }
+
+ /**
+ * Gets the "real" request method.
+ *
+ * @see getMethod()
+ */
+ public function getRealMethod(): string
+ {
+ return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+ }
+
+ /**
+ * Gets the mime type associated with the format.
+ */
+ public function getMimeType(string $format): ?string
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
+ }
+
+ /**
+ * Gets the mime types associated with the format.
+ *
+ * @return string[]
+ */
+ public static function getMimeTypes(string $format): array
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return static::$formats[$format] ?? [];
+ }
+
+ /**
+ * Gets the format associated with the mime type.
+ */
+ public function getFormat(?string $mimeType): ?string
+ {
+ $canonicalMimeType = null;
+ if ($mimeType && false !== $pos = strpos($mimeType, ';')) {
+ $canonicalMimeType = trim(substr($mimeType, 0, $pos));
+ }
+
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ foreach (static::$formats as $format => $mimeTypes) {
+ if (\in_array($mimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Associates a format with mime types.
+ *
+ * @param string|string[] $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
+ *
+ * @return void
+ */
+ public function setFormat(?string $format, string|array $mimeTypes)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes];
+ }
+
+ /**
+ * Gets the request format.
+ *
+ * Here is the process to determine the format:
+ *
+ * * format defined by the user (with setRequestFormat())
+ * * _format request attribute
+ * * $default
+ *
+ * @see getPreferredFormat
+ */
+ public function getRequestFormat(?string $default = 'html'): ?string
+ {
+ $this->format ??= $this->attributes->get('_format');
+
+ return $this->format ?? $default;
+ }
+
+ /**
+ * Sets the request format.
+ *
+ * @return void
+ */
+ public function setRequestFormat(?string $format)
+ {
+ $this->format = $format;
+ }
+
+ /**
+ * Gets the usual name of the format associated with the request's media type (provided in the Content-Type header).
+ *
+ * @deprecated since Symfony 6.2, use getContentTypeFormat() instead
+ */
+ public function getContentType(): ?string
+ {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s()" method is deprecated, use "getContentTypeFormat()" instead.', __METHOD__);
+
+ return $this->getContentTypeFormat();
+ }
+
+ /**
+ * Gets the usual name of the format associated with the request's media type (provided in the Content-Type header).
+ *
+ * @see Request::$formats
+ */
+ public function getContentTypeFormat(): ?string
+ {
+ return $this->getFormat($this->headers->get('CONTENT_TYPE', ''));
+ }
+
+ /**
+ * Sets the default locale.
+ *
+ * @return void
+ */
+ public function setDefaultLocale(string $locale)
+ {
+ $this->defaultLocale = $locale;
+
+ if (null === $this->locale) {
+ $this->setPhpDefaultLocale($locale);
+ }
+ }
+
+ /**
+ * Get the default locale.
+ */
+ public function getDefaultLocale(): string
+ {
+ return $this->defaultLocale;
+ }
+
+ /**
+ * Sets the locale.
+ *
+ * @return void
+ */
+ public function setLocale(string $locale)
+ {
+ $this->setPhpDefaultLocale($this->locale = $locale);
+ }
+
+ /**
+ * Get the locale.
+ */
+ public function getLocale(): string
+ {
+ return $this->locale ?? $this->defaultLocale;
+ }
+
+ /**
+ * Checks if the request method is of specified type.
+ *
+ * @param string $method Uppercase request method (GET, POST etc)
+ */
+ public function isMethod(string $method): bool
+ {
+ return $this->getMethod() === strtoupper($method);
+ }
+
+ /**
+ * Checks whether or not the method is safe.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+ */
+ public function isMethodSafe(): bool
+ {
+ return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
+ }
+
+ /**
+ * Checks whether or not the method is idempotent.
+ */
+ public function isMethodIdempotent(): bool
+ {
+ return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']);
+ }
+
+ /**
+ * Checks whether the method is cacheable or not.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+ */
+ public function isMethodCacheable(): bool
+ {
+ return \in_array($this->getMethod(), ['GET', 'HEAD']);
+ }
+
+ /**
+ * Returns the protocol version.
+ *
+ * If the application is behind a proxy, the protocol version used in the
+ * requests between the client and the proxy and between the proxy and the
+ * server might be different. This returns the former (from the "Via" header)
+ * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
+ * the latter (from the "SERVER_PROTOCOL" server parameter).
+ */
+ public function getProtocolVersion(): ?string
+ {
+ if ($this->isFromTrustedProxy()) {
+ preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches);
+
+ if ($matches) {
+ return 'HTTP/'.$matches[2];
+ }
+ }
+
+ return $this->server->get('SERVER_PROTOCOL');
+ }
+
+ /**
+ * Returns the request body content.
+ *
+ * @param bool $asResource If true, a resource will be returned
+ *
+ * @return string|resource
+ *
+ * @psalm-return ($asResource is true ? resource : string)
+ */
+ public function getContent(bool $asResource = false)
+ {
+ $currentContentIsResource = \is_resource($this->content);
+
+ if (true === $asResource) {
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return $this->content;
+ }
+
+ // Content passed in parameter (test)
+ if (\is_string($this->content)) {
+ $resource = fopen('php://temp', 'r+');
+ fwrite($resource, $this->content);
+ rewind($resource);
+
+ return $resource;
+ }
+
+ $this->content = false;
+
+ return fopen('php://input', 'r');
+ }
+
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return stream_get_contents($this->content);
+ }
+
+ if (null === $this->content || false === $this->content) {
+ $this->content = file_get_contents('php://input');
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * Gets the decoded form or json request body.
+ *
+ * @throws JsonException When the body cannot be decoded to an array
+ */
+ public function getPayload(): InputBag
+ {
+ if ($this->request->count()) {
+ return clone $this->request;
+ }
+
+ if ('' === $content = $this->getContent()) {
+ return new InputBag([]);
+ }
+
+ try {
+ $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ throw new JsonException('Could not decode request body.', $e->getCode(), $e);
+ }
+
+ if (!\is_array($content)) {
+ throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+ }
+
+ return new InputBag($content);
+ }
+
+ /**
+ * Gets the request body decoded as array, typically from a JSON payload.
+ *
+ * @see getPayload() for portability between content types
+ *
+ * @throws JsonException When the body cannot be decoded to an array
+ */
+ public function toArray(): array
+ {
+ if ('' === $content = $this->getContent()) {
+ throw new JsonException('Request body is empty.');
+ }
+
+ try {
+ $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ throw new JsonException('Could not decode request body.', $e->getCode(), $e);
+ }
+
+ if (!\is_array($content)) {
+ throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
+ }
+
+ return $content;
+ }
+
+ /**
+ * Gets the Etags.
+ */
+ public function getETags(): array
+ {
+ return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY);
+ }
+
+ public function isNoCache(): bool
+ {
+ return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
+ }
+
+ /**
+ * Gets the preferred format for the response by inspecting, in the following order:
+ * * the request format set using setRequestFormat;
+ * * the values of the Accept HTTP header.
+ *
+ * Note that if you use this method, you should send the "Vary: Accept" header
+ * in the response to prevent any issues with intermediary HTTP caches.
+ */
+ public function getPreferredFormat(?string $default = 'html'): ?string
+ {
+ if ($this->preferredFormat ??= $this->getRequestFormat(null)) {
+ return $this->preferredFormat;
+ }
+
+ foreach ($this->getAcceptableContentTypes() as $mimeType) {
+ if ($this->preferredFormat = $this->getFormat($mimeType)) {
+ return $this->preferredFormat;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Returns the preferred language.
+ *
+ * @param string[] $locales An array of ordered available locales
+ */
+ public function getPreferredLanguage(?array $locales = null): ?string
+ {
+ $preferredLanguages = $this->getLanguages();
+
+ if (empty($locales)) {
+ return $preferredLanguages[0] ?? null;
+ }
+
+ if (!$preferredLanguages) {
+ return $locales[0];
+ }
+
+ $extendedPreferredLanguages = [];
+ foreach ($preferredLanguages as $language) {
+ $extendedPreferredLanguages[] = $language;
+ if (false !== $position = strpos($language, '_')) {
+ $superLanguage = substr($language, 0, $position);
+ if (!\in_array($superLanguage, $preferredLanguages)) {
+ $extendedPreferredLanguages[] = $superLanguage;
+ }
+ }
+ }
+
+ $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
+
+ return $preferredLanguages[0] ?? $locales[0];
+ }
+
+ /**
+ * Gets a list of languages acceptable by the client browser ordered in the user browser preferences.
+ *
+ * @return string[]
+ */
+ public function getLanguages(): array
+ {
+ if (null !== $this->languages) {
+ return $this->languages;
+ }
+
+ $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
+ $this->languages = [];
+ foreach ($languages as $acceptHeaderItem) {
+ $lang = $acceptHeaderItem->getValue();
+ if (str_contains($lang, '-')) {
+ $codes = explode('-', $lang);
+ if ('i' === $codes[0]) {
+ // Language not listed in ISO 639 that are not variants
+ // of any listed language, which can be registered with the
+ // i-prefix, such as i-cherokee
+ if (\count($codes) > 1) {
+ $lang = $codes[1];
+ }
+ } else {
+ for ($i = 0, $max = \count($codes); $i < $max; ++$i) {
+ if (0 === $i) {
+ $lang = strtolower($codes[0]);
+ } else {
+ $lang .= '_'.strtoupper($codes[$i]);
+ }
+ }
+ }
+ }
+
+ $this->languages[] = $lang;
+ }
+
+ return $this->languages;
+ }
+
+ /**
+ * Gets a list of charsets acceptable by the client browser in preferable order.
+ *
+ * @return string[]
+ */
+ public function getCharsets(): array
+ {
+ return $this->charsets ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()));
+ }
+
+ /**
+ * Gets a list of encodings acceptable by the client browser in preferable order.
+ *
+ * @return string[]
+ */
+ public function getEncodings(): array
+ {
+ return $this->encodings ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()));
+ }
+
+ /**
+ * Gets a list of content types acceptable by the client browser in preferable order.
+ *
+ * @return string[]
+ */
+ public function getAcceptableContentTypes(): array
+ {
+ return $this->acceptableContentTypes ??= array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()));
+ }
+
+ /**
+ * Returns true if the request is an XMLHttpRequest.
+ *
+ * It works if your JavaScript library sets an X-Requested-With HTTP header.
+ * It is known to work with common JavaScript frameworks:
+ *
+ * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
+ */
+ public function isXmlHttpRequest(): bool
+ {
+ return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
+ }
+
+ /**
+ * Checks whether the client browser prefers safe content or not according to RFC8674.
+ *
+ * @see https://tools.ietf.org/html/rfc8674
+ */
+ public function preferSafeContent(): bool
+ {
+ if (isset($this->isSafeContentPreferred)) {
+ return $this->isSafeContentPreferred;
+ }
+
+ if (!$this->isSecure()) {
+ // see https://tools.ietf.org/html/rfc8674#section-3
+ return $this->isSafeContentPreferred = false;
+ }
+
+ return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe');
+ }
+
+ /*
+ * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
+ *
+ * Code subject to the new BSD license (https://framework.zend.com/license).
+ *
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/)
+ */
+
+ /**
+ * @return string
+ */
+ protected function prepareRequestUri()
+ {
+ $requestUri = '';
+
+ if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) {
+ // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
+ $requestUri = $this->server->get('UNENCODED_URL');
+ $this->server->remove('UNENCODED_URL');
+ } elseif ($this->server->has('REQUEST_URI')) {
+ $requestUri = $this->server->get('REQUEST_URI');
+
+ if ('' !== $requestUri && '/' === $requestUri[0]) {
+ // To only use path and query remove the fragment.
+ if (false !== $pos = strpos($requestUri, '#')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ } else {
+ // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path,
+ // only use URL path.
+ $uriComponents = parse_url($requestUri);
+
+ if (isset($uriComponents['path'])) {
+ $requestUri = $uriComponents['path'];
+ }
+
+ if (isset($uriComponents['query'])) {
+ $requestUri .= '?'.$uriComponents['query'];
+ }
+ }
+ } elseif ($this->server->has('ORIG_PATH_INFO')) {
+ // IIS 5.0, PHP as CGI
+ $requestUri = $this->server->get('ORIG_PATH_INFO');
+ if ('' != $this->server->get('QUERY_STRING')) {
+ $requestUri .= '?'.$this->server->get('QUERY_STRING');
+ }
+ $this->server->remove('ORIG_PATH_INFO');
+ }
+
+ // normalize the request URI to ease creating sub-requests from this request
+ $this->server->set('REQUEST_URI', $requestUri);
+
+ return $requestUri;
+ }
+
+ /**
+ * Prepares the base URL.
+ */
+ protected function prepareBaseUrl(): string
+ {
+ $filename = basename($this->server->get('SCRIPT_FILENAME', ''));
+
+ if (basename($this->server->get('SCRIPT_NAME', '')) === $filename) {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+ } elseif (basename($this->server->get('PHP_SELF', '')) === $filename) {
+ $baseUrl = $this->server->get('PHP_SELF');
+ } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) {
+ $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
+ } else {
+ // Backtrack up the script_filename to find the portion matching
+ // php_self
+ $path = $this->server->get('PHP_SELF', '');
+ $file = $this->server->get('SCRIPT_FILENAME', '');
+ $segs = explode('/', trim($file, '/'));
+ $segs = array_reverse($segs);
+ $index = 0;
+ $last = \count($segs);
+ $baseUrl = '';
+ do {
+ $seg = $segs[$index];
+ $baseUrl = '/'.$seg.$baseUrl;
+ ++$index;
+ } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
+ }
+
+ // Does the baseUrl have anything in common with the request_uri?
+ $requestUri = $this->getRequestUri();
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
+ // full $baseUrl matches
+ return $prefix;
+ }
+
+ if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) {
+ // directory portion of $baseUrl matches
+ return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ $truncatedRequestUri = $requestUri;
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $truncatedRequestUri = substr($requestUri, 0, $pos);
+ }
+
+ $basename = basename($baseUrl ?? '');
+ if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
+ // no match whatsoever; set it blank
+ return '';
+ }
+
+ // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+ // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+ // from PATH_INFO or QUERY_STRING
+ if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
+ $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl));
+ }
+
+ return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * Prepares the base path.
+ */
+ protected function prepareBasePath(): string
+ {
+ $baseUrl = $this->getBaseUrl();
+ if (empty($baseUrl)) {
+ return '';
+ }
+
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+ if (basename($baseUrl) === $filename) {
+ $basePath = \dirname($baseUrl);
+ } else {
+ $basePath = $baseUrl;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $basePath = str_replace('\\', '/', $basePath);
+ }
+
+ return rtrim($basePath, '/');
+ }
+
+ /**
+ * Prepares the path info.
+ */
+ protected function preparePathInfo(): string
+ {
+ if (null === ($requestUri = $this->getRequestUri())) {
+ return '/';
+ }
+
+ // Remove the query string from REQUEST_URI
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if (null === ($baseUrl = $this->getBaseUrlReal())) {
+ return $requestUri;
+ }
+
+ $pathInfo = substr($requestUri, \strlen($baseUrl));
+ if (false === $pathInfo || '' === $pathInfo) {
+ // If substr() returns false then PATH_INFO is set to an empty string
+ return '/';
+ }
+
+ return $pathInfo;
+ }
+
+ /**
+ * Initializes HTTP request formats.
+ *
+ * @return void
+ */
+ protected static function initializeFormats()
+ {
+ static::$formats = [
+ 'html' => ['text/html', 'application/xhtml+xml'],
+ 'txt' => ['text/plain'],
+ 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'],
+ 'css' => ['text/css'],
+ 'json' => ['application/json', 'application/x-json'],
+ 'jsonld' => ['application/ld+json'],
+ 'xml' => ['text/xml', 'application/xml', 'application/x-xml'],
+ 'rdf' => ['application/rdf+xml'],
+ 'atom' => ['application/atom+xml'],
+ 'rss' => ['application/rss+xml'],
+ 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'],
+ ];
+ }
+
+ private function setPhpDefaultLocale(string $locale): void
+ {
+ // if either the class Locale doesn't exist, or an exception is thrown when
+ // setting the default locale, the intl module is not installed, and
+ // the call can be ignored:
+ try {
+ if (class_exists(\Locale::class, false)) {
+ \Locale::setDefault($locale);
+ }
+ } catch (\Exception) {
+ }
+ }
+
+ /**
+ * Returns the prefix as encoded in the string when the string starts with
+ * the given prefix, null otherwise.
+ */
+ private function getUrlencodedPrefix(string $string, string $prefix): ?string
+ {
+ if ($this->isIisRewrite()) {
+ // ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case
+ // see https://github.com/php/php-src/issues/11981
+ if (0 !== stripos(rawurldecode($string), $prefix)) {
+ return null;
+ }
+ } elseif (!str_starts_with(rawurldecode($string), $prefix)) {
+ return null;
+ }
+
+ $len = \strlen($prefix);
+
+ if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+ return $match[0];
+ }
+
+ return null;
+ }
+
+ private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): static
+ {
+ if (self::$requestFactory) {
+ $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content);
+
+ if (!$request instanceof self) {
+ throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
+ }
+
+ return $request;
+ }
+
+ return new static($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Indicates whether this request originated from a trusted proxy.
+ *
+ * This can be useful to determine whether or not to trust the
+ * contents of a proxy-specific header.
+ */
+ public function isFromTrustedProxy(): bool
+ {
+ return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies);
+ }
+
+ /**
+ * This method is rather heavy because it splits and merges headers, and it's called by many other methods such as
+ * getPort(), isSecure(), getHost(), getClientIps(), getBaseUrl() etc. Thus, we try to cache the results for
+ * best performance.
+ */
+ private function getTrustedValues(int $type, ?string $ip = null): array
+ {
+ $cacheKey = $type."\0".((self::$trustedHeaderSet & $type) ? $this->headers->get(self::TRUSTED_HEADERS[$type]) : '');
+ $cacheKey .= "\0".$ip."\0".$this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]);
+
+ if (isset($this->trustedValuesCache[$cacheKey])) {
+ return $this->trustedValuesCache[$cacheKey];
+ }
+
+ $clientValues = [];
+ $forwardedValues = [];
+
+ if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::TRUSTED_HEADERS[$type])) {
+ foreach (explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) {
+ $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v);
+ }
+ }
+
+ if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) {
+ $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]);
+ $parts = HeaderUtils::split($forwarded, ',;=');
+ $param = self::FORWARDED_PARAMS[$type];
+ foreach ($parts as $subParts) {
+ if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) {
+ continue;
+ }
+ if (self::HEADER_X_FORWARDED_PORT === $type) {
+ if (str_ends_with($v, ']') || false === $v = strrchr($v, ':')) {
+ $v = $this->isSecure() ? ':443' : ':80';
+ }
+ $v = '0.0.0.0'.$v;
+ }
+ $forwardedValues[] = $v;
+ }
+ }
+
+ if (null !== $ip) {
+ $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
+ $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
+ }
+
+ if ($forwardedValues === $clientValues || !$clientValues) {
+ return $this->trustedValuesCache[$cacheKey] = $forwardedValues;
+ }
+
+ if (!$forwardedValues) {
+ return $this->trustedValuesCache[$cacheKey] = $clientValues;
+ }
+
+ if (!$this->isForwardedValid) {
+ return $this->trustedValuesCache[$cacheKey] = null !== $ip ? ['0.0.0.0', $ip] : [];
+ }
+ $this->isForwardedValid = false;
+
+ throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type]));
+ }
+
+ private function normalizeAndFilterClientIps(array $clientIps, string $ip): array
+ {
+ if (!$clientIps) {
+ return [];
+ }
+ $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
+ $firstTrustedIp = null;
+
+ foreach ($clientIps as $key => $clientIp) {
+ if (strpos($clientIp, '.')) {
+ // Strip :port from IPv4 addresses. This is allowed in Forwarded
+ // and may occur in X-Forwarded-For.
+ $i = strpos($clientIp, ':');
+ if ($i) {
+ $clientIps[$key] = $clientIp = substr($clientIp, 0, $i);
+ }
+ } elseif (str_starts_with($clientIp, '[')) {
+ // Strip brackets and :port from IPv6 addresses.
+ $i = strpos($clientIp, ']', 1);
+ $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1);
+ }
+
+ if (!filter_var($clientIp, \FILTER_VALIDATE_IP)) {
+ unset($clientIps[$key]);
+
+ continue;
+ }
+
+ if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
+ unset($clientIps[$key]);
+
+ // Fallback to this when the client IP falls into the range of trusted proxies
+ $firstTrustedIp ??= $clientIp;
+ }
+ }
+
+ // Now the IP chain contains only untrusted proxies and the client IP
+ return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp];
+ }
+
+ /**
+ * Is this IIS with UrlRewriteModule?
+ *
+ * This method consumes, caches and removed the IIS_WasUrlRewritten env var,
+ * so we don't inherit it to sub-requests.
+ */
+ private function isIisRewrite(): bool
+ {
+ if (1 === $this->server->getInt('IIS_WasUrlRewritten')) {
+ $this->isIisRewrite = true;
+ $this->server->remove('IIS_WasUrlRewritten');
+ }
+
+ return $this->isIisRewrite;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher.php
new file mode 100644
index 00000000..b3ca3715
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher.php
@@ -0,0 +1,200 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+trigger_deprecation('symfony/http-foundation', '6.2', 'The "%s" class is deprecated, use "%s" instead.', RequestMatcher::class, ChainRequestMatcher::class);
+
+/**
+ * RequestMatcher compares a pre-defined set of checks against a Request instance.
+ *
+ * @author Fabien Potencier
+ *
+ * @deprecated since Symfony 6.2, use ChainRequestMatcher instead
+ */
+class RequestMatcher implements RequestMatcherInterface
+{
+ private ?string $path = null;
+ private ?string $host = null;
+ private ?int $port = null;
+
+ /**
+ * @var string[]
+ */
+ private array $methods = [];
+
+ /**
+ * @var string[]
+ */
+ private array $ips = [];
+
+ /**
+ * @var string[]
+ */
+ private array $attributes = [];
+
+ /**
+ * @var string[]
+ */
+ private array $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, string|array|null $methods = null, string|array|null $ips = null, array $attributes = [], string|array|null $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
+ *
+ * @return void
+ */
+ public function matchScheme(string|array|null $scheme)
+ {
+ $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
+ }
+
+ /**
+ * Adds a check for the URL host name.
+ *
+ * @return void
+ */
+ public function matchHost(?string $regexp)
+ {
+ $this->host = $regexp;
+ }
+
+ /**
+ * Adds a check for the URL port.
+ *
+ * @param int|null $port The port number to connect to
+ *
+ * @return void
+ */
+ public function matchPort(?int $port)
+ {
+ $this->port = $port;
+ }
+
+ /**
+ * Adds a check for the URL path info.
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ public function matchIps(string|array|null $ips)
+ {
+ $ips = null !== $ips ? (array) $ips : [];
+
+ $this->ips = array_reduce($ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []);
+ }
+
+ /**
+ * Adds a check for the HTTP method.
+ *
+ * @param string|string[]|null $method An HTTP method or an array of HTTP methods
+ *
+ * @return void
+ */
+ public function matchMethod(string|array|null $method)
+ {
+ $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
+ }
+
+ /**
+ * Adds a check for request attribute.
+ *
+ * @return void
+ */
+ public function matchAttribute(string $key, string $regexp)
+ {
+ $this->attributes[$key] = $regexp;
+ }
+
+ public function matches(Request $request): bool
+ {
+ 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) {
+ $requestAttribute = $request->attributes->get($key);
+ if (!\is_string($requestAttribute)) {
+ return false;
+ }
+ if (!preg_match('{'.$pattern.'}', $requestAttribute)) {
+ 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);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php
new file mode 100644
index 00000000..09d6f49d
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/AttributesRequestMatcher.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the Request attributes matches all regular expressions.
+ *
+ * @author Fabien Potencier
+ */
+class AttributesRequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @param array $regexps
+ */
+ public function __construct(private array $regexps)
+ {
+ }
+
+ public function matches(Request $request): bool
+ {
+ foreach ($this->regexps as $key => $regexp) {
+ $attribute = $request->attributes->get($key);
+ if (!\is_string($attribute)) {
+ return false;
+ }
+ if (!preg_match('{'.$regexp.'}', $attribute)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php
new file mode 100644
index 00000000..935853f1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/ExpressionRequestMatcher.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\ExpressionLanguage\Expression;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier
+ */
+class ExpressionRequestMatcher implements RequestMatcherInterface
+{
+ public function __construct(
+ private ExpressionLanguage $language,
+ private Expression|string $expression,
+ ) {
+ }
+
+ public function matches(Request $request): bool
+ {
+ 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(),
+ ]);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php
new file mode 100644
index 00000000..2836759c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/HostRequestMatcher.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the Request URL host name matches a regular expression.
+ *
+ * @author Fabien Potencier
+ */
+class HostRequestMatcher implements RequestMatcherInterface
+{
+ public function __construct(private string $regexp)
+ {
+ }
+
+ public function matches(Request $request): bool
+ {
+ return preg_match('{'.$this->regexp.'}i', $request->getHost());
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php
new file mode 100644
index 00000000..333612e2
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/IpsRequestMatcher.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\IpUtils;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the client IP of a Request.
+ *
+ * @author Fabien Potencier
+ */
+class IpsRequestMatcher implements RequestMatcherInterface
+{
+ private array $ips;
+
+ /**
+ * @param string[]|string $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ * Strings can contain a comma-delimited list of IPs/ranges
+ */
+ public function __construct(array|string $ips)
+ {
+ $this->ips = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []);
+ }
+
+ public function matches(Request $request): bool
+ {
+ if (!$this->ips) {
+ return true;
+ }
+
+ return IpUtils::checkIp($request->getClientIp() ?? '', $this->ips);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php
new file mode 100644
index 00000000..875f992b
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/IsJsonRequestMatcher.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the Request content is valid JSON.
+ *
+ * @author Fabien Potencier
+ */
+class IsJsonRequestMatcher implements RequestMatcherInterface
+{
+ public function matches(Request $request): bool
+ {
+ return json_validate($request->getContent());
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php
new file mode 100644
index 00000000..b37f6e3c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/MethodRequestMatcher.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the HTTP method of a Request.
+ *
+ * @author Fabien Potencier
+ */
+class MethodRequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @var string[]
+ */
+ private array $methods = [];
+
+ /**
+ * @param string[]|string $methods An HTTP method or an array of HTTP methods
+ * Strings can contain a comma-delimited list of methods
+ */
+ public function __construct(array|string $methods)
+ {
+ $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static fn (array $methods, string $method) => array_merge($methods, preg_split('/\s*,\s*/', $method)), []);
+ }
+
+ public function matches(Request $request): bool
+ {
+ if (!$this->methods) {
+ return true;
+ }
+
+ return \in_array($request->getMethod(), $this->methods, true);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php
new file mode 100644
index 00000000..c7c7a02c
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/PathRequestMatcher.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the Request URL path info matches a regular expression.
+ *
+ * @author Fabien Potencier
+ */
+class PathRequestMatcher implements RequestMatcherInterface
+{
+ public function __construct(private string $regexp)
+ {
+ }
+
+ public function matches(Request $request): bool
+ {
+ return preg_match('{'.$this->regexp.'}', rawurldecode($request->getPathInfo()));
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php
new file mode 100644
index 00000000..5a01ce95
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/PortRequestMatcher.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the HTTP port of a Request.
+ *
+ * @author Fabien Potencier
+ */
+class PortRequestMatcher implements RequestMatcherInterface
+{
+ public function __construct(private int $port)
+ {
+ }
+
+ public function matches(Request $request): bool
+ {
+ return $request->getPort() === $this->port;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php b/www/libs/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php
new file mode 100644
index 00000000..9c9cd58b
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcher/SchemeRequestMatcher.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\RequestMatcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Checks the HTTP scheme of a Request.
+ *
+ * @author Fabien Potencier
+ */
+class SchemeRequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @var string[]
+ */
+ private array $schemes;
+
+ /**
+ * @param string[]|string $schemes A scheme or a list of schemes
+ * Strings can contain a comma-delimited list of schemes
+ */
+ public function __construct(array|string $schemes)
+ {
+ $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static fn (array $schemes, string $scheme) => array_merge($schemes, preg_split('/\s*,\s*/', $scheme)), []);
+ }
+
+ public function matches(Request $request): bool
+ {
+ if (!$this->schemes) {
+ return true;
+ }
+
+ return \in_array($request->getScheme(), $this->schemes, true);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestMatcherInterface.php b/www/libs/vendor/symfony/http-foundation/RequestMatcherInterface.php
new file mode 100644
index 00000000..6dcc3e0f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestMatcherInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+interface RequestMatcherInterface
+{
+ /**
+ * Decides whether the rule(s) implemented by the strategy matches the supplied request.
+ */
+ public function matches(Request $request): bool;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/RequestStack.php b/www/libs/vendor/symfony/http-foundation/RequestStack.php
new file mode 100644
index 00000000..5aa8ba79
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/RequestStack.php
@@ -0,0 +1,109 @@
+
+ *
+ * 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\Exception\SessionNotFoundException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei
+ */
+class RequestStack
+{
+ /**
+ * @var Request[]
+ */
+ private array $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.
+ *
+ * @return void
+ */
+ 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.
+ */
+ public function pop(): ?Request
+ {
+ if (!$this->requests) {
+ return null;
+ }
+
+ return array_pop($this->requests);
+ }
+
+ public function getCurrentRequest(): ?Request
+ {
+ return end($this->requests) ?: null;
+ }
+
+ /**
+ * Gets the main request.
+ *
+ * Be warned that making your code aware of the main request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ */
+ public function getMainRequest(): ?Request
+ {
+ 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 main request, it returns null.
+ */
+ public function getParentRequest(): ?Request
+ {
+ $pos = \count($this->requests) - 2;
+
+ return $this->requests[$pos] ?? null;
+ }
+
+ /**
+ * Gets the current session.
+ *
+ * @throws SessionNotFoundException
+ */
+ public function getSession(): SessionInterface
+ {
+ if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) {
+ return $request->getSession();
+ }
+
+ throw new SessionNotFoundException();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Response.php b/www/libs/vendor/symfony/http-foundation/Response.php
new file mode 100644
index 00000000..a43e7a9a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Response.php
@@ -0,0 +1,1353 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(ResponseHeaderBag::class);
+
+/**
+ * Response represents an HTTP response.
+ *
+ * @author Fabien Potencier
+ */
+class Response
+{
+ public const HTTP_CONTINUE = 100;
+ public const HTTP_SWITCHING_PROTOCOLS = 101;
+ public const HTTP_PROCESSING = 102; // RFC2518
+ public const HTTP_EARLY_HINTS = 103; // RFC8297
+ public const HTTP_OK = 200;
+ public const HTTP_CREATED = 201;
+ public const HTTP_ACCEPTED = 202;
+ public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
+ public const HTTP_NO_CONTENT = 204;
+ public const HTTP_RESET_CONTENT = 205;
+ public const HTTP_PARTIAL_CONTENT = 206;
+ public const HTTP_MULTI_STATUS = 207; // RFC4918
+ public const HTTP_ALREADY_REPORTED = 208; // RFC5842
+ public const HTTP_IM_USED = 226; // RFC3229
+ public const HTTP_MULTIPLE_CHOICES = 300;
+ public const HTTP_MOVED_PERMANENTLY = 301;
+ public const HTTP_FOUND = 302;
+ public const HTTP_SEE_OTHER = 303;
+ public const HTTP_NOT_MODIFIED = 304;
+ public const HTTP_USE_PROXY = 305;
+ public const HTTP_RESERVED = 306;
+ public const HTTP_TEMPORARY_REDIRECT = 307;
+ public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
+ public const HTTP_BAD_REQUEST = 400;
+ public const HTTP_UNAUTHORIZED = 401;
+ public const HTTP_PAYMENT_REQUIRED = 402;
+ public const HTTP_FORBIDDEN = 403;
+ public const HTTP_NOT_FOUND = 404;
+ public const HTTP_METHOD_NOT_ALLOWED = 405;
+ public const HTTP_NOT_ACCEPTABLE = 406;
+ public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
+ public const HTTP_REQUEST_TIMEOUT = 408;
+ public const HTTP_CONFLICT = 409;
+ public const HTTP_GONE = 410;
+ public const HTTP_LENGTH_REQUIRED = 411;
+ public const HTTP_PRECONDITION_FAILED = 412;
+ public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
+ public const HTTP_REQUEST_URI_TOO_LONG = 414;
+ public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
+ public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ public const HTTP_EXPECTATION_FAILED = 417;
+ public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
+ public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
+ public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
+ public const HTTP_LOCKED = 423; // RFC4918
+ public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
+ public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
+ public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
+ public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
+ public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
+ public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
+ public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
+ public const HTTP_INTERNAL_SERVER_ERROR = 500;
+ public const HTTP_NOT_IMPLEMENTED = 501;
+ public const HTTP_BAD_GATEWAY = 502;
+ public const HTTP_SERVICE_UNAVAILABLE = 503;
+ public const HTTP_GATEWAY_TIMEOUT = 504;
+ public const HTTP_VERSION_NOT_SUPPORTED = 505;
+ public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
+ public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
+ public const HTTP_LOOP_DETECTED = 508; // RFC5842
+ public const HTTP_NOT_EXTENDED = 510; // RFC2774
+ public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
+
+ /**
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+ */
+ private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [
+ 'must_revalidate' => false,
+ 'no_cache' => false,
+ 'no_store' => false,
+ 'no_transform' => false,
+ 'public' => false,
+ 'private' => false,
+ 'proxy_revalidate' => false,
+ 'max_age' => true,
+ 's_maxage' => true,
+ 'stale_if_error' => true, // RFC5861
+ 'stale_while_revalidate' => true, // RFC5861
+ 'immutable' => false,
+ 'last_modified' => true,
+ 'etag' => true,
+ ];
+
+ /**
+ * @var ResponseHeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * @var string
+ */
+ protected $statusText;
+
+ /**
+ * @var string
+ */
+ protected $charset;
+
+ /**
+ * Status codes translation table.
+ *
+ * The list of codes is complete according to the
+ * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
+ * (last updated 2021-10-01).
+ *
+ * Unless otherwise noted, the status code is defined in RFC2616.
+ *
+ * @var array
+ */
+ public static $statusTexts = [
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing', // RFC2518
+ 103 => 'Early Hints',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status', // RFC4918
+ 208 => 'Already Reported', // RFC5842
+ 226 => 'IM Used', // RFC3229
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect', // RFC7238
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot', // RFC2324
+ 421 => 'Misdirected Request', // RFC7540
+ 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics
+ 423 => 'Locked', // RFC4918
+ 424 => 'Failed Dependency', // RFC4918
+ 425 => 'Too Early', // RFC-ietf-httpbis-replay-04
+ 426 => 'Upgrade Required', // RFC2817
+ 428 => 'Precondition Required', // RFC6585
+ 429 => 'Too Many Requests', // RFC6585
+ 431 => 'Request Header Fields Too Large', // RFC6585
+ 451 => 'Unavailable For Legal Reasons', // RFC7725
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates', // RFC2295
+ 507 => 'Insufficient Storage', // RFC4918
+ 508 => 'Loop Detected', // RFC5842
+ 510 => 'Not Extended', // RFC2774
+ 511 => 'Network Authentication Required', // RFC6585
+ ];
+
+ /**
+ * Tracks headers already sent in informational responses.
+ */
+ private array $sentHeaders;
+
+ /**
+ * @param int $status The HTTP status code (200 "OK" by default)
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ */
+ public function __construct(?string $content = '', int $status = 200, array $headers = [])
+ {
+ $this->headers = new ResponseHeaderBag($headers);
+ $this->setContent($content);
+ $this->setStatusCode($status);
+ $this->setProtocolVersion('1.0');
+ }
+
+ /**
+ * Returns the Response as an HTTP string.
+ *
+ * The string representation of the Response is the same as the
+ * one that will be sent to the client only if the prepare() method
+ * has been called before.
+ *
+ * @see prepare()
+ */
+ public function __toString(): string
+ {
+ return
+ sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+ $this->headers."\r\n".
+ $this->getContent();
+ }
+
+ /**
+ * Clones the current Response instance.
+ */
+ public function __clone()
+ {
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Prepares the Response before it is sent to the client.
+ *
+ * This method tweaks the Response to ensure that it is
+ * compliant with RFC 2616. Most of the changes are based on
+ * the Request that is "associated" with this Response.
+ *
+ * @return $this
+ */
+ public function prepare(Request $request): static
+ {
+ $headers = $this->headers;
+
+ if ($this->isInformational() || $this->isEmpty()) {
+ $this->setContent(null);
+ $headers->remove('Content-Type');
+ $headers->remove('Content-Length');
+ // prevent PHP from sending the Content-Type header based on default_mimetype
+ ini_set('default_mimetype', '');
+ } else {
+ // Content-type based on the Request
+ if (!$headers->has('Content-Type')) {
+ $format = $request->getRequestFormat(null);
+ if (null !== $format && $mimeType = $request->getMimeType($format)) {
+ $headers->set('Content-Type', $mimeType);
+ }
+ }
+
+ // Fix Content-Type
+ $charset = $this->charset ?: 'UTF-8';
+ if (!$headers->has('Content-Type')) {
+ $headers->set('Content-Type', 'text/html; charset='.$charset);
+ } elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) {
+ // add the charset
+ $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
+ }
+
+ // Fix Content-Length
+ if ($headers->has('Transfer-Encoding')) {
+ $headers->remove('Content-Length');
+ }
+
+ if ($request->isMethod('HEAD')) {
+ // cf. RFC2616 14.13
+ $length = $headers->get('Content-Length');
+ $this->setContent(null);
+ if ($length) {
+ $headers->set('Content-Length', $length);
+ }
+ }
+ }
+
+ // Fix protocol
+ if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ // Check if we need to send extra expire info headers
+ if ('1.0' == $this->getProtocolVersion() && str_contains($headers->get('Cache-Control', ''), 'no-cache')) {
+ $headers->set('pragma', 'no-cache');
+ $headers->set('expires', -1);
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ if ($request->isSecure()) {
+ foreach ($headers->getCookies() as $cookie) {
+ $cookie->setSecureDefault(true);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers.
+ *
+ * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null
+ *
+ * @return $this
+ */
+ public function sendHeaders(/* int $statusCode = null */): static
+ {
+ // headers have already been sent by the developer
+ if (headers_sent()) {
+ return $this;
+ }
+
+ $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null;
+ $informationalResponse = $statusCode >= 100 && $statusCode < 200;
+ if ($informationalResponse && !\function_exists('headers_send')) {
+ // skip informational responses if not supported by the SAPI
+ return $this;
+ }
+
+ // headers
+ foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
+ $newValues = $values;
+ $replace = false;
+
+ // As recommended by RFC 8297, PHP automatically copies headers from previous 103 responses, we need to deal with that if headers changed
+ $previousValues = $this->sentHeaders[$name] ?? null;
+ if ($previousValues === $values) {
+ // Header already sent in a previous response, it will be automatically copied in this response by PHP
+ continue;
+ }
+
+ $replace = 0 === strcasecmp($name, 'Content-Type');
+
+ if (null !== $previousValues && array_diff($previousValues, $values)) {
+ header_remove($name);
+ $previousValues = null;
+ }
+
+ $newValues = null === $previousValues ? $values : array_diff($values, $previousValues);
+
+ foreach ($newValues as $value) {
+ header($name.': '.$value, $replace, $this->statusCode);
+ }
+
+ if ($informationalResponse) {
+ $this->sentHeaders[$name] = $values;
+ }
+ }
+
+ // cookies
+ foreach ($this->headers->getCookies() as $cookie) {
+ header('Set-Cookie: '.$cookie, false, $this->statusCode);
+ }
+
+ if ($informationalResponse) {
+ headers_send($statusCode);
+
+ return $this;
+ }
+
+ $statusCode ??= $this->statusCode;
+
+ // status
+ header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode);
+
+ return $this;
+ }
+
+ /**
+ * Sends content for the current web response.
+ *
+ * @return $this
+ */
+ public function sendContent(): static
+ {
+ echo $this->content;
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers and content.
+ *
+ * @param bool $flush Whether output buffers should be flushed
+ *
+ * @return $this
+ */
+ public function send(/* bool $flush = true */): static
+ {
+ $this->sendHeaders();
+ $this->sendContent();
+
+ $flush = 1 <= \func_num_args() ? func_get_arg(0) : true;
+ if (!$flush) {
+ return $this;
+ }
+
+ if (\function_exists('fastcgi_finish_request')) {
+ fastcgi_finish_request();
+ } elseif (\function_exists('litespeed_finish_request')) {
+ litespeed_finish_request();
+ } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+ static::closeOutputBuffers(0, true);
+ flush();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response content.
+ *
+ * @return $this
+ */
+ public function setContent(?string $content): static
+ {
+ $this->content = $content ?? '';
+
+ return $this;
+ }
+
+ /**
+ * Gets the current response content.
+ */
+ public function getContent(): string|false
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the HTTP protocol version (1.0 or 1.1).
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setProtocolVersion(string $version): static
+ {
+ $this->version = $version;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP protocol version.
+ *
+ * @final
+ */
+ public function getProtocolVersion(): string
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the response status code.
+ *
+ * If the status text is null it will be automatically populated for the known
+ * status codes and left empty otherwise.
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ *
+ * @final
+ */
+ public function setStatusCode(int $code, ?string $text = null): static
+ {
+ $this->statusCode = $code;
+ if ($this->isInvalid()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+ }
+
+ if (null === $text) {
+ $this->statusText = self::$statusTexts[$code] ?? 'unknown status';
+
+ return $this;
+ }
+
+ $this->statusText = $text;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the status code for the current web response.
+ *
+ * @final
+ */
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Sets the response charset.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setCharset(string $charset): static
+ {
+ $this->charset = $charset;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the response charset.
+ *
+ * @final
+ */
+ public function getCharset(): ?string
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Returns true if the response may safely be kept in a shared (surrogate) cache.
+ *
+ * Responses marked "private" with an explicit Cache-Control directive are
+ * considered uncacheable.
+ *
+ * Responses with neither a freshness lifetime (Expires, max-age) nor cache
+ * validator (Last-Modified, ETag) are considered uncacheable because there is
+ * no way to tell when or how to remove them from the cache.
+ *
+ * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
+ * for example "status codes that are defined as cacheable by default [...]
+ * can be reused by a cache with heuristic expiration unless otherwise indicated"
+ * (https://tools.ietf.org/html/rfc7231#section-6.1)
+ *
+ * @final
+ */
+ public function isCacheable(): bool
+ {
+ if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) {
+ return false;
+ }
+
+ if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
+ return false;
+ }
+
+ return $this->isValidateable() || $this->isFresh();
+ }
+
+ /**
+ * Returns true if the response is "fresh".
+ *
+ * Fresh responses may be served from cache without any interaction with the
+ * origin. A response is considered fresh when it includes a Cache-Control/max-age
+ * indicator or Expires header and the calculated age is less than the freshness lifetime.
+ *
+ * @final
+ */
+ public function isFresh(): bool
+ {
+ return $this->getTtl() > 0;
+ }
+
+ /**
+ * Returns true if the response includes headers that can be used to validate
+ * the response with the origin server using a conditional GET request.
+ *
+ * @final
+ */
+ public function isValidateable(): bool
+ {
+ return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
+ }
+
+ /**
+ * Marks the response as "private".
+ *
+ * It makes the response ineligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setPrivate(): static
+ {
+ $this->headers->removeCacheControlDirective('public');
+ $this->headers->addCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "public".
+ *
+ * It makes the response eligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setPublic(): static
+ {
+ $this->headers->addCacheControlDirective('public');
+ $this->headers->removeCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "immutable".
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setImmutable(bool $immutable = true): static
+ {
+ if ($immutable) {
+ $this->headers->addCacheControlDirective('immutable');
+ } else {
+ $this->headers->removeCacheControlDirective('immutable');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response is marked as "immutable".
+ *
+ * @final
+ */
+ public function isImmutable(): bool
+ {
+ return $this->headers->hasCacheControlDirective('immutable');
+ }
+
+ /**
+ * Returns true if the response must be revalidated by shared caches once it has become stale.
+ *
+ * This method indicates that the response must not be served stale by a
+ * cache in any circumstance without first revalidating with the origin.
+ * When present, the TTL of the response should not be overridden to be
+ * greater than the value provided by the origin.
+ *
+ * @final
+ */
+ public function mustRevalidate(): bool
+ {
+ return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
+ }
+
+ /**
+ * Returns the Date header as a DateTime instance.
+ *
+ * @throws \RuntimeException When the header is not parseable
+ *
+ * @final
+ */
+ public function getDate(): ?\DateTimeImmutable
+ {
+ return $this->headers->getDate('Date');
+ }
+
+ /**
+ * Sets the Date header.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setDate(\DateTimeInterface $date): static
+ {
+ $date = \DateTimeImmutable::createFromInterface($date);
+ $date = $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the age of the response in seconds.
+ *
+ * @final
+ */
+ public function getAge(): int
+ {
+ if (null !== $age = $this->headers->get('Age')) {
+ return (int) $age;
+ }
+
+ return max(time() - (int) $this->getDate()->format('U'), 0);
+ }
+
+ /**
+ * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
+ *
+ * @return $this
+ */
+ public function expire(): static
+ {
+ if ($this->isFresh()) {
+ $this->headers->set('Age', $this->getMaxAge());
+ $this->headers->remove('Expires');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of the Expires header as a DateTime instance.
+ *
+ * @final
+ */
+ public function getExpires(): ?\DateTimeImmutable
+ {
+ try {
+ return $this->headers->getDate('Expires');
+ } catch (\RuntimeException) {
+ // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
+ return \DateTimeImmutable::createFromFormat('U', time() - 172800);
+ }
+ }
+
+ /**
+ * Sets the Expires HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setExpires(?\DateTimeInterface $date = null): static
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ if (null === $date) {
+ $this->headers->remove('Expires');
+
+ return $this;
+ }
+
+ $date = \DateTimeImmutable::createFromInterface($date);
+ $date = $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of seconds after the time specified in the response's Date
+ * header when the response should no longer be considered fresh.
+ *
+ * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
+ * back on an expires header. It returns null when no maximum age can be established.
+ *
+ * @final
+ */
+ public function getMaxAge(): ?int
+ {
+ if ($this->headers->hasCacheControlDirective('s-maxage')) {
+ return (int) $this->headers->getCacheControlDirective('s-maxage');
+ }
+
+ if ($this->headers->hasCacheControlDirective('max-age')) {
+ return (int) $this->headers->getCacheControlDirective('max-age');
+ }
+
+ if (null !== $expires = $this->getExpires()) {
+ $maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U');
+
+ return max($maxAge, 0);
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh.
+ *
+ * This methods sets the Cache-Control max-age directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setMaxAge(int $value): static
+ {
+ $this->headers->addCacheControlDirective('max-age', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be returned by shared caches when backend is down.
+ *
+ * This method sets the Cache-Control stale-if-error directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setStaleIfError(int $value): static
+ {
+ $this->headers->addCacheControlDirective('stale-if-error', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer return stale content by shared caches.
+ *
+ * This method sets the Cache-Control stale-while-revalidate directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setStaleWhileRevalidate(int $value): static
+ {
+ $this->headers->addCacheControlDirective('stale-while-revalidate', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
+ *
+ * This methods sets the Cache-Control s-maxage directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setSharedMaxAge(int $value): static
+ {
+ $this->setPublic();
+ $this->headers->addCacheControlDirective('s-maxage', $value);
+
+ return $this;
+ }
+
+ /**
+ * Returns the response's time-to-live in seconds.
+ *
+ * It returns null when no freshness information is present in the response.
+ *
+ * When the response's TTL is 0, the response may not be served from cache without first
+ * revalidating with the origin.
+ *
+ * @final
+ */
+ public function getTtl(): ?int
+ {
+ $maxAge = $this->getMaxAge();
+
+ return null !== $maxAge ? max($maxAge - $this->getAge(), 0) : null;
+ }
+
+ /**
+ * Sets the response's time-to-live for shared caches in seconds.
+ *
+ * This method adjusts the Cache-Control/s-maxage directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setTtl(int $seconds): static
+ {
+ $this->setSharedMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's time-to-live for private/client caches in seconds.
+ *
+ * This method adjusts the Cache-Control/max-age directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setClientTtl(int $seconds): static
+ {
+ $this->setMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Returns the Last-Modified HTTP header as a DateTime instance.
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ *
+ * @final
+ */
+ public function getLastModified(): ?\DateTimeImmutable
+ {
+ return $this->headers->getDate('Last-Modified');
+ }
+
+ /**
+ * Sets the Last-Modified HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setLastModified(?\DateTimeInterface $date = null): static
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ if (null === $date) {
+ $this->headers->remove('Last-Modified');
+
+ return $this;
+ }
+
+ $date = \DateTimeImmutable::createFromInterface($date);
+ $date = $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the literal value of the ETag HTTP header.
+ *
+ * @final
+ */
+ public function getEtag(): ?string
+ {
+ return $this->headers->get('ETag');
+ }
+
+ /**
+ * Sets the ETag value.
+ *
+ * @param string|null $etag The ETag unique identifier or null to remove the header
+ * @param bool $weak Whether you want a weak ETag or not
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setEtag(?string $etag = null, bool $weak = false): static
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ if (null === $etag) {
+ $this->headers->remove('Etag');
+ } else {
+ if (!str_starts_with($etag, '"')) {
+ $etag = '"'.$etag.'"';
+ }
+
+ $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's cache headers (validation and/or expiration).
+ *
+ * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag.
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @final
+ */
+ public function setCache(array $options): static
+ {
+ if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) {
+ throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
+ }
+
+ if (isset($options['etag'])) {
+ $this->setEtag($options['etag']);
+ }
+
+ if (isset($options['last_modified'])) {
+ $this->setLastModified($options['last_modified']);
+ }
+
+ if (isset($options['max_age'])) {
+ $this->setMaxAge($options['max_age']);
+ }
+
+ if (isset($options['s_maxage'])) {
+ $this->setSharedMaxAge($options['s_maxage']);
+ }
+
+ if (isset($options['stale_while_revalidate'])) {
+ $this->setStaleWhileRevalidate($options['stale_while_revalidate']);
+ }
+
+ if (isset($options['stale_if_error'])) {
+ $this->setStaleIfError($options['stale_if_error']);
+ }
+
+ foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) {
+ if (!$hasValue && isset($options[$directive])) {
+ if ($options[$directive]) {
+ $this->headers->addCacheControlDirective(str_replace('_', '-', $directive));
+ } else {
+ $this->headers->removeCacheControlDirective(str_replace('_', '-', $directive));
+ }
+ }
+ }
+
+ if (isset($options['public'])) {
+ if ($options['public']) {
+ $this->setPublic();
+ } else {
+ $this->setPrivate();
+ }
+ }
+
+ if (isset($options['private'])) {
+ if ($options['private']) {
+ $this->setPrivate();
+ } else {
+ $this->setPublic();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Modifies the response so that it conforms to the rules defined for a 304 status code.
+ *
+ * This sets the status, removes the body, and discards any headers
+ * that MUST NOT be included in 304 responses.
+ *
+ * @return $this
+ *
+ * @see https://tools.ietf.org/html/rfc2616#section-10.3.5
+ *
+ * @final
+ */
+ public function setNotModified(): static
+ {
+ $this->setStatusCode(304);
+ $this->setContent(null);
+
+ // remove headers that MUST NOT be included with 304 Not Modified responses
+ foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) {
+ $this->headers->remove($header);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response includes a Vary header.
+ *
+ * @final
+ */
+ public function hasVary(): bool
+ {
+ return null !== $this->headers->get('Vary');
+ }
+
+ /**
+ * Returns an array of header names given in the Vary header.
+ *
+ * @final
+ */
+ public function getVary(): array
+ {
+ if (!$vary = $this->headers->all('Vary')) {
+ return [];
+ }
+
+ $ret = [];
+ foreach ($vary as $item) {
+ $ret[] = preg_split('/[\s,]+/', $item);
+ }
+
+ return array_merge([], ...$ret);
+ }
+
+ /**
+ * Sets the Vary header.
+ *
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setVary(string|array $headers, bool $replace = true): static
+ {
+ $this->headers->set('Vary', $headers, $replace);
+
+ return $this;
+ }
+
+ /**
+ * Determines if the Response validators (ETag, Last-Modified) match
+ * a conditional value specified in the Request.
+ *
+ * If the Response is not modified, it sets the status code to 304 and
+ * removes the actual content by calling the setNotModified() method.
+ *
+ * @final
+ */
+ public function isNotModified(Request $request): bool
+ {
+ if (!$request->isMethodCacheable()) {
+ return false;
+ }
+
+ $notModified = false;
+ $lastModified = $this->headers->get('Last-Modified');
+ $modifiedSince = $request->headers->get('If-Modified-Since');
+
+ if (($ifNoneMatchEtags = $request->getETags()) && (null !== $etag = $this->getEtag())) {
+ if (0 == strncmp($etag, 'W/', 2)) {
+ $etag = substr($etag, 2);
+ }
+
+ // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2.
+ foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) {
+ if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) {
+ $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2);
+ }
+
+ if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) {
+ $notModified = true;
+ break;
+ }
+ }
+ }
+ // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3.
+ elseif ($modifiedSince && $lastModified) {
+ $notModified = strtotime($modifiedSince) >= strtotime($lastModified);
+ }
+
+ if ($notModified) {
+ $this->setNotModified();
+ }
+
+ return $notModified;
+ }
+
+ /**
+ * Is response invalid?
+ *
+ * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ *
+ * @final
+ */
+ public function isInvalid(): bool
+ {
+ return $this->statusCode < 100 || $this->statusCode >= 600;
+ }
+
+ /**
+ * Is response informative?
+ *
+ * @final
+ */
+ public function isInformational(): bool
+ {
+ return $this->statusCode >= 100 && $this->statusCode < 200;
+ }
+
+ /**
+ * Is response successful?
+ *
+ * @final
+ */
+ public function isSuccessful(): bool
+ {
+ return $this->statusCode >= 200 && $this->statusCode < 300;
+ }
+
+ /**
+ * Is the response a redirect?
+ *
+ * @final
+ */
+ public function isRedirection(): bool
+ {
+ return $this->statusCode >= 300 && $this->statusCode < 400;
+ }
+
+ /**
+ * Is there a client error?
+ *
+ * @final
+ */
+ public function isClientError(): bool
+ {
+ return $this->statusCode >= 400 && $this->statusCode < 500;
+ }
+
+ /**
+ * Was there a server side error?
+ *
+ * @final
+ */
+ public function isServerError(): bool
+ {
+ return $this->statusCode >= 500 && $this->statusCode < 600;
+ }
+
+ /**
+ * Is the response OK?
+ *
+ * @final
+ */
+ public function isOk(): bool
+ {
+ return 200 === $this->statusCode;
+ }
+
+ /**
+ * Is the response forbidden?
+ *
+ * @final
+ */
+ public function isForbidden(): bool
+ {
+ return 403 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a not found error?
+ *
+ * @final
+ */
+ public function isNotFound(): bool
+ {
+ return 404 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a redirect of some form?
+ *
+ * @final
+ */
+ public function isRedirect(?string $location = null): bool
+ {
+ return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location'));
+ }
+
+ /**
+ * Is the response empty?
+ *
+ * @final
+ */
+ public function isEmpty(): bool
+ {
+ return \in_array($this->statusCode, [204, 304]);
+ }
+
+ /**
+ * Cleans or flushes output buffers up to target level.
+ *
+ * Resulting level can be greater than target level if a non-removable buffer has been encountered.
+ *
+ * @final
+ */
+ public static function closeOutputBuffers(int $targetLevel, bool $flush): void
+ {
+ $status = ob_get_status(true);
+ $level = \count($status);
+ $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE);
+
+ while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
+ if ($flush) {
+ ob_end_flush();
+ } else {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Marks a response as safe according to RFC8674.
+ *
+ * @see https://tools.ietf.org/html/rfc8674
+ */
+ public function setContentSafe(bool $safe = true): void
+ {
+ if ($safe) {
+ $this->headers->set('Preference-Applied', 'safe');
+ } elseif ('safe' === $this->headers->get('Preference-Applied')) {
+ $this->headers->remove('Preference-Applied');
+ }
+
+ $this->setVary('Prefer', false);
+ }
+
+ /**
+ * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
+ *
+ * @see http://support.microsoft.com/kb/323308
+ *
+ * @final
+ */
+ protected function ensureIEOverSSLCompatibility(Request $request): void
+ {
+ if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) {
+ if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
+ $this->headers->remove('Cache-Control');
+ }
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/ResponseHeaderBag.php b/www/libs/vendor/symfony/http-foundation/ResponseHeaderBag.php
new file mode 100644
index 00000000..376357d0
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/ResponseHeaderBag.php
@@ -0,0 +1,292 @@
+
+ *
+ * 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
+ */
+class ResponseHeaderBag extends HeaderBag
+{
+ public const COOKIES_FLAT = 'flat';
+ public const COOKIES_ARRAY = 'array';
+
+ public const DISPOSITION_ATTACHMENT = 'attachment';
+ public 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.
+ */
+ public function allPreserveCase(): array
+ {
+ $headers = [];
+ foreach ($this->all() as $name => $value) {
+ $headers[$this->headerNames[$name] ?? $name] = $value;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @return array
+ */
+ public function allPreserveCaseWithoutCookies()
+ {
+ $headers = $this->allPreserveCase();
+ if (isset($this->headerNames['set-cookie'])) {
+ unset($headers[$this->headerNames['set-cookie']]);
+ }
+
+ return $headers;
+ }
+
+ /**
+ * @return void
+ */
+ 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();
+ }
+ }
+
+ public function all(?string $key = null): array
+ {
+ $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;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $key, string|array|null $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);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ 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();
+ }
+ }
+
+ public function hasCacheControlDirective(string $key): bool
+ {
+ return \array_key_exists($key, $this->computedCacheControl);
+ }
+
+ public function getCacheControlDirective(string $key): bool|string|null
+ {
+ return $this->computedCacheControl[$key] ?? null;
+ }
+
+ /**
+ * @return void
+ */
+ 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.
+ *
+ * @return void
+ */
+ public function removeCookie(string $name, ?string $path = '/', ?string $domain = null)
+ {
+ $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): array
+ {
+ 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.
+ *
+ * @param bool $partitioned
+ *
+ * @return void
+ */
+ public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null /* , bool $partitioned = false */)
+ {
+ $partitioned = 6 < \func_num_args() ? \func_get_arg(6) : false;
+
+ $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite, $partitioned));
+ }
+
+ /**
+ * @see HeaderUtils::makeDisposition()
+ *
+ * @return string
+ */
+ 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.
+ */
+ protected function computeCacheControlValue(): string
+ {
+ if (!$this->cacheControl) {
+ if ($this->has('Last-Modified') || $this->has('Expires')) {
+ return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified"
+ }
+
+ // conservative by default
+ return 'no-cache, private';
+ }
+
+ $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
+ {
+ $this->set('Date', gmdate('D, d M Y H:i:s').' GMT');
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/ServerBag.php b/www/libs/vendor/symfony/http-foundation/ServerBag.php
new file mode 100644
index 00000000..09fc3866
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/ServerBag.php
@@ -0,0 +1,97 @@
+
+ *
+ * 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
+ * @author Bulat Shakirzyanov
+ * @author Robert Kiss
+ */
+class ServerBag extends ParameterBag
+{
+ /**
+ * Gets the HTTP headers.
+ */
+ public function getHeaders(): array
+ {
+ $headers = [];
+ foreach ($this->parameters as $key => $value) {
+ if (str_starts_with($key, 'HTTP_')) {
+ $headers[substr($key, 5)] = $value;
+ } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true) && '' !== $value) {
+ $headers[$key] = $value;
+ }
+ }
+
+ if (isset($this->parameters['PHP_AUTH_USER'])) {
+ $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+ $headers['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:%0]
+ *
+ * A sample .htaccess file:
+ * RewriteEngine On
+ * RewriteCond %{HTTP:Authorization} .+
+ * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
+ * RewriteCond %{REQUEST_FILENAME} !-f
+ * RewriteRule ^(.*)$ index.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)) {
+ [$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;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
new file mode 100644
index 00000000..ad5a6590
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
@@ -0,0 +1,130 @@
+
+ *
+ * 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.
+ *
+ * @implements \IteratorAggregate
+ */
+class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
+{
+ private string $name = 'attributes';
+ private string $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;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ public function initialize(array &$attributes)
+ {
+ $this->attributes = &$attributes;
+ }
+
+ public function getStorageKey(): string
+ {
+ return $this->storageKey;
+ }
+
+ public function has(string $name): bool
+ {
+ return \array_key_exists($name, $this->attributes);
+ }
+
+ public function get(string $name, mixed $default = null): mixed
+ {
+ return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $name, mixed $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function all(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * @return void
+ */
+ public function replace(array $attributes)
+ {
+ $this->attributes = [];
+ foreach ($attributes as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ public function remove(string $name): mixed
+ {
+ $retval = null;
+ if (\array_key_exists($name, $this->attributes)) {
+ $retval = $this->attributes[$name];
+ unset($this->attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ public function clear(): mixed
+ {
+ $return = $this->attributes;
+ $this->attributes = [];
+
+ return $return;
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->attributes);
+ }
+
+ /**
+ * Returns the number of attributes.
+ */
+ public function count(): int
+ {
+ return \count($this->attributes);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
new file mode 100644
index 00000000..e8cd0b5a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
@@ -0,0 +1,58 @@
+
+ *
+ * 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
+ */
+interface AttributeBagInterface extends SessionBagInterface
+{
+ /**
+ * Checks if an attribute is defined.
+ */
+ public function has(string $name): bool;
+
+ /**
+ * Returns an attribute.
+ */
+ public function get(string $name, mixed $default = null): mixed;
+
+ /**
+ * Sets an attribute.
+ *
+ * @return void
+ */
+ public function set(string $name, mixed $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array
+ */
+ public function all(): array;
+
+ /**
+ * @return void
+ */
+ 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): mixed;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/www/libs/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
new file mode 100644
index 00000000..80bbeda0
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
@@ -0,0 +1,137 @@
+
+ *
+ * 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
+ */
+class AutoExpireFlashBag implements FlashBagInterface
+{
+ private string $name = 'flashes';
+ private array $flashes = ['display' => [], 'new' => []];
+ private string $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct(string $storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ 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'] = [];
+ }
+
+ /**
+ * @return void
+ */
+ public function add(string $type, mixed $message)
+ {
+ $this->flashes['new'][$type][] = $message;
+ }
+
+ public function peek(string $type, array $default = []): array
+ {
+ return $this->has($type) ? $this->flashes['display'][$type] : $default;
+ }
+
+ public function peekAll(): array
+ {
+ return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : [];
+ }
+
+ public function get(string $type, array $default = []): array
+ {
+ $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;
+ }
+
+ public function all(): array
+ {
+ $return = $this->flashes['display'];
+ $this->flashes['display'] = [];
+
+ return $return;
+ }
+
+ /**
+ * @return void
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes['new'] = $messages;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $type, string|array $messages)
+ {
+ $this->flashes['new'][$type] = (array) $messages;
+ }
+
+ public function has(string $type): bool
+ {
+ return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
+ }
+
+ public function keys(): array
+ {
+ return array_keys($this->flashes['display']);
+ }
+
+ public function getStorageKey(): string
+ {
+ return $this->storageKey;
+ }
+
+ public function clear(): mixed
+ {
+ return $this->all();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
new file mode 100644
index 00000000..659d59d1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
@@ -0,0 +1,128 @@
+
+ *
+ * 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
+ */
+class FlashBag implements FlashBagInterface
+{
+ private string $name = 'flashes';
+ private array $flashes = [];
+ private string $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct(string $storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+ }
+
+ /**
+ * @return void
+ */
+ public function add(string $type, mixed $message)
+ {
+ $this->flashes[$type][] = $message;
+ }
+
+ public function peek(string $type, array $default = []): array
+ {
+ return $this->has($type) ? $this->flashes[$type] : $default;
+ }
+
+ public function peekAll(): array
+ {
+ return $this->flashes;
+ }
+
+ public function get(string $type, array $default = []): array
+ {
+ if (!$this->has($type)) {
+ return $default;
+ }
+
+ $return = $this->flashes[$type];
+
+ unset($this->flashes[$type]);
+
+ return $return;
+ }
+
+ public function all(): array
+ {
+ $return = $this->peekAll();
+ $this->flashes = [];
+
+ return $return;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $type, string|array $messages)
+ {
+ $this->flashes[$type] = (array) $messages;
+ }
+
+ /**
+ * @return void
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes = $messages;
+ }
+
+ public function has(string $type): bool
+ {
+ return \array_key_exists($type, $this->flashes) && $this->flashes[$type];
+ }
+
+ public function keys(): array
+ {
+ return array_keys($this->flashes);
+ }
+
+ public function getStorageKey(): string
+ {
+ return $this->storageKey;
+ }
+
+ public function clear(): mixed
+ {
+ return $this->all();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
new file mode 100644
index 00000000..bbcf7f8b
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
@@ -0,0 +1,78 @@
+
+ *
+ * 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
+ */
+interface FlashBagInterface extends SessionBagInterface
+{
+ /**
+ * Adds a flash message for the given type.
+ *
+ * @return void
+ */
+ public function add(string $type, mixed $message);
+
+ /**
+ * Registers one or more messages for a given type.
+ *
+ * @return void
+ */
+ public function set(string $type, string|array $messages);
+
+ /**
+ * Gets flash messages for a given type.
+ *
+ * @param string $type Message category type
+ * @param array $default Default value if $type does not exist
+ */
+ public function peek(string $type, array $default = []): array;
+
+ /**
+ * Gets all flash messages.
+ */
+ public function peekAll(): array;
+
+ /**
+ * Gets and clears flash from the stack.
+ *
+ * @param array $default Default value if $type does not exist
+ */
+ public function get(string $type, array $default = []): array;
+
+ /**
+ * Gets and clears flashes from the stack.
+ */
+ public function all(): array;
+
+ /**
+ * Sets all flash messages.
+ *
+ * @return void
+ */
+ public function setAll(array $messages);
+
+ /**
+ * Has flash messages for a given type?
+ */
+ public function has(string $type): bool;
+
+ /**
+ * Returns a list of all defined types.
+ */
+ public function keys(): array;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php b/www/libs/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php
new file mode 100644
index 00000000..90151d38
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/FlashBagAwareSessionInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * 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\Flash\FlashBagInterface;
+
+/**
+ * Interface for session with a flashbag.
+ */
+interface FlashBagAwareSessionInterface extends SessionInterface
+{
+ public function getFlashBag(): FlashBagInterface;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Session.php b/www/libs/vendor/symfony/http-foundation/Session/Session.php
new file mode 100644
index 00000000..5b6db175
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Session.php
@@ -0,0 +1,244 @@
+
+ *
+ * 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\MetadataBag;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(AttributeBag::class);
+class_exists(FlashBag::class);
+class_exists(SessionBagProxy::class);
+
+/**
+ * @author Fabien Potencier
+ * @author Drak
+ *
+ * @implements \IteratorAggregate
+ */
+class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Countable
+{
+ protected $storage;
+
+ private string $flashName;
+ private string $attributeName;
+ private array $data = [];
+ private int $usageIndex = 0;
+ private ?\Closure $usageReporter;
+
+ public function __construct(?SessionStorageInterface $storage = null, ?AttributeBagInterface $attributes = null, ?FlashBagInterface $flashes = null, ?callable $usageReporter = null)
+ {
+ $this->storage = $storage ?? new NativeSessionStorage();
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
+
+ $attributes ??= new AttributeBag();
+ $this->attributeName = $attributes->getName();
+ $this->registerBag($attributes);
+
+ $flashes ??= new FlashBag();
+ $this->flashName = $flashes->getName();
+ $this->registerBag($flashes);
+ }
+
+ public function start(): bool
+ {
+ return $this->storage->start();
+ }
+
+ public function has(string $name): bool
+ {
+ return $this->getAttributeBag()->has($name);
+ }
+
+ public function get(string $name, mixed $default = null): mixed
+ {
+ return $this->getAttributeBag()->get($name, $default);
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $name, mixed $value)
+ {
+ $this->getAttributeBag()->set($name, $value);
+ }
+
+ public function all(): array
+ {
+ return $this->getAttributeBag()->all();
+ }
+
+ /**
+ * @return void
+ */
+ public function replace(array $attributes)
+ {
+ $this->getAttributeBag()->replace($attributes);
+ }
+
+ public function remove(string $name): mixed
+ {
+ return $this->getAttributeBag()->remove($name);
+ }
+
+ /**
+ * @return void
+ */
+ public function clear()
+ {
+ $this->getAttributeBag()->clear();
+ }
+
+ public function isStarted(): bool
+ {
+ return $this->storage->isStarted();
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->getAttributeBag()->all());
+ }
+
+ /**
+ * Returns the number of attributes.
+ */
+ public function count(): int
+ {
+ return \count($this->getAttributeBag()->all());
+ }
+
+ public function &getUsageIndex(): int
+ {
+ return $this->usageIndex;
+ }
+
+ /**
+ * @internal
+ */
+ public function isEmpty(): bool
+ {
+ if ($this->isStarted()) {
+ ++$this->usageIndex;
+ if ($this->usageReporter && 0 <= $this->usageIndex) {
+ ($this->usageReporter)();
+ }
+ }
+ foreach ($this->data as &$data) {
+ if (!empty($data)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function invalidate(?int $lifetime = null): bool
+ {
+ $this->storage->clear();
+
+ return $this->migrate(true, $lifetime);
+ }
+
+ public function migrate(bool $destroy = false, ?int $lifetime = null): bool
+ {
+ return $this->storage->regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * @return void
+ */
+ public function save()
+ {
+ $this->storage->save();
+ }
+
+ public function getId(): string
+ {
+ return $this->storage->getId();
+ }
+
+ /**
+ * @return void
+ */
+ public function setId(string $id)
+ {
+ if ($this->storage->getId() !== $id) {
+ $this->storage->setId($id);
+ }
+ }
+
+ public function getName(): string
+ {
+ return $this->storage->getName();
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->storage->setName($name);
+ }
+
+ public function getMetadataBag(): MetadataBag
+ {
+ ++$this->usageIndex;
+ if ($this->usageReporter && 0 <= $this->usageIndex) {
+ ($this->usageReporter)();
+ }
+
+ return $this->storage->getMetadataBag();
+ }
+
+ /**
+ * @return void
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter));
+ }
+
+ public function getBag(string $name): SessionBagInterface
+ {
+ $bag = $this->storage->getBag($name);
+
+ return method_exists($bag, 'getBag') ? $bag->getBag() : $bag;
+ }
+
+ /**
+ * Gets the flashbag interface.
+ */
+ public function getFlashBag(): FlashBagInterface
+ {
+ 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);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/www/libs/vendor/symfony/http-foundation/Session/SessionBagInterface.php
new file mode 100644
index 00000000..e1c25055
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionBagInterface.php
@@ -0,0 +1,44 @@
+
+ *
+ * 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
+ */
+interface SessionBagInterface
+{
+ /**
+ * Gets this bag's name.
+ */
+ public function getName(): string;
+
+ /**
+ * Initializes the Bag.
+ *
+ * @return void
+ */
+ public function initialize(array &$array);
+
+ /**
+ * Gets the storage key for this bag.
+ */
+ public function getStorageKey(): string;
+
+ /**
+ * Clears out data from bag.
+ *
+ * @return mixed Whatever data was contained
+ */
+ public function clear(): mixed;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/www/libs/vendor/symfony/http-foundation/Session/SessionBagProxy.php
new file mode 100644
index 00000000..e759d94d
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionBagProxy.php
@@ -0,0 +1,83 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+final class SessionBagProxy implements SessionBagInterface
+{
+ private SessionBagInterface $bag;
+ private array $data;
+ private ?int $usageIndex;
+ private ?\Closure $usageReporter;
+
+ public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter)
+ {
+ $this->bag = $bag;
+ $this->data = &$data;
+ $this->usageIndex = &$usageIndex;
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
+ }
+
+ public function getBag(): SessionBagInterface
+ {
+ ++$this->usageIndex;
+ if ($this->usageReporter && 0 <= $this->usageIndex) {
+ ($this->usageReporter)();
+ }
+
+ return $this->bag;
+ }
+
+ public function isEmpty(): bool
+ {
+ if (!isset($this->data[$this->bag->getStorageKey()])) {
+ return true;
+ }
+ ++$this->usageIndex;
+ if ($this->usageReporter && 0 <= $this->usageIndex) {
+ ($this->usageReporter)();
+ }
+
+ return empty($this->data[$this->bag->getStorageKey()]);
+ }
+
+ public function getName(): string
+ {
+ return $this->bag->getName();
+ }
+
+ public function initialize(array &$array): void
+ {
+ ++$this->usageIndex;
+ if ($this->usageReporter && 0 <= $this->usageIndex) {
+ ($this->usageReporter)();
+ }
+
+ $this->data[$this->bag->getStorageKey()] = &$array;
+
+ $this->bag->initialize($array);
+ }
+
+ public function getStorageKey(): string
+ {
+ return $this->bag->getStorageKey();
+ }
+
+ public function clear(): mixed
+ {
+ return $this->bag->clear();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionFactory.php b/www/libs/vendor/symfony/http-foundation/Session/SessionFactory.php
new file mode 100644
index 00000000..c06ed4b7
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionFactory.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\RequestStack;
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(Session::class);
+
+/**
+ * @author Jérémy Derussé
+ */
+class SessionFactory implements SessionFactoryInterface
+{
+ private RequestStack $requestStack;
+ private SessionStorageFactoryInterface $storageFactory;
+ private ?\Closure $usageReporter;
+
+ public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null)
+ {
+ $this->requestStack = $requestStack;
+ $this->storageFactory = $storageFactory;
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
+ }
+
+ public function createSession(): SessionInterface
+ {
+ return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php b/www/libs/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php
new file mode 100644
index 00000000..b24fdc49
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * @author Kevin Bond
+ */
+interface SessionFactoryInterface
+{
+ public function createSession(): SessionInterface;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionInterface.php b/www/libs/vendor/symfony/http-foundation/Session/SessionInterface.php
new file mode 100644
index 00000000..07785a6f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionInterface.php
@@ -0,0 +1,154 @@
+
+ *
+ * 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
+ */
+interface SessionInterface
+{
+ /**
+ * Starts the session storage.
+ *
+ * @throws \RuntimeException if session fails to start
+ */
+ public function start(): bool;
+
+ /**
+ * Returns the session ID.
+ */
+ public function getId(): string;
+
+ /**
+ * Sets the session ID.
+ *
+ * @return void
+ */
+ public function setId(string $id);
+
+ /**
+ * Returns the session name.
+ */
+ public function getName(): string;
+
+ /**
+ * Sets the session name.
+ *
+ * @return void
+ */
+ 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|null $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 invalidate(?int $lifetime = null): bool;
+
+ /**
+ * 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|null $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 migrate(bool $destroy = false, ?int $lifetime = null): bool;
+
+ /**
+ * 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.
+ *
+ * @return void
+ */
+ public function save();
+
+ /**
+ * Checks if an attribute is defined.
+ */
+ public function has(string $name): bool;
+
+ /**
+ * Returns an attribute.
+ */
+ public function get(string $name, mixed $default = null): mixed;
+
+ /**
+ * Sets an attribute.
+ *
+ * @return void
+ */
+ public function set(string $name, mixed $value);
+
+ /**
+ * Returns attributes.
+ */
+ public function all(): array;
+
+ /**
+ * Sets attributes.
+ *
+ * @return void
+ */
+ 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): mixed;
+
+ /**
+ * Clears all attributes.
+ *
+ * @return void
+ */
+ public function clear();
+
+ /**
+ * Checks if the session was started.
+ */
+ public function isStarted(): bool;
+
+ /**
+ * Registers a SessionBagInterface with the session.
+ *
+ * @return void
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ /**
+ * Gets a bag instance by name.
+ */
+ public function getBag(string $name): SessionBagInterface;
+
+ /**
+ * Gets session meta.
+ */
+ public function getMetadataBag(): MetadataBag;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/SessionUtils.php b/www/libs/vendor/symfony/http-foundation/Session/SessionUtils.php
new file mode 100644
index 00000000..504c5848
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/SessionUtils.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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
+ * @author Rémon van de Kamp
+ *
+ * @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, #[\SensitiveParameter] 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;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
new file mode 100644
index 00000000..288c2423
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -0,0 +1,111 @@
+
+ *
+ * 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
+ */
+abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private string $sessionName;
+ private string $prefetchId;
+ private string $prefetchData;
+ private ?string $newSessionId = null;
+ private string $igbinaryEmptyData;
+
+ public function open(string $savePath, string $sessionName): bool
+ {
+ $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;
+ }
+
+ abstract protected function doRead(#[\SensitiveParameter] string $sessionId): string;
+
+ abstract protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool;
+
+ abstract protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool;
+
+ public function validateId(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $this->prefetchData = $this->read($sessionId);
+ $this->prefetchId = $sessionId;
+
+ return '' !== $this->prefetchData;
+ }
+
+ public function read(#[\SensitiveParameter] string $sessionId): string
+ {
+ if (isset($this->prefetchId)) {
+ $prefetchId = $this->prefetchId;
+ $prefetchData = $this->prefetchData;
+ unset($this->prefetchId, $this->prefetchData);
+
+ if ($prefetchId === $sessionId || '' === $prefetchData) {
+ $this->newSessionId = '' === $prefetchData ? $sessionId : null;
+
+ return $prefetchData;
+ }
+ }
+
+ $data = $this->doRead($sessionId);
+ $this->newSessionId = '' === $data ? $sessionId : null;
+
+ return $data;
+ }
+
+ public function write(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ // 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);
+ }
+
+ public function destroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) {
+ if (!isset($this->sessionName)) {
+ throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
+ }
+ $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])) {
+ $params = session_get_cookie_params();
+ unset($params['lifetime']);
+ setcookie($this->sessionName, '', $params);
+ }
+ }
+
+ return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php
new file mode 100644
index 00000000..411a8d1f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Ahmed TAILOULOUTE
+ */
+class IdentityMarshaller implements MarshallerInterface
+{
+ public function marshall(array $values, ?array &$failed): array
+ {
+ foreach ($values as $key => $value) {
+ if (!\is_string($value)) {
+ throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__));
+ }
+ }
+
+ return $values;
+ }
+
+ public function unmarshall(string $value): string
+ {
+ return $value;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php
new file mode 100644
index 00000000..1567f543
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php
@@ -0,0 +1,76 @@
+
+ *
+ * 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\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Ahmed TAILOULOUTE
+ */
+class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private AbstractSessionHandler $handler;
+ private MarshallerInterface $marshaller;
+
+ public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller)
+ {
+ $this->handler = $handler;
+ $this->marshaller = $marshaller;
+ }
+
+ public function open(string $savePath, string $name): bool
+ {
+ return $this->handler->open($savePath, $name);
+ }
+
+ public function close(): bool
+ {
+ return $this->handler->close();
+ }
+
+ public function destroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return $this->handler->destroy($sessionId);
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+
+ public function read(#[\SensitiveParameter] string $sessionId): string
+ {
+ return $this->marshaller->unmarshall($this->handler->read($sessionId));
+ }
+
+ public function write(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $failed = [];
+ $marshalledData = $this->marshaller->marshall(['data' => $data], $failed);
+
+ if (isset($failed['data'])) {
+ return false;
+ }
+
+ return $this->handler->write($sessionId, $marshalledData['data']);
+ }
+
+ public function validateId(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return $this->handler->validateId($sessionId);
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->handler->updateTimestamp($sessionId, $data);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
new file mode 100644
index 00000000..91a023dd
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -0,0 +1,112 @@
+
+ *
+ * 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
+ */
+class MemcachedSessionHandler extends AbstractSessionHandler
+{
+ private \Memcached $memcached;
+
+ /**
+ * Time to live in seconds.
+ */
+ private int|\Closure|null $ttl;
+
+ /**
+ * Key prefix for shared environments.
+ */
+ private string $prefix;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * prefix: The prefix to use for the memcached keys in order to avoid collision
+ * * ttl: 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', 'ttl'])) {
+ throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+ }
+
+ $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null;
+ $this->prefix = $options['prefix'] ?? 'sf2s';
+ }
+
+ public function close(): bool
+ {
+ return $this->memcached->quit();
+ }
+
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ return $this->memcached->get($this->prefix.$sessionId) ?: '';
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl());
+
+ return true;
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl());
+ }
+
+ private function getCompatibleTtl(): int
+ {
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+
+ // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time.
+ // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct.
+ if ($ttl > 60 * 60 * 24 * 30) {
+ $ttl += time();
+ }
+
+ return $ttl;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $result = $this->memcached->delete($this->prefix.$sessionId);
+
+ return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode();
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ // not required here because memcached will auto expire the records anyhow.
+ return 0;
+ }
+
+ /**
+ * Return a Memcached instance.
+ */
+ protected function getMemcached(): \Memcached
+ {
+ return $this->memcached;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php
new file mode 100644
index 00000000..8ed6a7b3
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php
@@ -0,0 +1,100 @@
+
+ *
+ * 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
+ * @author Oliver Radwell
+ */
+class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $currentHandler;
+ private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $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;
+ }
+
+ public function close(): bool
+ {
+ $result = $this->currentHandler->close();
+ $this->writeOnlyHandler->close();
+
+ return $result;
+ }
+
+ public function destroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $result = $this->currentHandler->destroy($sessionId);
+ $this->writeOnlyHandler->destroy($sessionId);
+
+ return $result;
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ $result = $this->currentHandler->gc($maxlifetime);
+ $this->writeOnlyHandler->gc($maxlifetime);
+
+ return $result;
+ }
+
+ public function open(string $savePath, string $sessionName): bool
+ {
+ $result = $this->currentHandler->open($savePath, $sessionName);
+ $this->writeOnlyHandler->open($savePath, $sessionName);
+
+ return $result;
+ }
+
+ public function read(#[\SensitiveParameter] string $sessionId): string
+ {
+ // No reading from new handler until switch-over
+ return $this->currentHandler->read($sessionId);
+ }
+
+ public function write(#[\SensitiveParameter] string $sessionId, string $sessionData): bool
+ {
+ $result = $this->currentHandler->write($sessionId, $sessionData);
+ $this->writeOnlyHandler->write($sessionId, $sessionData);
+
+ return $result;
+ }
+
+ public function validateId(#[\SensitiveParameter] string $sessionId): bool
+ {
+ // No reading from new handler until switch-over
+ return $this->currentHandler->validateId($sessionId);
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $sessionData): bool
+ {
+ $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData);
+ $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData);
+
+ return $result;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
new file mode 100644
index 00000000..d5586030
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -0,0 +1,186 @@
+
+ *
+ * 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 MongoDB\BSON\Binary;
+use MongoDB\BSON\UTCDateTime;
+use MongoDB\Client;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\Query;
+
+/**
+ * Session handler using the MongoDB driver extension.
+ *
+ * @author Markus Bachmann
+ * @author Jérôme Tamarelle
+ *
+ * @see https://php.net/mongodb
+ */
+class MongoDbSessionHandler extends AbstractSessionHandler
+{
+ private Manager $manager;
+ private string $namespace;
+ private array $options;
+ private int|\Closure|null $ttl;
+
+ /**
+ * 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]
+ * * ttl: The time to live in seconds.
+ *
+ * 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..createIndex(
+ * { "": 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(Client|Manager $mongo, array $options)
+ {
+ if (!isset($options['database']) || !isset($options['collection'])) {
+ throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.');
+ }
+
+ if ($mongo instanceof Client) {
+ $mongo = $mongo->getManager();
+ }
+
+ $this->manager = $mongo;
+ $this->namespace = $options['database'].'.'.$options['collection'];
+
+ $this->options = array_merge([
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'expiry_field' => 'expires_at',
+ ], $options);
+ $this->ttl = $this->options['ttl'] ?? null;
+ }
+
+ public function close(): bool
+ {
+ return true;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $write = new BulkWrite();
+ $write->delete(
+ [$this->options['id_field'] => $sessionId],
+ ['limit' => 1]
+ );
+
+ $this->manager->executeBulkWrite($this->namespace, $write);
+
+ return true;
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ $write = new BulkWrite();
+ $write->delete(
+ [$this->options['expiry_field'] => ['$lt' => $this->getUTCDateTime()]],
+ );
+ $result = $this->manager->executeBulkWrite($this->namespace, $write);
+
+ return $result->getDeletedCount() ?? false;
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $expiry = $this->getUTCDateTime($ttl);
+
+ $fields = [
+ $this->options['time_field'] => $this->getUTCDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ $this->options['data_field'] => new Binary($data, Binary::TYPE_GENERIC),
+ ];
+
+ $write = new BulkWrite();
+ $write->update(
+ [$this->options['id_field'] => $sessionId],
+ ['$set' => $fields],
+ ['upsert' => true]
+ );
+
+ $this->manager->executeBulkWrite($this->namespace, $write);
+
+ return true;
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $expiry = $this->getUTCDateTime($ttl);
+
+ $write = new BulkWrite();
+ $write->update(
+ [$this->options['id_field'] => $sessionId],
+ ['$set' => [
+ $this->options['time_field'] => $this->getUTCDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ ]],
+ ['multi' => false],
+ );
+
+ $this->manager->executeBulkWrite($this->namespace, $write);
+
+ return true;
+ }
+
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ $cursor = $this->manager->executeQuery($this->namespace, new Query([
+ $this->options['id_field'] => $sessionId,
+ $this->options['expiry_field'] => ['$gte' => $this->getUTCDateTime()],
+ ], [
+ 'projection' => [
+ '_id' => false,
+ $this->options['data_field'] => true,
+ ],
+ 'limit' => 1,
+ ]));
+
+ foreach ($cursor as $document) {
+ return (string) $document->{$this->options['data_field']} ?? '';
+ }
+
+ // Not found
+ return '';
+ }
+
+ private function getUTCDateTime(int $additionalSeconds = 0): UTCDateTime
+ {
+ return new UTCDateTime((time() + $additionalSeconds) * 1000);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
new file mode 100644
index 00000000..f8c6151a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -0,0 +1,55 @@
+
+ *
+ * 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
+ */
+class NativeFileSessionHandler extends \SessionHandler
+{
+ /**
+ * @param string|null $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)
+ {
+ $baseDir = $savePath ??= \ini_get('session.save_path');
+
+ 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));
+ }
+
+ if ($savePath !== \ini_get('session.save_path')) {
+ ini_set('session.save_path', $savePath);
+ }
+ if ('files' !== \ini_get('session.save_handler')) {
+ ini_set('session.save_handler', 'files');
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
new file mode 100644
index 00000000..a77185e2
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
@@ -0,0 +1,55 @@
+
+ *
+ * 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
+ */
+class NullSessionHandler extends AbstractSessionHandler
+{
+ public function close(): bool
+ {
+ return true;
+ }
+
+ public function validateId(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return true;
+ }
+
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ return '';
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return true;
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return true;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return true;
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ return 0;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
new file mode 100644
index 00000000..9cee76dd
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -0,0 +1,901 @@
+
+ *
+ * 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\Schema\Schema;
+use Doctrine\DBAL\Types\Types;
+
+/**
+ * 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
+ * @author Michael Williams
+ * @author Tobias Schultze
+ */
+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.
+ */
+ public 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.
+ */
+ public 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.
+ */
+ public const LOCK_TRANSACTIONAL = 2;
+
+ private \PDO $pdo;
+
+ /**
+ * DSN string or null for session.save_path or false when lazy connection disabled.
+ */
+ private string|false|null $dsn = false;
+
+ private string $driver;
+ private string $table = 'sessions';
+ private string $idCol = 'sess_id';
+ private string $dataCol = 'sess_data';
+ private string $lifetimeCol = 'sess_lifetime';
+ private string $timeCol = 'sess_time';
+
+ /**
+ * Time to live in seconds.
+ */
+ private int|\Closure|null $ttl;
+
+ /**
+ * Username when lazy-connect.
+ */
+ private ?string $username = null;
+
+ /**
+ * Password when lazy-connect.
+ */
+ private ?string $password = null;
+
+ /**
+ * Connection options when lazy-connect.
+ */
+ private array $connectionOptions = [];
+
+ /**
+ * The strategy for locking, see constants.
+ */
+ private int $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 array $unlockStatements = [];
+
+ /**
+ * True when the current session exists but expired according to session.gc_maxlifetime.
+ */
+ private bool $sessionExpired = false;
+
+ /**
+ * Whether a transaction is active.
+ */
+ private bool $inTransaction = false;
+
+ /**
+ * Whether gc() has been called.
+ */
+ private bool $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]
+ * * ttl: The time to live in seconds.
+ *
+ * @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(#[\SensitiveParameter] \PDO|string|null $pdoOrDsn = null, #[\SensitiveParameter] 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) && str_contains($pdoOrDsn, '://')) {
+ $this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
+ } else {
+ $this->dsn = $pdoOrDsn;
+ }
+
+ $this->table = $options['db_table'] ?? $this->table;
+ $this->idCol = $options['db_id_col'] ?? $this->idCol;
+ $this->dataCol = $options['db_data_col'] ?? $this->dataCol;
+ $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol;
+ $this->timeCol = $options['db_time_col'] ?? $this->timeCol;
+ $this->username = $options['db_username'] ?? $this->username;
+ $this->password = $options['db_password'] ?? $this->password;
+ $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions;
+ $this->lockMode = $options['lock_mode'] ?? $this->lockMode;
+ $this->ttl = $options['ttl'] ?? null;
+ }
+
+ /**
+ * Adds the Table to the Schema if it doesn't exist.
+ */
+ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null): void
+ {
+ if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) {
+ return;
+ }
+
+ $table = $schema->createTable($this->table);
+ switch ($this->driver) {
+ case 'mysql':
+ $table->addColumn($this->idCol, Types::BINARY)->setLength(128)->setNotnull(true);
+ $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
+ $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
+ $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
+ $table->addOption('collate', 'utf8mb4_bin');
+ $table->addOption('engine', 'InnoDB');
+ break;
+ case 'sqlite':
+ $table->addColumn($this->idCol, Types::TEXT)->setNotnull(true);
+ $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
+ $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true);
+ $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true);
+ break;
+ case 'pgsql':
+ $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true);
+ $table->addColumn($this->dataCol, Types::BINARY)->setNotnull(true);
+ $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true);
+ $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true);
+ break;
+ case 'oci':
+ $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true);
+ $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
+ $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true);
+ $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true);
+ break;
+ case 'sqlsrv':
+ $table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true);
+ $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
+ $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
+ $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
+ break;
+ default:
+ throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+ }
+ $table->setPrimaryKey([$this->idCol]);
+ $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx');
+ }
+
+ /**
+ * 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.
+ *
+ * @return void
+ *
+ * @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();
+
+ $sql = match ($this->driver) {
+ // 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
+ 'mysql' => "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 utf8mb4_bin, ENGINE = InnoDB",
+ 'sqlite' => "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)",
+ 'pgsql' => "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)",
+ 'oci' => "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)",
+ 'sqlsrv' => "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)",
+ 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 {$this->lifetimeCol}_idx 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.
+ */
+ public function isSessionExpired(): bool
+ {
+ return $this->sessionExpired;
+ }
+
+ public function open(string $savePath, string $sessionName): bool
+ {
+ $this->sessionExpired = false;
+
+ if (!isset($this->pdo)) {
+ $this->connect($this->dsn ?: $savePath);
+ }
+
+ return parent::open($savePath, $sessionName);
+ }
+
+ public function read(#[\SensitiveParameter] string $sessionId): string
+ {
+ try {
+ return parent::read($sessionId);
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ // 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 0;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ // 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;
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $maxlifetime = (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \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 (str_starts_with($e->getCode(), '23')) {
+ $updateStmt->execute();
+ } else {
+ throw $e;
+ }
+ }
+ }
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $expiry = time() + (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \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->bindValue(':id', $sessionId, \PDO::PARAM_STR);
+ $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT);
+ $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $updateStmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ public function close(): bool
+ {
+ $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";
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->execute();
+ }
+
+ if (false !== $this->dsn) {
+ unset($this->pdo, $this->driver); // only close lazy-connection
+ }
+
+ return true;
+ }
+
+ /**
+ * Lazy-connects to the database.
+ */
+ private function connect(#[\SensitiveParameter] 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(#[\SensitiveParameter] 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 = $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 (str_starts_with($driver, 'pdo_') || str_starts_with($driver, 'pdo-')) {
+ $driver = substr($driver, 4);
+ }
+
+ $dsn = null;
+ switch ($driver) {
+ case 'mysql':
+ $dsn = 'mysql:';
+ if ('' !== ($params['query'] ?? '')) {
+ $queryParams = [];
+ parse_str($params['query'], $queryParams);
+ if ('' !== ($queryParams['charset'] ?? '')) {
+ $dsn .= 'charset='.$queryParams['charset'].';';
+ }
+
+ if ('' !== ($queryParams['unix_socket'] ?? '')) {
+ $dsn .= 'unix_socket='.$queryParams['unix_socket'].';';
+
+ if (isset($params['path'])) {
+ $dbName = substr($params['path'], 1); // Remove the leading slash
+ $dsn .= 'dbname='.$dbName.';';
+ }
+
+ return $dsn;
+ }
+ }
+ // If "unix_socket" is not in the query, we continue with the same process as pgsql
+ // no break
+ case 'pgsql':
+ $dsn ??= 'pgsql:';
+
+ 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.
+ */
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ 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;
+
+ while (true) {
+ $selectStmt->execute();
+ $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
+
+ if ($sessionRows) {
+ $expiry = (int) $sessionRows[0][1];
+
+ 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_BOOL) && 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 (str_starts_with($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 '';
+ }
+ }
+
+ /**
+ * 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(#[\SensitiveParameter] 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();
+
+ switch ($this->driver) {
+ case 'mysql':
+ case 'oci':
+ case 'pgsql':
+ return "SELECT $this->dataCol, $this->lifetimeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
+ case 'sqlsrv':
+ return "SELECT $this->dataCol, $this->lifetimeCol 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 FROM $this->table WHERE $this->idCol = :id";
+ }
+
+ /**
+ * Returns an insert statement supported by the database for writing session data.
+ */
+ private function getInsertStatement(#[\SensitiveParameter] 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(#[\SensitiveParameter] 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(#[\SensitiveParameter] 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(5, time(), \PDO::PARAM_INT);
+ $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindValue(7, time() + $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(8, 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.
+ */
+ protected function getConnection(): \PDO
+ {
+ if (!isset($this->pdo)) {
+ $this->connect($this->dsn ?: \ini_get('session.save_path'));
+ }
+
+ return $this->pdo;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
new file mode 100644
index 00000000..b696eee4
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
@@ -0,0 +1,103 @@
+
+ *
+ * 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 Relay\Relay;
+
+/**
+ * Redis based session storage handler based on the Redis class
+ * provided by the PHP redis extension.
+ *
+ * @author Dalibor Karlović
+ */
+class RedisSessionHandler extends AbstractSessionHandler
+{
+ /**
+ * Key prefix for shared environments.
+ */
+ private string $prefix;
+
+ /**
+ * Time to live in seconds.
+ */
+ private int|\Closure|null $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.
+ *
+ * @throws \InvalidArgumentException When unsupported client or options are passed
+ */
+ public function __construct(
+ private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis,
+ array $options = [],
+ ) {
+ if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) {
+ throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff)));
+ }
+
+ $this->prefix = $options['prefix'] ?? 'sf_s';
+ $this->ttl = $options['ttl'] ?? null;
+ }
+
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ return $this->redis->get($this->prefix.$sessionId) ?: '';
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $result = $this->redis->setEx($this->prefix.$sessionId, (int) $ttl, $data);
+
+ return $result && !$result instanceof ErrorInterface;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ static $unlink = true;
+
+ if ($unlink) {
+ try {
+ $unlink = false !== $this->redis->unlink($this->prefix.$sessionId);
+ } catch (\Throwable) {
+ $unlink = false;
+ }
+ }
+
+ if (!$unlink) {
+ $this->redis->del($this->prefix.$sessionId);
+ }
+
+ return true;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function close(): bool
+ {
+ return true;
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ return 0;
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+
+ return $this->redis->expire($this->prefix.$sessionId, (int) $ttl);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
new file mode 100644
index 00000000..ff5b70d8
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
@@ -0,0 +1,99 @@
+
+ *
+ * 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\Configuration;
+use Doctrine\DBAL\DriverManager;
+use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
+use Doctrine\DBAL\Tools\DsnParser;
+use Relay\Relay;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+
+/**
+ * @author Nicolas Grekas
+ */
+class SessionHandlerFactory
+{
+ public static function createHandler(object|string $connection, array $options = []): AbstractSessionHandler
+ {
+ if ($query = \is_string($connection) ? parse_url($connection) : false) {
+ parse_str($query['query'] ?? '', $query);
+
+ if (($options['ttl'] ?? null) instanceof \Closure) {
+ $query['ttl'] = $options['ttl'];
+ }
+ }
+ $options = ($query ?: []) + $options;
+
+ switch (true) {
+ case $connection instanceof \Redis:
+ case $connection instanceof Relay:
+ case $connection instanceof \RedisArray:
+ case $connection instanceof \RedisCluster:
+ case $connection instanceof \Predis\ClientInterface:
+ 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_debug_type($connection)));
+ case str_starts_with($connection, 'file://'):
+ $savePath = substr($connection, 7);
+
+ return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath));
+
+ case str_starts_with($connection, 'redis:'):
+ case str_starts_with($connection, 'rediss:'):
+ case str_starts_with($connection, 'memcached:'):
+ if (!class_exists(AbstractAdapter::class)) {
+ throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');
+ }
+ $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
+ $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
+
+ return new $handlerClass($connection, array_intersect_key($options, ['prefix' => 1, 'ttl' => 1]));
+
+ case str_starts_with($connection, 'pdo_oci://'):
+ if (!class_exists(DriverManager::class)) {
+ throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".');
+ }
+ $connection[3] = '-';
+ $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection];
+ $config = new Configuration();
+ if (class_exists(DefaultSchemaManagerFactory::class)) {
+ $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory());
+ }
+
+ $connection = DriverManager::getConnection($params, $config);
+ // The condition should be removed once support for DBAL <3.3 is dropped
+ $connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection();
+ // no break;
+
+ case str_starts_with($connection, 'mssql://'):
+ case str_starts_with($connection, 'mysql://'):
+ case str_starts_with($connection, 'mysql2://'):
+ case str_starts_with($connection, 'pgsql://'):
+ case str_starts_with($connection, 'postgres://'):
+ case str_starts_with($connection, 'postgresql://'):
+ case str_starts_with($connection, 'sqlsrv://'):
+ case str_starts_with($connection, 'sqlite://'):
+ case str_starts_with($connection, 'sqlite3://'):
+ return new PdoSessionHandler($connection, $options);
+ }
+
+ throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection));
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
new file mode 100644
index 00000000..1f866874
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
@@ -0,0 +1,89 @@
+
+ *
+ * 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
+ */
+class StrictSessionHandler extends AbstractSessionHandler
+{
+ private \SessionHandlerInterface $handler;
+ private bool $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_debug_type($handler), self::class));
+ }
+
+ $this->handler = $handler;
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ *
+ * @internal
+ */
+ public function isWrapper(): bool
+ {
+ return $this->handler instanceof \SessionHandler;
+ }
+
+ public function open(string $savePath, string $sessionName): bool
+ {
+ parent::open($savePath, $sessionName);
+
+ return $this->handler->open($savePath, $sessionName);
+ }
+
+ protected function doRead(#[\SensitiveParameter] string $sessionId): string
+ {
+ return $this->handler->read($sessionId);
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->write($sessionId, $data);
+ }
+
+ protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->handler->write($sessionId, $data);
+ }
+
+ public function destroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $this->doDestroy = true;
+ $destroyed = parent::destroy($sessionId);
+
+ return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
+ }
+
+ protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ $this->doDestroy = false;
+
+ return $this->handler->destroy($sessionId);
+ }
+
+ public function close(): bool
+ {
+ return $this->handler->close();
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
new file mode 100644
index 00000000..5bb4cfbc
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
@@ -0,0 +1,148 @@
+
+ *
+ * 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
+ */
+class MetadataBag implements SessionBagInterface
+{
+ public const CREATED = 'c';
+ public const UPDATED = 'u';
+ public const LIFETIME = 'l';
+
+ private string $name = '__metadata';
+ private string $storageKey;
+
+ /**
+ * @var array
+ */
+ protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0];
+
+ /**
+ * Unix timestamp.
+ */
+ private int $lastUsed;
+
+ private int $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;
+ }
+
+ /**
+ * @return void
+ */
+ 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.
+ */
+ public function getLifetime(): int
+ {
+ return $this->meta[self::LIFETIME];
+ }
+
+ /**
+ * Stamps a new session's metadata.
+ *
+ * @param int|null $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 void
+ */
+ public function stampNew(?int $lifetime = null)
+ {
+ $this->stampCreated($lifetime);
+ }
+
+ public function getStorageKey(): string
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * Gets the created timestamp metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getCreated(): int
+ {
+ return $this->meta[self::CREATED];
+ }
+
+ /**
+ * Gets the last used metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getLastUsed(): int
+ {
+ return $this->lastUsed;
+ }
+
+ public function clear(): mixed
+ {
+ // nothing to do
+ return null;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets name.
+ *
+ * @return void
+ */
+ 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] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
new file mode 100644
index 00000000..f02793d3
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
@@ -0,0 +1,238 @@
+
+ *
+ * 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
+ * @author Bulat Shakirzyanov
+ * @author Drak
+ */
+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);
+ }
+
+ /**
+ * @return void
+ */
+ public function setSessionData(array $array)
+ {
+ $this->data = $array;
+ }
+
+ public function start(): bool
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (empty($this->id)) {
+ $this->id = $this->generateId();
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ public function regenerate(bool $destroy = false, ?int $lifetime = null): bool
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ $this->metadataBag->stampNew($lifetime);
+ $this->id = $this->generateId();
+
+ return true;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return void
+ */
+ public function setId(string $id)
+ {
+ if ($this->started) {
+ throw new \LogicException('Cannot set session ID after the session has started.');
+ }
+
+ $this->id = $id;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ 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;
+ }
+
+ /**
+ * @return void
+ */
+ 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();
+ }
+
+ /**
+ * @return void
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->bags[$bag->getName()] = $bag;
+ }
+
+ public function getBag(string $name): SessionBagInterface
+ {
+ 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];
+ }
+
+ public function isStarted(): bool
+ {
+ return $this->started;
+ }
+
+ /**
+ * @return void
+ */
+ public function setMetadataBag(?MetadataBag $bag = null)
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ $this->metadataBag = $bag ?? new MetadataBag();
+ }
+
+ /**
+ * Gets the MetadataBag.
+ */
+ public function getMetadataBag(): MetadataBag
+ {
+ return $this->metadataBag;
+ }
+
+ /**
+ * Generates a session ID.
+ *
+ * This doesn't need to be particularly cryptographically secure since this is just
+ * a mock.
+ */
+ protected function generateId(): string
+ {
+ return bin2hex(random_bytes(16));
+ }
+
+ /**
+ * @return void
+ */
+ protected function loadSession()
+ {
+ $bags = array_merge($this->bags, [$this->metadataBag]);
+
+ foreach ($bags as $bag) {
+ $key = $bag->getStorageKey();
+ $this->data[$key] ??= [];
+ $bag->initialize($this->data[$key]);
+ }
+
+ $this->started = true;
+ $this->closed = false;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
new file mode 100644
index 00000000..ef6d9d8f
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
@@ -0,0 +1,152 @@
+
+ *
+ * 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 where you may need to persist session data
+ * across separate PHP processes.
+ *
+ * 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
+ */
+class MockFileSessionStorage extends MockArraySessionStorage
+{
+ private string $savePath;
+
+ /**
+ * @param string|null $savePath Path of directory to save session files
+ */
+ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
+ {
+ $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);
+ }
+
+ public function start(): bool
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (!$this->id) {
+ $this->id = $this->generateId();
+ }
+
+ $this->read();
+
+ $this->started = true;
+
+ return true;
+ }
+
+ public function regenerate(bool $destroy = false, ?int $lifetime = null): bool
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ if ($destroy) {
+ $this->destroy();
+ }
+
+ return parent::regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * @return void
+ */
+ 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) {
+ $path = $this->getFilePath();
+ $tmp = $path.bin2hex(random_bytes(6));
+ file_put_contents($tmp, serialize($data));
+ rename($tmp, $path);
+ } else {
+ $this->destroy();
+ }
+ } finally {
+ $this->data = $data;
+ }
+
+ // this is needed when the session object is re-used across multiple requests
+ // in functional tests.
+ $this->started = false;
+ }
+
+ /**
+ * Deletes a session from persistent storage.
+ * Deliberately leaves session data in memory intact.
+ */
+ private function destroy(): void
+ {
+ set_error_handler(static function () {});
+ try {
+ unlink($this->getFilePath());
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ /**
+ * 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
+ {
+ set_error_handler(static function () {});
+ try {
+ $data = file_get_contents($this->getFilePath());
+ } finally {
+ restore_error_handler();
+ }
+
+ $this->data = $data ? unserialize($data) : [];
+
+ $this->loadSession();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php
new file mode 100644
index 00000000..6727cf14
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php
@@ -0,0 +1,42 @@
+
+ *
+ * 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\Request;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(MockFileSessionStorage::class);
+
+/**
+ * @author Jérémy Derussé
+ */
+class MockFileSessionStorageFactory implements SessionStorageFactoryInterface
+{
+ private ?string $savePath;
+ private string $name;
+ private ?MetadataBag $metaBag;
+
+ /**
+ * @see MockFileSessionStorage constructor.
+ */
+ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
+ {
+ $this->savePath = $savePath;
+ $this->name = $name;
+ $this->metaBag = $metaBag;
+ }
+
+ public function createStorage(?Request $request): SessionStorageInterface
+ {
+ return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
new file mode 100644
index 00000000..f63de574
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
@@ -0,0 +1,449 @@
+
+ *
+ * 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\Storage\Handler\StrictSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(MetadataBag::class);
+class_exists(StrictSessionHandler::class);
+class_exists(SessionHandlerProxy::class);
+
+/**
+ * This provides a base class for session attribute storage.
+ *
+ * @author Drak
+ */
+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;
+
+ /**
+ * 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, "1"
+ * use_cookies, "1"
+ * use_only_cookies, "1"
+ * use_trans_sid, "0"
+ * sid_length, "32"
+ * sid_bits_per_character, "5"
+ * trans_sid_hosts, $_SERVER['HTTP_HOST']
+ * trans_sid_tags, "a=href,area=href,frame=src,form="
+ */
+ public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $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.
+ */
+ public function getSaveHandler(): AbstractProxy|\SessionHandlerInterface
+ {
+ return $this->saveHandler;
+ }
+
+ public function start(): bool
+ {
+ 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_BOOL) && 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));
+ }
+
+ $sessionId = $_COOKIE[session_name()] ?? null;
+ /*
+ * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.
+ *
+ * ---------- Part 1
+ *
+ * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
+ * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
+ * Allowed values are integers such as:
+ * - 4 for range `a-f0-9`
+ * - 5 for range `a-v0-9`
+ * - 6 for range `a-zA-Z0-9,-`
+ *
+ * ---------- Part 2
+ *
+ * The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
+ * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
+ * Allowed values are integers between 22 and 256, but we use 250 for the max.
+ *
+ * Where does the 250 come from?
+ * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
+ * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
+ *
+ * ---------- Conclusion
+ *
+ * The parts 1 and 2 prevent the warning below:
+ * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.`
+ *
+ * The part 2 prevents the warning below:
+ * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`
+ */
+ if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {
+ // the session ID in the header is invalid, create a new one
+ session_id(session_create_id());
+ }
+
+ // ok to try and start the session
+ if (!session_start()) {
+ throw new \RuntimeException('Failed to start the session.');
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ public function getId(): string
+ {
+ return $this->saveHandler->getId();
+ }
+
+ /**
+ * @return void
+ */
+ public function setId(string $id)
+ {
+ $this->saveHandler->setId($id);
+ }
+
+ public function getName(): string
+ {
+ return $this->saveHandler->getName();
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->saveHandler->setName($name);
+ }
+
+ public function regenerate(bool $destroy = false, ?int $lifetime = null): bool
+ {
+ // 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 && $lifetime != \ini_get('session.cookie_lifetime')) {
+ $this->save();
+ ini_set('session.cookie_lifetime', $lifetime);
+ $this->start();
+ }
+
+ if ($destroy) {
+ $this->metadataBag->stampNew();
+ }
+
+ return session_regenerate_id($destroy);
+ }
+
+ /**
+ * @return void
+ */
+ 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 ($_SESSION && [$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 && str_starts_with($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', $handler::class);
+ }
+
+ 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;
+ }
+
+ /**
+ * @return void
+ */
+ 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();
+ }
+
+ /**
+ * @return void
+ */
+ 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;
+ }
+
+ public function getBag(string $name): SessionBagInterface
+ {
+ 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];
+ }
+
+ /**
+ * @return void
+ */
+ public function setMetadataBag(?MetadataBag $metaBag = null)
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+ $this->metadataBag = $metaBag ?? new MetadataBag();
+ }
+
+ /**
+ * Gets the MetadataBag.
+ */
+ public function getMetadataBag(): MetadataBag
+ {
+ return $this->metadataBag;
+ }
+
+ public function isStarted(): bool
+ {
+ 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
+ *
+ * @return void
+ */
+ 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',
+ 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
+ ]);
+
+ foreach ($options as $key => $value) {
+ if (isset($validOptions[$key])) {
+ if ('cookie_secure' === $key && 'auto' === $value) {
+ continue;
+ }
+ ini_set('session.'.$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.
+ *
+ * @see https://php.net/session-set-save-handler
+ * @see https://php.net/sessionhandlerinterface
+ * @see https://php.net/sessionhandler
+ *
+ * @return void
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setSaveHandler(AbstractProxy|\SessionHandlerInterface|null $saveHandler = null)
+ {
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
+
+ // 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.
+ *
+ * @return void
+ */
+ 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;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php
new file mode 100644
index 00000000..6463a4c1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(NativeSessionStorage::class);
+
+/**
+ * @author Jérémy Derussé
+ */
+class NativeSessionStorageFactory implements SessionStorageFactoryInterface
+{
+ private array $options;
+ private AbstractProxy|\SessionHandlerInterface|null $handler;
+ private ?MetadataBag $metaBag;
+ private bool $secure;
+
+ /**
+ * @see NativeSessionStorage constructor.
+ */
+ public function __construct(array $options = [], AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
+ {
+ $this->options = $options;
+ $this->handler = $handler;
+ $this->metaBag = $metaBag;
+ $this->secure = $secure;
+ }
+
+ public function createStorage(?Request $request): SessionStorageInterface
+ {
+ $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag);
+ if ($this->secure && $request?->isSecure()) {
+ $storage->setOptions(['cookie_secure' => true]);
+ }
+
+ return $storage;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
new file mode 100644
index 00000000..4fb26d2a
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
@@ -0,0 +1,58 @@
+
+ *
+ * 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
+ */
+class PhpBridgeSessionStorage extends NativeSessionStorage
+{
+ public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null)
+ {
+ if (!\extension_loaded('session')) {
+ throw new \LogicException('PHP extension "session" is required.');
+ }
+
+ $this->setMetadataBag($metaBag);
+ $this->setSaveHandler($handler);
+ }
+
+ public function start(): bool
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * @return void
+ */
+ 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();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php
new file mode 100644
index 00000000..aa4f800d
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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\Request;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(PhpBridgeSessionStorage::class);
+
+/**
+ * @author Jérémy Derussé
+ */
+class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
+{
+ private AbstractProxy|\SessionHandlerInterface|null $handler;
+ private ?MetadataBag $metaBag;
+ private bool $secure;
+
+ public function __construct(AbstractProxy|\SessionHandlerInterface|null $handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
+ {
+ $this->handler = $handler;
+ $this->metaBag = $metaBag;
+ $this->secure = $secure;
+ }
+
+ public function createStorage(?Request $request): SessionStorageInterface
+ {
+ $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag);
+ if ($this->secure && $request?->isSecure()) {
+ $storage->setOptions(['cookie_secure' => true]);
+ }
+
+ return $storage;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
new file mode 100644
index 00000000..2fcd06b1
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
@@ -0,0 +1,110 @@
+
+ *
+ * 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
+ */
+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.
+ */
+ public function getSaveHandlerName(): ?string
+ {
+ return $this->saveHandlerName;
+ }
+
+ /**
+ * Is this proxy handler and instance of \SessionHandlerInterface.
+ */
+ public function isSessionHandlerInterface(): bool
+ {
+ return $this instanceof \SessionHandlerInterface;
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ */
+ public function isWrapper(): bool
+ {
+ return $this->wrapper;
+ }
+
+ /**
+ * Has a session started?
+ */
+ public function isActive(): bool
+ {
+ return \PHP_SESSION_ACTIVE === session_status();
+ }
+
+ /**
+ * Gets the session ID.
+ */
+ public function getId(): string
+ {
+ return session_id();
+ }
+
+ /**
+ * Sets the session ID.
+ *
+ * @return void
+ *
+ * @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.
+ */
+ public function getName(): string
+ {
+ return session_name();
+ }
+
+ /**
+ * Sets the session name.
+ *
+ * @return void
+ *
+ * @throws \LogicException
+ */
+ public function setName(string $name)
+ {
+ if ($this->isActive()) {
+ throw new \LogicException('Cannot change the name of an active session.');
+ }
+
+ session_name($name);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
new file mode 100644
index 00000000..7bf3f9ff
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -0,0 +1,76 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+
+/**
+ * @author Drak
+ */
+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 || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user';
+ }
+
+ public function getHandler(): \SessionHandlerInterface
+ {
+ return $this->handler;
+ }
+
+ // \SessionHandlerInterface
+
+ public function open(string $savePath, string $sessionName): bool
+ {
+ return $this->handler->open($savePath, $sessionName);
+ }
+
+ public function close(): bool
+ {
+ return $this->handler->close();
+ }
+
+ public function read(#[\SensitiveParameter] string $sessionId): string|false
+ {
+ return $this->handler->read($sessionId);
+ }
+
+ public function write(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->handler->write($sessionId, $data);
+ }
+
+ public function destroy(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return $this->handler->destroy($sessionId);
+ }
+
+ public function gc(int $maxlifetime): int|false
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+
+ public function validateId(#[\SensitiveParameter] string $sessionId): bool
+ {
+ return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId);
+ }
+
+ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
+ {
+ return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data);
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php
new file mode 100644
index 00000000..d03f0da4
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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\Request;
+
+/**
+ * @author Jérémy Derussé
+ */
+interface SessionStorageFactoryInterface
+{
+ /**
+ * Creates a new instance of SessionStorageInterface.
+ */
+ public function createStorage(?Request $request): SessionStorageInterface;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
new file mode 100644
index 00000000..7865135b
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
@@ -0,0 +1,126 @@
+
+ *
+ * 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
+ * @author Drak
+ */
+interface SessionStorageInterface
+{
+ /**
+ * Starts the session.
+ *
+ * @throws \RuntimeException if something goes wrong starting the session
+ */
+ public function start(): bool;
+
+ /**
+ * Checks if the session is started.
+ */
+ public function isStarted(): bool;
+
+ /**
+ * Returns the session ID.
+ */
+ public function getId(): string;
+
+ /**
+ * Sets the session ID.
+ *
+ * @return void
+ */
+ public function setId(string $id);
+
+ /**
+ * Returns the session name.
+ */
+ public function getName(): string;
+
+ /**
+ * Sets the session name.
+ *
+ * @return void
+ */
+ 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|null $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.
+ *
+ * @throws \RuntimeException If an error occurs while regenerating this storage
+ */
+ public function regenerate(bool $destroy = false, ?int $lifetime = null): bool;
+
+ /**
+ * 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.
+ *
+ * @return void
+ *
+ * @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.
+ *
+ * @return void
+ */
+ public function clear();
+
+ /**
+ * Gets a SessionBagInterface by name.
+ *
+ * @throws \InvalidArgumentException If the bag does not exist
+ */
+ public function getBag(string $name): SessionBagInterface;
+
+ /**
+ * Registers a SessionBagInterface for use.
+ *
+ * @return void
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ public function getMetadataBag(): MetadataBag;
+}
diff --git a/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php b/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php
new file mode 100644
index 00000000..5b20ce91
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/StreamedJsonResponse.php
@@ -0,0 +1,162 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * StreamedJsonResponse represents a streamed HTTP response for JSON.
+ *
+ * A StreamedJsonResponse uses a structure and generics to create an
+ * efficient resource-saving JSON response.
+ *
+ * It is recommended to use flush() function after a specific number of items to directly stream the data.
+ *
+ * @see flush()
+ *
+ * @author Alexander Schranz
+ *
+ * Example usage:
+ *
+ * function loadArticles(): \Generator
+ * // some streamed loading
+ * yield ['title' => 'Article 1'];
+ * yield ['title' => 'Article 2'];
+ * yield ['title' => 'Article 3'];
+ * // recommended to use flush() after every specific number of items
+ * }),
+ *
+ * $response = new StreamedJsonResponse(
+ * // json structure with generators in which will be streamed
+ * [
+ * '_embedded' => [
+ * 'articles' => loadArticles(), // any generator which you want to stream as list of data
+ * ],
+ * ],
+ * );
+ */
+class StreamedJsonResponse extends StreamedResponse
+{
+ private const PLACEHOLDER = '__symfony_json__';
+
+ /**
+ * @param mixed[] $data JSON Data containing PHP generators which will be streamed as list of data or a Generator
+ * @param int $status The HTTP status code (200 "OK" by default)
+ * @param array $headers An array of HTTP headers
+ * @param int $encodingOptions Flags for the json_encode() function
+ */
+ public function __construct(
+ private readonly iterable $data,
+ int $status = 200,
+ array $headers = [],
+ private int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS,
+ ) {
+ parent::__construct($this->stream(...), $status, $headers);
+
+ if (!$this->headers->get('Content-Type')) {
+ $this->headers->set('Content-Type', 'application/json');
+ }
+ }
+
+ private function stream(): void
+ {
+ $jsonEncodingOptions = \JSON_THROW_ON_ERROR | $this->encodingOptions;
+ $keyEncodingOptions = $jsonEncodingOptions & ~\JSON_NUMERIC_CHECK;
+
+ $this->streamData($this->data, $jsonEncodingOptions, $keyEncodingOptions);
+ }
+
+ private function streamData(mixed $data, int $jsonEncodingOptions, int $keyEncodingOptions): void
+ {
+ if (\is_array($data)) {
+ $this->streamArray($data, $jsonEncodingOptions, $keyEncodingOptions);
+
+ return;
+ }
+
+ if (is_iterable($data) && !$data instanceof \JsonSerializable) {
+ $this->streamIterable($data, $jsonEncodingOptions, $keyEncodingOptions);
+
+ return;
+ }
+
+ echo json_encode($data, $jsonEncodingOptions);
+ }
+
+ private function streamArray(array $data, int $jsonEncodingOptions, int $keyEncodingOptions): void
+ {
+ $generators = [];
+
+ array_walk_recursive($data, function (&$item, $key) use (&$generators) {
+ if (self::PLACEHOLDER === $key) {
+ // if the placeholder is already in the structure it should be replaced with a new one that explode
+ // works like expected for the structure
+ $generators[] = $key;
+ }
+
+ // generators should be used but for better DX all kind of Traversable and objects are supported
+ if (\is_object($item)) {
+ $generators[] = $item;
+ $item = self::PLACEHOLDER;
+ } elseif (self::PLACEHOLDER === $item) {
+ // if the placeholder is already in the structure it should be replaced with a new one that explode
+ // works like expected for the structure
+ $generators[] = $item;
+ }
+ });
+
+ $jsonParts = explode('"'.self::PLACEHOLDER.'"', json_encode($data, $jsonEncodingOptions));
+
+ foreach ($generators as $index => $generator) {
+ // send first and between parts of the structure
+ echo $jsonParts[$index];
+
+ $this->streamData($generator, $jsonEncodingOptions, $keyEncodingOptions);
+ }
+
+ // send last part of the structure
+ echo $jsonParts[array_key_last($jsonParts)];
+ }
+
+ private function streamIterable(iterable $iterable, int $jsonEncodingOptions, int $keyEncodingOptions): void
+ {
+ $isFirstItem = true;
+ $startTag = '[';
+
+ foreach ($iterable as $key => $item) {
+ if ($isFirstItem) {
+ $isFirstItem = false;
+ // depending on the first elements key the generator is detected as a list or map
+ // we can not check for a whole list or map because that would hurt the performance
+ // of the streamed response which is the main goal of this response class
+ if (0 !== $key) {
+ $startTag = '{';
+ }
+
+ echo $startTag;
+ } else {
+ // if not first element of the generic, a separator is required between the elements
+ echo ',';
+ }
+
+ if ('{' === $startTag) {
+ echo json_encode((string) $key, $keyEncodingOptions).':';
+ }
+
+ $this->streamData($item, $jsonEncodingOptions, $keyEncodingOptions);
+ }
+
+ if ($isFirstItem) { // indicates that the generator was empty
+ echo '[';
+ }
+
+ echo '[' === $startTag ? ']' : '}';
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/StreamedResponse.php b/www/libs/vendor/symfony/http-foundation/StreamedResponse.php
new file mode 100644
index 00000000..0ab88e09
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/StreamedResponse.php
@@ -0,0 +1,131 @@
+
+ *
+ * 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
+ */
+class StreamedResponse extends Response
+{
+ protected $callback;
+ protected $streamed;
+ private bool $headersSent;
+
+ /**
+ * @param int $status The HTTP status code (200 "OK" by default)
+ */
+ 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;
+ }
+
+ /**
+ * Sets the PHP callback associated with this Response.
+ *
+ * @return $this
+ */
+ public function setCallback(callable $callback): static
+ {
+ $this->callback = $callback(...);
+
+ return $this;
+ }
+
+ public function getCallback(): ?\Closure
+ {
+ if (!isset($this->callback)) {
+ return null;
+ }
+
+ return ($this->callback)(...);
+ }
+
+ /**
+ * This method only sends the headers once.
+ *
+ * @param positive-int|null $statusCode The status code to use, override the statusCode property if set and not null
+ *
+ * @return $this
+ */
+ public function sendHeaders(/* int $statusCode = null */): static
+ {
+ if ($this->headersSent) {
+ return $this;
+ }
+
+ $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null;
+ if ($statusCode < 100 || $statusCode >= 200) {
+ $this->headersSent = true;
+ }
+
+ return parent::sendHeaders($statusCode);
+ }
+
+ /**
+ * This method only sends the content once.
+ *
+ * @return $this
+ */
+ public function sendContent(): static
+ {
+ if ($this->streamed) {
+ return $this;
+ }
+
+ $this->streamed = true;
+
+ if (!isset($this->callback)) {
+ throw new \LogicException('The Response callback must be set.');
+ }
+
+ ($this->callback)();
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ *
+ * @throws \LogicException when the content is not null
+ */
+ public function setContent(?string $content): static
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
+ }
+
+ $this->streamed = true;
+
+ return $this;
+ }
+
+ public function getContent(): string|false
+ {
+ return false;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
new file mode 100644
index 00000000..6e114268
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php
@@ -0,0 +1,48 @@
+
+ *
+ * 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 string $name;
+ private string $value;
+
+ public function __construct(string $name, string $value)
+ {
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ public function toString(): string
+ {
+ return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value);
+ }
+
+ /**
+ * @param Request $request
+ */
+ protected function matches($request): bool
+ {
+ return $this->value === $request->attributes->get($this->name);
+ }
+
+ /**
+ * @param Request $request
+ */
+ protected function failureDescription($request): string
+ {
+ return 'the Request '.$this->toString();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
new file mode 100644
index 00000000..768007b9
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php
@@ -0,0 +1,76 @@
+
+ *
+ * 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 string $name;
+ private string $value;
+ private string $path;
+ private ?string $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;
+ }
+
+ 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
+ */
+ protected function matches($response): bool
+ {
+ $cookie = $this->getCookie($response);
+ if (!$cookie) {
+ return false;
+ }
+
+ return $this->value === (string) $cookie->getValue();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ protected function getCookie(Response $response): ?Cookie
+ {
+ $cookies = $response->headers->getCookies();
+
+ $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain);
+
+ return reset($filteredCookies) ?: null;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php
new file mode 100644
index 00000000..b14dcee9
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php
@@ -0,0 +1,62 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Asserts that the response is in the given format.
+ *
+ * @author Kévin Dunglas
+ */
+final class ResponseFormatSame extends Constraint
+{
+ private Request $request;
+ private ?string $format;
+
+ public function __construct(Request $request, ?string $format)
+ {
+ $this->request = $request;
+ $this->format = $format;
+ }
+
+ public function toString(): string
+ {
+ return 'format is '.($this->format ?? 'null');
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $this->format === $this->request->getFormat($response->headers->get('Content-Type'));
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function additionalFailureDescription($response): string
+ {
+ return (string) $response;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
new file mode 100644
index 00000000..8eccea9d
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php
@@ -0,0 +1,68 @@
+
+ *
+ * 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 string $name;
+ private string $path;
+ private ?string $domain;
+
+ public function __construct(string $name, string $path = '/', ?string $domain = null)
+ {
+ $this->name = $name;
+ $this->path = $path;
+ $this->domain = $domain;
+ }
+
+ 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
+ */
+ protected function matches($response): bool
+ {
+ return null !== $this->getCookie($response);
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ private function getCookie(Response $response): ?Cookie
+ {
+ $cookies = $response->headers->getCookies();
+
+ $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain);
+
+ return reset($filteredCookies) ?: null;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
new file mode 100644
index 00000000..08522c89
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php
@@ -0,0 +1,46 @@
+
+ *
+ * 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 string $headerName;
+
+ public function __construct(string $headerName)
+ {
+ $this->headerName = $headerName;
+ }
+
+ public function toString(): string
+ {
+ return sprintf('has header "%s"', $this->headerName);
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $response->headers->has($this->headerName);
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php
new file mode 100644
index 00000000..9286ec71
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderLocationSame.php
@@ -0,0 +1,65 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseHeaderLocationSame extends Constraint
+{
+ public function __construct(private Request $request, private string $expectedValue)
+ {
+ }
+
+ public function toString(): string
+ {
+ return sprintf('has header "Location" matching "%s"', $this->expectedValue);
+ }
+
+ protected function matches($other): bool
+ {
+ if (!$other instanceof Response) {
+ return false;
+ }
+
+ $location = $other->headers->get('Location');
+
+ if (null === $location) {
+ return false;
+ }
+
+ return $this->toFullUrl($this->expectedValue) === $this->toFullUrl($location);
+ }
+
+ protected function failureDescription($other): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ private function toFullUrl(string $url): string
+ {
+ if (null === parse_url($url, \PHP_URL_PATH)) {
+ $url .= '/';
+ }
+
+ if (str_starts_with($url, '//')) {
+ return sprintf('%s:%s', $this->request->getScheme(), $url);
+ }
+
+ if (str_starts_with($url, '/')) {
+ return $this->request->getSchemeAndHttpHost().$url;
+ }
+
+ return $url;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
new file mode 100644
index 00000000..8141df97
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php
@@ -0,0 +1,48 @@
+
+ *
+ * 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 string $headerName;
+ private string $expectedValue;
+
+ public function __construct(string $headerName, string $expectedValue)
+ {
+ $this->headerName = $headerName;
+ $this->expectedValue = $expectedValue;
+ }
+
+ public function toString(): string
+ {
+ return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $this->expectedValue === $response->headers->get($this->headerName, null);
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
new file mode 100644
index 00000000..bb0e53d0
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseIsRedirected extends Constraint
+{
+ public function toString(): string
+ {
+ return 'is redirected';
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $response->isRedirect();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function additionalFailureDescription($response): string
+ {
+ return (string) $response;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
new file mode 100644
index 00000000..2c6b7680
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseIsSuccessful extends Constraint
+{
+ public function toString(): string
+ {
+ return 'is successful';
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $response->isSuccessful();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function additionalFailureDescription($response): string
+ {
+ return (string) $response;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php
new file mode 100644
index 00000000..52336a5b
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Test\Constraint;
+
+use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\HttpFoundation\Response;
+
+final class ResponseIsUnprocessable extends Constraint
+{
+ public function toString(): string
+ {
+ return 'is unprocessable';
+ }
+
+ /**
+ * @param Response $other
+ */
+ protected function matches($other): bool
+ {
+ return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode();
+ }
+
+ /**
+ * @param Response $other
+ */
+ protected function failureDescription($other): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ /**
+ * @param Response $other
+ */
+ protected function additionalFailureDescription($other): string
+ {
+ return (string) $other;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
new file mode 100644
index 00000000..cd565ba9
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php
@@ -0,0 +1,54 @@
+
+ *
+ * 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 int $statusCode;
+
+ public function __construct(int $statusCode)
+ {
+ $this->statusCode = $statusCode;
+ }
+
+ public function toString(): string
+ {
+ return 'status code is '.$this->statusCode;
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function matches($response): bool
+ {
+ return $this->statusCode === $response->getStatusCode();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function failureDescription($response): string
+ {
+ return 'the Response '.$this->toString();
+ }
+
+ /**
+ * @param Response $response
+ */
+ protected function additionalFailureDescription($response): string
+ {
+ return (string) $response;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/UriSigner.php b/www/libs/vendor/symfony/http-foundation/UriSigner.php
new file mode 100644
index 00000000..b0498772
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/UriSigner.php
@@ -0,0 +1,112 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * @author Fabien Potencier
+ */
+class UriSigner
+{
+ private string $secret;
+ private string $parameter;
+
+ /**
+ * @param string $parameter Query string parameter to use
+ */
+ public function __construct(#[\SensitiveParameter] string $secret, string $parameter = '_hash')
+ {
+ if (!$secret) {
+ throw new \InvalidArgumentException('A non-empty secret is required.');
+ }
+
+ $this->secret = $secret;
+ $this->parameter = $parameter;
+ }
+
+ /**
+ * Signs a URI.
+ *
+ * The given URI is signed by adding the query string parameter
+ * which value depends on the URI and the secret.
+ */
+ public function sign(string $uri): string
+ {
+ $url = parse_url($uri);
+ $params = [];
+
+ if (isset($url['query'])) {
+ parse_str($url['query'], $params);
+ }
+
+ $uri = $this->buildUrl($url, $params);
+ $params[$this->parameter] = $this->computeHash($uri);
+
+ return $this->buildUrl($url, $params);
+ }
+
+ /**
+ * Checks that a URI contains the correct hash.
+ */
+ public function check(string $uri): bool
+ {
+ $url = parse_url($uri);
+ $params = [];
+
+ if (isset($url['query'])) {
+ parse_str($url['query'], $params);
+ }
+
+ if (empty($params[$this->parameter])) {
+ return false;
+ }
+
+ $hash = $params[$this->parameter];
+ unset($params[$this->parameter]);
+
+ return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash);
+ }
+
+ public function checkRequest(Request $request): bool
+ {
+ $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : '';
+
+ // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
+ return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs);
+ }
+
+ private function computeHash(string $uri): string
+ {
+ return base64_encode(hash_hmac('sha256', $uri, $this->secret, true));
+ }
+
+ private function buildUrl(array $url, array $params = []): string
+ {
+ ksort($params, \SORT_STRING);
+ $url['query'] = http_build_query($params, '', '&');
+
+ $scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';
+ $host = $url['host'] ?? '';
+ $port = isset($url['port']) ? ':'.$url['port'] : '';
+ $user = $url['user'] ?? '';
+ $pass = isset($url['pass']) ? ':'.$url['pass'] : '';
+ $pass = ($user || $pass) ? "$pass@" : '';
+ $path = $url['path'] ?? '';
+ $query = $url['query'] ? '?'.$url['query'] : '';
+ $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';
+
+ return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
+ }
+}
+
+if (!class_exists(\Symfony\Component\HttpKernel\UriSigner::class, false)) {
+ class_alias(UriSigner::class, \Symfony\Component\HttpKernel\UriSigner::class);
+}
diff --git a/www/libs/vendor/symfony/http-foundation/UrlHelper.php b/www/libs/vendor/symfony/http-foundation/UrlHelper.php
new file mode 100644
index 00000000..f971cf66
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/UrlHelper.php
@@ -0,0 +1,108 @@
+
+ *
+ * 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;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+
+/**
+ * A helper service for manipulating URLs within and outside the request scope.
+ *
+ * @author Valentin Udaltsov
+ */
+final class UrlHelper
+{
+ public function __construct(
+ private RequestStack $requestStack,
+ private RequestContextAwareInterface|RequestContext|null $requestContext = null,
+ ) {
+ }
+
+ public function getAbsoluteUrl(string $path): string
+ {
+ if (str_contains($path, '://') || str_starts_with($path, '//')) {
+ return $path;
+ }
+
+ if (null === $request = $this->requestStack->getMainRequest()) {
+ 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 (str_contains($path, '://') || str_starts_with($path, '//')) {
+ return $path;
+ }
+
+ if (null === $request = $this->requestStack->getMainRequest()) {
+ return $path;
+ }
+
+ return $request->getRelativeUriForPath($path);
+ }
+
+ private function getAbsoluteUrlFromContext(string $path): string
+ {
+ if (null === $context = $this->requestContext) {
+ return $path;
+ }
+
+ if ($context instanceof RequestContextAwareInterface) {
+ $context = $context->getContext();
+ }
+
+ if ('' === $host = $context->getHost()) {
+ return $path;
+ }
+
+ $scheme = $context->getScheme();
+ $port = '';
+
+ if ('http' === $scheme && 80 !== $context->getHttpPort()) {
+ $port = ':'.$context->getHttpPort();
+ } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) {
+ $port = ':'.$context->getHttpsPort();
+ }
+
+ if ('#' === $path[0]) {
+ $queryString = $context->getQueryString();
+ $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path;
+ } elseif ('?' === $path[0]) {
+ $path = $context->getPathInfo().$path;
+ }
+
+ if ('/' !== $path[0]) {
+ $path = rtrim($context->getBaseUrl(), '/').'/'.$path;
+ }
+
+ return $scheme.'://'.$host.$port.$path;
+ }
+}
diff --git a/www/libs/vendor/symfony/http-foundation/composer.json b/www/libs/vendor/symfony/http-foundation/composer.json
new file mode 100644
index 00000000..732a011e
--- /dev/null
+++ b/www/libs/vendor/symfony/http-foundation/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "symfony/http-foundation",
+ "type": "library",
+ "description": "Defines an object-oriented layer for the HTTP specification",
+ "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": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php83": "^1.27"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.13.1|^3|^4",
+ "predis/predis": "^1.1|^2.0",
+ "symfony/cache": "^6.4.12|^7.1.5",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
+ "symfony/mime": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/rate-limiter": "^5.4|^6.0|^7.0"
+ },
+ "conflict": {
+ "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/LICENSE b/www/libs/vendor/symfony/polyfill-mbstring/LICENSE
new file mode 100644
index 00000000..6e3afce6
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+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.
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php b/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php
new file mode 100644
index 00000000..3d45c9d9
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -0,0 +1,1045 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding - Convert character encoding
+ * - mb_convert_variables - Convert character code in variable(s)
+ * - mb_decode_mimeheader - Decode string in MIME header field
+ * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case - Perform case folding on a string
+ * - mb_detect_encoding - Detect character encoding
+ * - mb_get_info - Get internal settings of mbstring
+ * - mb_http_input - Detect HTTP input character encoding
+ * - mb_http_output - Set/Get HTTP output character encoding
+ * - mb_internal_encoding - Set/Get internal character encoding
+ * - mb_list_encodings - Returns an array of all supported encodings
+ * - mb_ord - Returns the Unicode code point of a character
+ * - mb_output_handler - Callback function converts character encoding in output buffer
+ * - mb_scrub - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen - Get string length
+ * - mb_strpos - Find position of first occurrence of string in a string
+ * - mb_strrpos - Find position of last occurrence of a string in a string
+ * - mb_str_split - Convert a string to an array
+ * - mb_strtolower - Make a string lowercase
+ * - mb_strtoupper - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr - Get part of string
+ * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr - Finds first occurrence of a string within another
+ * - mb_strwidth - Return width of string
+ * - mb_substr_count - Count the number of substring occurrences
+ * - mb_ucfirst - Make a string's first character uppercase
+ * - mb_lcfirst - Make a string's first character lowercase
+ * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string
+ * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string
+ * - mb_rtrim - Strip whitespace (or other characters) from the end of a string
+ *
+ * Not implemented:
+ * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_* - Regular expression with multibyte support
+ * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name - Get MIME charset string
+ * - mb_regex_encoding - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options - Set/Get the default options for mbregex functions
+ * - mb_send_mail - Send encoded mail
+ * - mb_split - Split multibyte string using regular expression
+ * - mb_strcut - Get part of string
+ * - mb_strimwidth - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class Mbstring
+{
+ public const MB_CASE_FOLD = \PHP_INT_MAX;
+
+ private const SIMPLE_CASE_FOLD = [
+ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
+ ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
+ ];
+
+ private static $encodingList = ['ASCII', 'UTF-8'];
+ private static $language = 'neutral';
+ private static $internalEncoding = 'UTF-8';
+
+ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
+ {
+ if (\is_array($s)) {
+ $r = [];
+ foreach ($s as $str) {
+ $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding);
+ }
+
+ return $r;
+ }
+
+ if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
+ $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
+ } else {
+ $fromEncoding = self::getEncoding($fromEncoding);
+ }
+
+ $toEncoding = self::getEncoding($toEncoding);
+
+ if ('BASE64' === $fromEncoding) {
+ $s = base64_decode($s);
+ $fromEncoding = $toEncoding;
+ }
+
+ if ('BASE64' === $toEncoding) {
+ return base64_encode($s);
+ }
+
+ if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
+ if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
+ $fromEncoding = 'Windows-1252';
+ }
+ if ('UTF-8' !== $fromEncoding) {
+ $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+ }
+
+ return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
+ }
+
+ if ('HTML-ENTITIES' === $fromEncoding) {
+ $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
+ $fromEncoding = 'UTF-8';
+ }
+
+ return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+ }
+
+ public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
+ {
+ $ok = true;
+ array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+ if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
+ $ok = false;
+ }
+ });
+
+ return $ok ? $fromEncoding : false;
+ }
+
+ public static function mb_decode_mimeheader($s)
+ {
+ return iconv_mime_decode($s, 2, self::$internalEncoding);
+ }
+
+ public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
+ {
+ trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
+ }
+
+ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
+ {
+ if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
+ trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
+ return false;
+ }
+
+ if (null !== $encoding && !\is_scalar($encoding)) {
+ trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return ''; // Instead of null (cf. mb_encode_numericentity).
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+
+ for ($i = 0; $i < $cnt; $i += 4) {
+ // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
+ $convmap[$i] += $convmap[$i + 2];
+ $convmap[$i + 1] += $convmap[$i + 2];
+ }
+
+ $s = preg_replace_callback('/(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+ $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
+ for ($i = 0; $i < $cnt; $i += 4) {
+ if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
+ return self::mb_chr($c - $convmap[$i + 2]);
+ }
+ }
+
+ return $m[0];
+ }, $s);
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
+ {
+ if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
+ trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
+ return false;
+ }
+
+ if (null !== $encoding && !\is_scalar($encoding)) {
+ trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null; // Instead of '' (cf. mb_decode_numericentity).
+ }
+
+ if (null !== $is_hex && !\is_scalar($is_hex)) {
+ trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+ $i = 0;
+ $len = \strlen($s);
+ $result = '';
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+ $c = self::mb_ord($uchr);
+
+ for ($j = 0; $j < $cnt; $j += 4) {
+ if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
+ $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
+ $result .= $is_hex ? sprintf('%X;', $cOffset) : ''.$cOffset.';';
+ continue 2;
+ }
+ }
+ $result .= $uchr;
+ }
+
+ if (null === $encoding) {
+ return $result;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $result);
+ }
+
+ public static function mb_convert_case($s, $mode, $encoding = null)
+ {
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ if (\MB_CASE_TITLE == $mode) {
+ static $titleRegexp = null;
+ if (null === $titleRegexp) {
+ $titleRegexp = self::getData('titleCaseRegexp');
+ }
+ $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
+ } else {
+ if (\MB_CASE_UPPER == $mode) {
+ static $upper = null;
+ if (null === $upper) {
+ $upper = self::getData('upperCase');
+ }
+ $map = $upper;
+ } else {
+ if (self::MB_CASE_FOLD === $mode) {
+ static $caseFolding = null;
+ if (null === $caseFolding) {
+ $caseFolding = self::getData('caseFolding');
+ }
+ $s = strtr($s, $caseFolding);
+ }
+
+ static $lower = null;
+ if (null === $lower) {
+ $lower = self::getData('lowerCase');
+ }
+ $map = $lower;
+ }
+
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
+
+ $i = 0;
+ $len = \strlen($s);
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+
+ if (isset($map[$uchr])) {
+ $uchr = $map[$uchr];
+ $nlen = \strlen($uchr);
+
+ if ($nlen == $ulen) {
+ $nlen = $i;
+ do {
+ $s[--$nlen] = $uchr[--$ulen];
+ } while ($ulen);
+ } else {
+ $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
+ $len += $nlen - $ulen;
+ $i += $nlen - $ulen;
+ }
+ }
+ }
+ }
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_internal_encoding($encoding = null)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ $normalizedEncoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
+ self::$internalEncoding = $normalizedEncoding;
+
+ return true;
+ }
+
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ }
+
+ public static function mb_language($lang = null)
+ {
+ if (null === $lang) {
+ return self::$language;
+ }
+
+ switch ($normalizedLang = strtolower($lang)) {
+ case 'uni':
+ case 'neutral':
+ self::$language = $normalizedLang;
+
+ return true;
+ }
+
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
+ }
+
+ public static function mb_list_encodings()
+ {
+ return ['UTF-8'];
+ }
+
+ public static function mb_encoding_aliases($encoding)
+ {
+ switch (strtoupper($encoding)) {
+ case 'UTF8':
+ case 'UTF-8':
+ return ['utf8'];
+ }
+
+ return false;
+ }
+
+ public static function mb_check_encoding($var = null, $encoding = null)
+ {
+ if (null === $encoding) {
+ if (null === $var) {
+ return false;
+ }
+ $encoding = self::$internalEncoding;
+ }
+
+ if (!\is_array($var)) {
+ return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
+ }
+
+ foreach ($var as $key => $value) {
+ if (!self::mb_check_encoding($key, $encoding)) {
+ return false;
+ }
+ if (!self::mb_check_encoding($value, $encoding)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
+ {
+ if (null === $encodingList) {
+ $encodingList = self::$encodingList;
+ } else {
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+ }
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ case 'ASCII':
+ if (!preg_match('/[\x80-\xFF]/', $str)) {
+ return $enc;
+ }
+ break;
+
+ case 'UTF8':
+ case 'UTF-8':
+ if (preg_match('//u', $str)) {
+ return 'UTF-8';
+ }
+ break;
+
+ default:
+ if (0 === strncmp($enc, 'ISO-8859-', 9)) {
+ return $enc;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static function mb_detect_order($encodingList = null)
+ {
+ if (null === $encodingList) {
+ return self::$encodingList;
+ }
+
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ default:
+ if (strncmp($enc, 'ISO-8859-', 9)) {
+ return false;
+ }
+ // no break
+ case 'ASCII':
+ case 'UTF8':
+ case 'UTF-8':
+ }
+ }
+
+ self::$encodingList = $encodingList;
+
+ return true;
+ }
+
+ public static function mb_strlen($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return \strlen($s);
+ }
+
+ return @iconv_strlen($s, $encoding);
+ }
+
+ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strpos($haystack, $needle, $offset);
+ }
+
+ $needle = (string) $needle;
+ if ('' === $needle) {
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
+
+ return false;
+ }
+
+ return 0;
+ }
+
+ return iconv_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strrpos($haystack, $needle, $offset);
+ }
+
+ if ($offset != (int) $offset) {
+ $offset = 0;
+ } elseif ($offset = (int) $offset) {
+ if ($offset < 0) {
+ if (0 > $offset += self::mb_strlen($needle)) {
+ $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
+ }
+ $offset = 0;
+ } else {
+ $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
+ }
+ }
+
+ $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
+ ? iconv_strrpos($haystack, $needle, $encoding)
+ : self::mb_strlen($haystack, $encoding);
+
+ return false !== $pos ? $offset + $pos : false;
+ }
+
+ public static function mb_str_split($string, $split_length = 1, $encoding = null)
+ {
+ if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
+ trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
+
+ return null;
+ }
+
+ if (1 > $split_length = (int) $split_length) {
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
+
+ return false;
+ }
+
+ throw new \ValueError('Argument #2 ($length) must be greater than 0');
+ }
+
+ if (null === $encoding) {
+ $encoding = mb_internal_encoding();
+ }
+
+ if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
+ $rx = '/(';
+ while (65535 < $split_length) {
+ $rx .= '.{65535}';
+ $split_length -= 65535;
+ }
+ $rx .= '.{'.$split_length.'})/us';
+
+ return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
+ }
+
+ $result = [];
+ $length = mb_strlen($string, $encoding);
+
+ for ($i = 0; $i < $length; $i += $split_length) {
+ $result[] = mb_substr($string, $i, $split_length, $encoding);
+ }
+
+ return $result;
+ }
+
+ public static function mb_strtolower($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
+ }
+
+ public static function mb_strtoupper($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
+ }
+
+ public static function mb_substitute_character($c = null)
+ {
+ if (null === $c) {
+ return 'none';
+ }
+ if (0 === strcasecmp($c, 'none')) {
+ return true;
+ }
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+ if (\is_int($c) || 'long' === $c || 'entity' === $c) {
+ return false;
+ }
+
+ throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
+ }
+
+ public static function mb_substr($s, $start, $length = null, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return (string) substr($s, $start, null === $length ? 2147483647 : $length);
+ }
+
+ if ($start < 0) {
+ $start = iconv_strlen($s, $encoding) + $start;
+ if ($start < 0) {
+ $start = 0;
+ }
+ }
+
+ if (null === $length) {
+ $length = 2147483647;
+ } elseif ($length < 0) {
+ $length = iconv_strlen($s, $encoding) + $length - $start;
+ if ($length < 0) {
+ return '';
+ }
+ }
+
+ return (string) iconv_substr($s, $start, $length, $encoding);
+ }
+
+ public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
+ self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
+ self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
+ ]);
+
+ return self::mb_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ $pos = strrpos($haystack, $needle);
+ } else {
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = iconv_strrpos($haystack, $needle, $encoding);
+ }
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = self::mb_strripos($haystack, $needle, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
+ $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);
+
+ $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
+ $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);
+
+ return self::mb_strrpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = strpos($haystack, $needle);
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return substr($haystack, 0, $pos);
+ }
+
+ return substr($haystack, $pos);
+ }
+
+ public static function mb_get_info($type = 'all')
+ {
+ $info = [
+ 'internal_encoding' => self::$internalEncoding,
+ 'http_output' => 'pass',
+ 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
+ 'func_overload' => 0,
+ 'func_overload_list' => 'no overload',
+ 'mail_charset' => 'UTF-8',
+ 'mail_header_encoding' => 'BASE64',
+ 'mail_body_encoding' => 'BASE64',
+ 'illegal_chars' => 0,
+ 'encoding_translation' => 'Off',
+ 'language' => self::$language,
+ 'detect_order' => self::$encodingList,
+ 'substitute_character' => 'none',
+ 'strict_detection' => 'Off',
+ ];
+
+ if ('all' === $type) {
+ return $info;
+ }
+ if (isset($info[$type])) {
+ return $info[$type];
+ }
+
+ return false;
+ }
+
+ public static function mb_http_input($type = '')
+ {
+ return false;
+ }
+
+ public static function mb_http_output($encoding = null)
+ {
+ return null !== $encoding ? 'pass' === $encoding : 'pass';
+ }
+
+ public static function mb_strwidth($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' !== $encoding) {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
+
+ return ($wide << 1) + iconv_strlen($s, 'UTF-8');
+ }
+
+ public static function mb_substr_count($haystack, $needle, $encoding = null)
+ {
+ return substr_count($haystack, $needle);
+ }
+
+ public static function mb_output_handler($contents, $status)
+ {
+ return $contents;
+ }
+
+ public static function mb_chr($code, $encoding = null)
+ {
+ if (0x80 > $code %= 0x200000) {
+ $s = \chr($code);
+ } elseif (0x800 > $code) {
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+ } elseif (0x10000 > $code) {
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ } else {
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ }
+
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+ }
+
+ return $s;
+ }
+
+ public static function mb_ord($s, $encoding = null)
+ {
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+ }
+
+ if (1 === \strlen($s)) {
+ return \ord($s);
+ }
+
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+ if (0xF0 <= $code) {
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+ }
+ if (0xE0 <= $code) {
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+ }
+ if (0xC0 <= $code) {
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
+ }
+
+ return $code;
+ }
+
+ public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
+ {
+ if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
+ throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
+ }
+
+ if (null === $encoding) {
+ $encoding = self::mb_internal_encoding();
+ } else {
+ self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
+ }
+
+ if (self::mb_strlen($pad_string, $encoding) <= 0) {
+ throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
+ }
+
+ $paddingRequired = $length - self::mb_strlen($string, $encoding);
+
+ if ($paddingRequired < 1) {
+ return $string;
+ }
+
+ switch ($pad_type) {
+ case \STR_PAD_LEFT:
+ return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
+ case \STR_PAD_RIGHT:
+ return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
+ default:
+ $leftPaddingLength = floor($paddingRequired / 2);
+ $rightPaddingLength = $paddingRequired - $leftPaddingLength;
+
+ return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
+ }
+ }
+
+ public static function mb_ucfirst(string $string, ?string $encoding = null): string
+ {
+ if (null === $encoding) {
+ $encoding = self::mb_internal_encoding();
+ } else {
+ self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
+ }
+
+ $firstChar = mb_substr($string, 0, 1, $encoding);
+ $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);
+
+ return $firstChar.mb_substr($string, 1, null, $encoding);
+ }
+
+ public static function mb_lcfirst(string $string, ?string $encoding = null): string
+ {
+ if (null === $encoding) {
+ $encoding = self::mb_internal_encoding();
+ } else {
+ self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
+ }
+
+ $firstChar = mb_substr($string, 0, 1, $encoding);
+ $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);
+
+ return $firstChar.mb_substr($string, 1, null, $encoding);
+ }
+
+ private static function getSubpart($pos, $part, $haystack, $encoding)
+ {
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return self::mb_substr($haystack, 0, $pos, $encoding);
+ }
+
+ return self::mb_substr($haystack, $pos, null, $encoding);
+ }
+
+ private static function html_encoding_callback(array $m)
+ {
+ $i = 1;
+ $entities = '';
+ $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
+
+ while (isset($m[$i])) {
+ if (0x80 > $m[$i]) {
+ $entities .= \chr($m[$i++]);
+ continue;
+ }
+ if (0xF0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } elseif (0xE0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } else {
+ $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
+ }
+
+ $entities .= ''.$c.';';
+ }
+
+ return $entities;
+ }
+
+ private static function title_case(array $s)
+ {
+ return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
+ }
+
+ private static function getData($file)
+ {
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+ return require $file;
+ }
+
+ return false;
+ }
+
+ private static function getEncoding($encoding)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ if ('UTF-8' === $encoding) {
+ return 'UTF-8';
+ }
+
+ $encoding = strtoupper($encoding);
+
+ if ('8BIT' === $encoding || 'BINARY' === $encoding) {
+ return 'CP850';
+ }
+
+ if ('UTF8' === $encoding) {
+ return 'UTF-8';
+ }
+
+ return $encoding;
+ }
+
+ public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
+ {
+ return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
+ }
+
+ public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
+ {
+ return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
+ }
+
+ public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
+ {
+ return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
+ }
+
+ private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
+ {
+ if (null === $encoding) {
+ $encoding = self::mb_internal_encoding();
+ } else {
+ self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
+ }
+
+ if ('' === $characters) {
+ return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
+ }
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $string)) {
+ $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
+ }
+ if (null !== $characters && !preg_match('//u', $characters)) {
+ $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
+ }
+ } else {
+ $string = iconv($encoding, 'UTF-8//IGNORE', $string);
+
+ if (null !== $characters) {
+ $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
+ }
+ }
+
+ if (null === $characters) {
+ $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}";
+ } else {
+ $characters = preg_quote($characters);
+ }
+
+ $string = preg_replace(sprintf($regex, $characters), '', $string);
+
+ if (null === $encoding) {
+ return $string;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $string);
+ }
+
+ private static function assertEncoding(string $encoding, string $errorFormat): void
+ {
+ try {
+ $validEncoding = @self::mb_check_encoding('', $encoding);
+ } catch (\ValueError $e) {
+ throw new \ValueError(sprintf($errorFormat, $encoding));
+ }
+
+ // BC for PHP 7.3 and lower
+ if (!$validEncoding) {
+ throw new \ValueError(sprintf($errorFormat, $encoding));
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/README.md b/www/libs/vendor/symfony/polyfill-mbstring/README.md
new file mode 100644
index 00000000..478b40da
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/README.md
@@ -0,0 +1,13 @@
+Symfony Polyfill / Mbstring
+===========================
+
+This component provides a partial, native PHP implementation for the
+[Mbstring](https://php.net/mbstring) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php
new file mode 100644
index 00000000..512bba0b
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php
@@ -0,0 +1,119 @@
+ 'i̇',
+ 'µ' => 'μ',
+ 'ſ' => 's',
+ 'ͅ' => 'ι',
+ 'ς' => 'σ',
+ 'ϐ' => 'β',
+ 'ϑ' => 'θ',
+ 'ϕ' => 'φ',
+ 'ϖ' => 'π',
+ 'ϰ' => 'κ',
+ 'ϱ' => 'ρ',
+ 'ϵ' => 'ε',
+ 'ẛ' => 'ṡ',
+ 'ι' => 'ι',
+ 'ß' => 'ss',
+ 'ʼn' => 'ʼn',
+ 'ǰ' => 'ǰ',
+ 'ΐ' => 'ΐ',
+ 'ΰ' => 'ΰ',
+ 'և' => 'եւ',
+ 'ẖ' => 'ẖ',
+ 'ẗ' => 'ẗ',
+ 'ẘ' => 'ẘ',
+ 'ẙ' => 'ẙ',
+ 'ẚ' => 'aʾ',
+ 'ẞ' => 'ss',
+ 'ὐ' => 'ὐ',
+ 'ὒ' => 'ὒ',
+ 'ὔ' => 'ὔ',
+ 'ὖ' => 'ὖ',
+ 'ᾀ' => 'ἀι',
+ 'ᾁ' => 'ἁι',
+ 'ᾂ' => 'ἂι',
+ 'ᾃ' => 'ἃι',
+ 'ᾄ' => 'ἄι',
+ 'ᾅ' => 'ἅι',
+ 'ᾆ' => 'ἆι',
+ 'ᾇ' => 'ἇι',
+ 'ᾈ' => 'ἀι',
+ 'ᾉ' => 'ἁι',
+ 'ᾊ' => 'ἂι',
+ 'ᾋ' => 'ἃι',
+ 'ᾌ' => 'ἄι',
+ 'ᾍ' => 'ἅι',
+ 'ᾎ' => 'ἆι',
+ 'ᾏ' => 'ἇι',
+ 'ᾐ' => 'ἠι',
+ 'ᾑ' => 'ἡι',
+ 'ᾒ' => 'ἢι',
+ 'ᾓ' => 'ἣι',
+ 'ᾔ' => 'ἤι',
+ 'ᾕ' => 'ἥι',
+ 'ᾖ' => 'ἦι',
+ 'ᾗ' => 'ἧι',
+ 'ᾘ' => 'ἠι',
+ 'ᾙ' => 'ἡι',
+ 'ᾚ' => 'ἢι',
+ 'ᾛ' => 'ἣι',
+ 'ᾜ' => 'ἤι',
+ 'ᾝ' => 'ἥι',
+ 'ᾞ' => 'ἦι',
+ 'ᾟ' => 'ἧι',
+ 'ᾠ' => 'ὠι',
+ 'ᾡ' => 'ὡι',
+ 'ᾢ' => 'ὢι',
+ 'ᾣ' => 'ὣι',
+ 'ᾤ' => 'ὤι',
+ 'ᾥ' => 'ὥι',
+ 'ᾦ' => 'ὦι',
+ 'ᾧ' => 'ὧι',
+ 'ᾨ' => 'ὠι',
+ 'ᾩ' => 'ὡι',
+ 'ᾪ' => 'ὢι',
+ 'ᾫ' => 'ὣι',
+ 'ᾬ' => 'ὤι',
+ 'ᾭ' => 'ὥι',
+ 'ᾮ' => 'ὦι',
+ 'ᾯ' => 'ὧι',
+ 'ᾲ' => 'ὰι',
+ 'ᾳ' => 'αι',
+ 'ᾴ' => 'άι',
+ 'ᾶ' => 'ᾶ',
+ 'ᾷ' => 'ᾶι',
+ 'ᾼ' => 'αι',
+ 'ῂ' => 'ὴι',
+ 'ῃ' => 'ηι',
+ 'ῄ' => 'ήι',
+ 'ῆ' => 'ῆ',
+ 'ῇ' => 'ῆι',
+ 'ῌ' => 'ηι',
+ 'ῒ' => 'ῒ',
+ 'ῖ' => 'ῖ',
+ 'ῗ' => 'ῗ',
+ 'ῢ' => 'ῢ',
+ 'ῤ' => 'ῤ',
+ 'ῦ' => 'ῦ',
+ 'ῧ' => 'ῧ',
+ 'ῲ' => 'ὼι',
+ 'ῳ' => 'ωι',
+ 'ῴ' => 'ώι',
+ 'ῶ' => 'ῶ',
+ 'ῷ' => 'ῶι',
+ 'ῼ' => 'ωι',
+ 'ff' => 'ff',
+ 'fi' => 'fi',
+ 'fl' => 'fl',
+ 'ffi' => 'ffi',
+ 'ffl' => 'ffl',
+ 'ſt' => 'st',
+ 'st' => 'st',
+ 'ﬓ' => 'մն',
+ 'ﬔ' => 'մե',
+ 'ﬕ' => 'մի',
+ 'ﬖ' => 'վն',
+ 'ﬗ' => 'մխ',
+];
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
new file mode 100644
index 00000000..fac60b08
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
@@ -0,0 +1,1397 @@
+ 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ 'À' => 'à',
+ 'Á' => 'á',
+ 'Â' => 'â',
+ 'Ã' => 'ã',
+ 'Ä' => 'ä',
+ 'Å' => 'å',
+ 'Æ' => 'æ',
+ 'Ç' => 'ç',
+ 'È' => 'è',
+ 'É' => 'é',
+ 'Ê' => 'ê',
+ 'Ë' => 'ë',
+ 'Ì' => 'ì',
+ 'Í' => 'í',
+ 'Î' => 'î',
+ 'Ï' => 'ï',
+ 'Ð' => 'ð',
+ 'Ñ' => 'ñ',
+ 'Ò' => 'ò',
+ 'Ó' => 'ó',
+ 'Ô' => 'ô',
+ 'Õ' => 'õ',
+ 'Ö' => 'ö',
+ 'Ø' => 'ø',
+ 'Ù' => 'ù',
+ 'Ú' => 'ú',
+ 'Û' => 'û',
+ 'Ü' => 'ü',
+ 'Ý' => 'ý',
+ 'Þ' => 'þ',
+ 'Ā' => 'ā',
+ 'Ă' => 'ă',
+ 'Ą' => 'ą',
+ 'Ć' => 'ć',
+ 'Ĉ' => 'ĉ',
+ 'Ċ' => 'ċ',
+ 'Č' => 'č',
+ 'Ď' => 'ď',
+ 'Đ' => 'đ',
+ 'Ē' => 'ē',
+ 'Ĕ' => 'ĕ',
+ 'Ė' => 'ė',
+ 'Ę' => 'ę',
+ 'Ě' => 'ě',
+ 'Ĝ' => 'ĝ',
+ 'Ğ' => 'ğ',
+ 'Ġ' => 'ġ',
+ 'Ģ' => 'ģ',
+ 'Ĥ' => 'ĥ',
+ 'Ħ' => 'ħ',
+ 'Ĩ' => 'ĩ',
+ 'Ī' => 'ī',
+ 'Ĭ' => 'ĭ',
+ 'Į' => 'į',
+ 'İ' => 'i̇',
+ 'IJ' => 'ij',
+ 'Ĵ' => 'ĵ',
+ 'Ķ' => 'ķ',
+ 'Ĺ' => 'ĺ',
+ 'Ļ' => 'ļ',
+ 'Ľ' => 'ľ',
+ 'Ŀ' => 'ŀ',
+ 'Ł' => 'ł',
+ 'Ń' => 'ń',
+ 'Ņ' => 'ņ',
+ 'Ň' => 'ň',
+ 'Ŋ' => 'ŋ',
+ 'Ō' => 'ō',
+ 'Ŏ' => 'ŏ',
+ 'Ő' => 'ő',
+ 'Œ' => 'œ',
+ 'Ŕ' => 'ŕ',
+ 'Ŗ' => 'ŗ',
+ 'Ř' => 'ř',
+ 'Ś' => 'ś',
+ 'Ŝ' => 'ŝ',
+ 'Ş' => 'ş',
+ 'Š' => 'š',
+ 'Ţ' => 'ţ',
+ 'Ť' => 'ť',
+ 'Ŧ' => 'ŧ',
+ 'Ũ' => 'ũ',
+ 'Ū' => 'ū',
+ 'Ŭ' => 'ŭ',
+ 'Ů' => 'ů',
+ 'Ű' => 'ű',
+ 'Ų' => 'ų',
+ 'Ŵ' => 'ŵ',
+ 'Ŷ' => 'ŷ',
+ 'Ÿ' => 'ÿ',
+ 'Ź' => 'ź',
+ 'Ż' => 'ż',
+ 'Ž' => 'ž',
+ 'Ɓ' => 'ɓ',
+ 'Ƃ' => 'ƃ',
+ 'Ƅ' => 'ƅ',
+ 'Ɔ' => 'ɔ',
+ 'Ƈ' => 'ƈ',
+ 'Ɖ' => 'ɖ',
+ 'Ɗ' => 'ɗ',
+ 'Ƌ' => 'ƌ',
+ 'Ǝ' => 'ǝ',
+ 'Ə' => 'ə',
+ 'Ɛ' => 'ɛ',
+ 'Ƒ' => 'ƒ',
+ 'Ɠ' => 'ɠ',
+ 'Ɣ' => 'ɣ',
+ 'Ɩ' => 'ɩ',
+ 'Ɨ' => 'ɨ',
+ 'Ƙ' => 'ƙ',
+ 'Ɯ' => 'ɯ',
+ 'Ɲ' => 'ɲ',
+ 'Ɵ' => 'ɵ',
+ 'Ơ' => 'ơ',
+ 'Ƣ' => 'ƣ',
+ 'Ƥ' => 'ƥ',
+ 'Ʀ' => 'ʀ',
+ 'Ƨ' => 'ƨ',
+ 'Ʃ' => 'ʃ',
+ 'Ƭ' => 'ƭ',
+ 'Ʈ' => 'ʈ',
+ 'Ư' => 'ư',
+ 'Ʊ' => 'ʊ',
+ 'Ʋ' => 'ʋ',
+ 'Ƴ' => 'ƴ',
+ 'Ƶ' => 'ƶ',
+ 'Ʒ' => 'ʒ',
+ 'Ƹ' => 'ƹ',
+ 'Ƽ' => 'ƽ',
+ 'DŽ' => 'dž',
+ 'Dž' => 'dž',
+ 'LJ' => 'lj',
+ 'Lj' => 'lj',
+ 'NJ' => 'nj',
+ 'Nj' => 'nj',
+ 'Ǎ' => 'ǎ',
+ 'Ǐ' => 'ǐ',
+ 'Ǒ' => 'ǒ',
+ 'Ǔ' => 'ǔ',
+ 'Ǖ' => 'ǖ',
+ 'Ǘ' => 'ǘ',
+ 'Ǚ' => 'ǚ',
+ 'Ǜ' => 'ǜ',
+ 'Ǟ' => 'ǟ',
+ 'Ǡ' => 'ǡ',
+ 'Ǣ' => 'ǣ',
+ 'Ǥ' => 'ǥ',
+ 'Ǧ' => 'ǧ',
+ 'Ǩ' => 'ǩ',
+ 'Ǫ' => 'ǫ',
+ 'Ǭ' => 'ǭ',
+ 'Ǯ' => 'ǯ',
+ 'DZ' => 'dz',
+ 'Dz' => 'dz',
+ 'Ǵ' => 'ǵ',
+ 'Ƕ' => 'ƕ',
+ 'Ƿ' => 'ƿ',
+ 'Ǹ' => 'ǹ',
+ 'Ǻ' => 'ǻ',
+ 'Ǽ' => 'ǽ',
+ 'Ǿ' => 'ǿ',
+ 'Ȁ' => 'ȁ',
+ 'Ȃ' => 'ȃ',
+ 'Ȅ' => 'ȅ',
+ 'Ȇ' => 'ȇ',
+ 'Ȉ' => 'ȉ',
+ 'Ȋ' => 'ȋ',
+ 'Ȍ' => 'ȍ',
+ 'Ȏ' => 'ȏ',
+ 'Ȑ' => 'ȑ',
+ 'Ȓ' => 'ȓ',
+ 'Ȕ' => 'ȕ',
+ 'Ȗ' => 'ȗ',
+ 'Ș' => 'ș',
+ 'Ț' => 'ț',
+ 'Ȝ' => 'ȝ',
+ 'Ȟ' => 'ȟ',
+ 'Ƞ' => 'ƞ',
+ 'Ȣ' => 'ȣ',
+ 'Ȥ' => 'ȥ',
+ 'Ȧ' => 'ȧ',
+ 'Ȩ' => 'ȩ',
+ 'Ȫ' => 'ȫ',
+ 'Ȭ' => 'ȭ',
+ 'Ȯ' => 'ȯ',
+ 'Ȱ' => 'ȱ',
+ 'Ȳ' => 'ȳ',
+ 'Ⱥ' => 'ⱥ',
+ 'Ȼ' => 'ȼ',
+ 'Ƚ' => 'ƚ',
+ 'Ⱦ' => 'ⱦ',
+ 'Ɂ' => 'ɂ',
+ 'Ƀ' => 'ƀ',
+ 'Ʉ' => 'ʉ',
+ 'Ʌ' => 'ʌ',
+ 'Ɇ' => 'ɇ',
+ 'Ɉ' => 'ɉ',
+ 'Ɋ' => 'ɋ',
+ 'Ɍ' => 'ɍ',
+ 'Ɏ' => 'ɏ',
+ 'Ͱ' => 'ͱ',
+ 'Ͳ' => 'ͳ',
+ 'Ͷ' => 'ͷ',
+ 'Ϳ' => 'ϳ',
+ 'Ά' => 'ά',
+ 'Έ' => 'έ',
+ 'Ή' => 'ή',
+ 'Ί' => 'ί',
+ 'Ό' => 'ό',
+ 'Ύ' => 'ύ',
+ 'Ώ' => 'ώ',
+ 'Α' => 'α',
+ 'Β' => 'β',
+ 'Γ' => 'γ',
+ 'Δ' => 'δ',
+ 'Ε' => 'ε',
+ 'Ζ' => 'ζ',
+ 'Η' => 'η',
+ 'Θ' => 'θ',
+ 'Ι' => 'ι',
+ 'Κ' => 'κ',
+ 'Λ' => 'λ',
+ 'Μ' => 'μ',
+ 'Ν' => 'ν',
+ 'Ξ' => 'ξ',
+ 'Ο' => 'ο',
+ 'Π' => 'π',
+ 'Ρ' => 'ρ',
+ 'Σ' => 'σ',
+ 'Τ' => 'τ',
+ 'Υ' => 'υ',
+ 'Φ' => 'φ',
+ 'Χ' => 'χ',
+ 'Ψ' => 'ψ',
+ 'Ω' => 'ω',
+ 'Ϊ' => 'ϊ',
+ 'Ϋ' => 'ϋ',
+ 'Ϗ' => 'ϗ',
+ 'Ϙ' => 'ϙ',
+ 'Ϛ' => 'ϛ',
+ 'Ϝ' => 'ϝ',
+ 'Ϟ' => 'ϟ',
+ 'Ϡ' => 'ϡ',
+ 'Ϣ' => 'ϣ',
+ 'Ϥ' => 'ϥ',
+ 'Ϧ' => 'ϧ',
+ 'Ϩ' => 'ϩ',
+ 'Ϫ' => 'ϫ',
+ 'Ϭ' => 'ϭ',
+ 'Ϯ' => 'ϯ',
+ 'ϴ' => 'θ',
+ 'Ϸ' => 'ϸ',
+ 'Ϲ' => 'ϲ',
+ 'Ϻ' => 'ϻ',
+ 'Ͻ' => 'ͻ',
+ 'Ͼ' => 'ͼ',
+ 'Ͽ' => 'ͽ',
+ 'Ѐ' => 'ѐ',
+ 'Ё' => 'ё',
+ 'Ђ' => 'ђ',
+ 'Ѓ' => 'ѓ',
+ 'Є' => 'є',
+ 'Ѕ' => 'ѕ',
+ 'І' => 'і',
+ 'Ї' => 'ї',
+ 'Ј' => 'ј',
+ 'Љ' => 'љ',
+ 'Њ' => 'њ',
+ 'Ћ' => 'ћ',
+ 'Ќ' => 'ќ',
+ 'Ѝ' => 'ѝ',
+ 'Ў' => 'ў',
+ 'Џ' => 'џ',
+ 'А' => 'а',
+ 'Б' => 'б',
+ 'В' => 'в',
+ 'Г' => 'г',
+ 'Д' => 'д',
+ 'Е' => 'е',
+ 'Ж' => 'ж',
+ 'З' => 'з',
+ 'И' => 'и',
+ 'Й' => 'й',
+ 'К' => 'к',
+ 'Л' => 'л',
+ 'М' => 'м',
+ 'Н' => 'н',
+ 'О' => 'о',
+ 'П' => 'п',
+ 'Р' => 'р',
+ 'С' => 'с',
+ 'Т' => 'т',
+ 'У' => 'у',
+ 'Ф' => 'ф',
+ 'Х' => 'х',
+ 'Ц' => 'ц',
+ 'Ч' => 'ч',
+ 'Ш' => 'ш',
+ 'Щ' => 'щ',
+ 'Ъ' => 'ъ',
+ 'Ы' => 'ы',
+ 'Ь' => 'ь',
+ 'Э' => 'э',
+ 'Ю' => 'ю',
+ 'Я' => 'я',
+ 'Ѡ' => 'ѡ',
+ 'Ѣ' => 'ѣ',
+ 'Ѥ' => 'ѥ',
+ 'Ѧ' => 'ѧ',
+ 'Ѩ' => 'ѩ',
+ 'Ѫ' => 'ѫ',
+ 'Ѭ' => 'ѭ',
+ 'Ѯ' => 'ѯ',
+ 'Ѱ' => 'ѱ',
+ 'Ѳ' => 'ѳ',
+ 'Ѵ' => 'ѵ',
+ 'Ѷ' => 'ѷ',
+ 'Ѹ' => 'ѹ',
+ 'Ѻ' => 'ѻ',
+ 'Ѽ' => 'ѽ',
+ 'Ѿ' => 'ѿ',
+ 'Ҁ' => 'ҁ',
+ 'Ҋ' => 'ҋ',
+ 'Ҍ' => 'ҍ',
+ 'Ҏ' => 'ҏ',
+ 'Ґ' => 'ґ',
+ 'Ғ' => 'ғ',
+ 'Ҕ' => 'ҕ',
+ 'Җ' => 'җ',
+ 'Ҙ' => 'ҙ',
+ 'Қ' => 'қ',
+ 'Ҝ' => 'ҝ',
+ 'Ҟ' => 'ҟ',
+ 'Ҡ' => 'ҡ',
+ 'Ң' => 'ң',
+ 'Ҥ' => 'ҥ',
+ 'Ҧ' => 'ҧ',
+ 'Ҩ' => 'ҩ',
+ 'Ҫ' => 'ҫ',
+ 'Ҭ' => 'ҭ',
+ 'Ү' => 'ү',
+ 'Ұ' => 'ұ',
+ 'Ҳ' => 'ҳ',
+ 'Ҵ' => 'ҵ',
+ 'Ҷ' => 'ҷ',
+ 'Ҹ' => 'ҹ',
+ 'Һ' => 'һ',
+ 'Ҽ' => 'ҽ',
+ 'Ҿ' => 'ҿ',
+ 'Ӏ' => 'ӏ',
+ 'Ӂ' => 'ӂ',
+ 'Ӄ' => 'ӄ',
+ 'Ӆ' => 'ӆ',
+ 'Ӈ' => 'ӈ',
+ 'Ӊ' => 'ӊ',
+ 'Ӌ' => 'ӌ',
+ 'Ӎ' => 'ӎ',
+ 'Ӑ' => 'ӑ',
+ 'Ӓ' => 'ӓ',
+ 'Ӕ' => 'ӕ',
+ 'Ӗ' => 'ӗ',
+ 'Ә' => 'ә',
+ 'Ӛ' => 'ӛ',
+ 'Ӝ' => 'ӝ',
+ 'Ӟ' => 'ӟ',
+ 'Ӡ' => 'ӡ',
+ 'Ӣ' => 'ӣ',
+ 'Ӥ' => 'ӥ',
+ 'Ӧ' => 'ӧ',
+ 'Ө' => 'ө',
+ 'Ӫ' => 'ӫ',
+ 'Ӭ' => 'ӭ',
+ 'Ӯ' => 'ӯ',
+ 'Ӱ' => 'ӱ',
+ 'Ӳ' => 'ӳ',
+ 'Ӵ' => 'ӵ',
+ 'Ӷ' => 'ӷ',
+ 'Ӹ' => 'ӹ',
+ 'Ӻ' => 'ӻ',
+ 'Ӽ' => 'ӽ',
+ 'Ӿ' => 'ӿ',
+ 'Ԁ' => 'ԁ',
+ 'Ԃ' => 'ԃ',
+ 'Ԅ' => 'ԅ',
+ 'Ԇ' => 'ԇ',
+ 'Ԉ' => 'ԉ',
+ 'Ԋ' => 'ԋ',
+ 'Ԍ' => 'ԍ',
+ 'Ԏ' => 'ԏ',
+ 'Ԑ' => 'ԑ',
+ 'Ԓ' => 'ԓ',
+ 'Ԕ' => 'ԕ',
+ 'Ԗ' => 'ԗ',
+ 'Ԙ' => 'ԙ',
+ 'Ԛ' => 'ԛ',
+ 'Ԝ' => 'ԝ',
+ 'Ԟ' => 'ԟ',
+ 'Ԡ' => 'ԡ',
+ 'Ԣ' => 'ԣ',
+ 'Ԥ' => 'ԥ',
+ 'Ԧ' => 'ԧ',
+ 'Ԩ' => 'ԩ',
+ 'Ԫ' => 'ԫ',
+ 'Ԭ' => 'ԭ',
+ 'Ԯ' => 'ԯ',
+ 'Ա' => 'ա',
+ 'Բ' => 'բ',
+ 'Գ' => 'գ',
+ 'Դ' => 'դ',
+ 'Ե' => 'ե',
+ 'Զ' => 'զ',
+ 'Է' => 'է',
+ 'Ը' => 'ը',
+ 'Թ' => 'թ',
+ 'Ժ' => 'ժ',
+ 'Ի' => 'ի',
+ 'Լ' => 'լ',
+ 'Խ' => 'խ',
+ 'Ծ' => 'ծ',
+ 'Կ' => 'կ',
+ 'Հ' => 'հ',
+ 'Ձ' => 'ձ',
+ 'Ղ' => 'ղ',
+ 'Ճ' => 'ճ',
+ 'Մ' => 'մ',
+ 'Յ' => 'յ',
+ 'Ն' => 'ն',
+ 'Շ' => 'շ',
+ 'Ո' => 'ո',
+ 'Չ' => 'չ',
+ 'Պ' => 'պ',
+ 'Ջ' => 'ջ',
+ 'Ռ' => 'ռ',
+ 'Ս' => 'ս',
+ 'Վ' => 'վ',
+ 'Տ' => 'տ',
+ 'Ր' => 'ր',
+ 'Ց' => 'ց',
+ 'Ւ' => 'ւ',
+ 'Փ' => 'փ',
+ 'Ք' => 'ք',
+ 'Օ' => 'օ',
+ 'Ֆ' => 'ֆ',
+ 'Ⴀ' => 'ⴀ',
+ 'Ⴁ' => 'ⴁ',
+ 'Ⴂ' => 'ⴂ',
+ 'Ⴃ' => 'ⴃ',
+ 'Ⴄ' => 'ⴄ',
+ 'Ⴅ' => 'ⴅ',
+ 'Ⴆ' => 'ⴆ',
+ 'Ⴇ' => 'ⴇ',
+ 'Ⴈ' => 'ⴈ',
+ 'Ⴉ' => 'ⴉ',
+ 'Ⴊ' => 'ⴊ',
+ 'Ⴋ' => 'ⴋ',
+ 'Ⴌ' => 'ⴌ',
+ 'Ⴍ' => 'ⴍ',
+ 'Ⴎ' => 'ⴎ',
+ 'Ⴏ' => 'ⴏ',
+ 'Ⴐ' => 'ⴐ',
+ 'Ⴑ' => 'ⴑ',
+ 'Ⴒ' => 'ⴒ',
+ 'Ⴓ' => 'ⴓ',
+ 'Ⴔ' => 'ⴔ',
+ 'Ⴕ' => 'ⴕ',
+ 'Ⴖ' => 'ⴖ',
+ 'Ⴗ' => 'ⴗ',
+ 'Ⴘ' => 'ⴘ',
+ 'Ⴙ' => 'ⴙ',
+ 'Ⴚ' => 'ⴚ',
+ 'Ⴛ' => 'ⴛ',
+ 'Ⴜ' => 'ⴜ',
+ 'Ⴝ' => 'ⴝ',
+ 'Ⴞ' => 'ⴞ',
+ 'Ⴟ' => 'ⴟ',
+ 'Ⴠ' => 'ⴠ',
+ 'Ⴡ' => 'ⴡ',
+ 'Ⴢ' => 'ⴢ',
+ 'Ⴣ' => 'ⴣ',
+ 'Ⴤ' => 'ⴤ',
+ 'Ⴥ' => 'ⴥ',
+ 'Ⴧ' => 'ⴧ',
+ 'Ⴭ' => 'ⴭ',
+ 'Ꭰ' => 'ꭰ',
+ 'Ꭱ' => 'ꭱ',
+ 'Ꭲ' => 'ꭲ',
+ 'Ꭳ' => 'ꭳ',
+ 'Ꭴ' => 'ꭴ',
+ 'Ꭵ' => 'ꭵ',
+ 'Ꭶ' => 'ꭶ',
+ 'Ꭷ' => 'ꭷ',
+ 'Ꭸ' => 'ꭸ',
+ 'Ꭹ' => 'ꭹ',
+ 'Ꭺ' => 'ꭺ',
+ 'Ꭻ' => 'ꭻ',
+ 'Ꭼ' => 'ꭼ',
+ 'Ꭽ' => 'ꭽ',
+ 'Ꭾ' => 'ꭾ',
+ 'Ꭿ' => 'ꭿ',
+ 'Ꮀ' => 'ꮀ',
+ 'Ꮁ' => 'ꮁ',
+ 'Ꮂ' => 'ꮂ',
+ 'Ꮃ' => 'ꮃ',
+ 'Ꮄ' => 'ꮄ',
+ 'Ꮅ' => 'ꮅ',
+ 'Ꮆ' => 'ꮆ',
+ 'Ꮇ' => 'ꮇ',
+ 'Ꮈ' => 'ꮈ',
+ 'Ꮉ' => 'ꮉ',
+ 'Ꮊ' => 'ꮊ',
+ 'Ꮋ' => 'ꮋ',
+ 'Ꮌ' => 'ꮌ',
+ 'Ꮍ' => 'ꮍ',
+ 'Ꮎ' => 'ꮎ',
+ 'Ꮏ' => 'ꮏ',
+ 'Ꮐ' => 'ꮐ',
+ 'Ꮑ' => 'ꮑ',
+ 'Ꮒ' => 'ꮒ',
+ 'Ꮓ' => 'ꮓ',
+ 'Ꮔ' => 'ꮔ',
+ 'Ꮕ' => 'ꮕ',
+ 'Ꮖ' => 'ꮖ',
+ 'Ꮗ' => 'ꮗ',
+ 'Ꮘ' => 'ꮘ',
+ 'Ꮙ' => 'ꮙ',
+ 'Ꮚ' => 'ꮚ',
+ 'Ꮛ' => 'ꮛ',
+ 'Ꮜ' => 'ꮜ',
+ 'Ꮝ' => 'ꮝ',
+ 'Ꮞ' => 'ꮞ',
+ 'Ꮟ' => 'ꮟ',
+ 'Ꮠ' => 'ꮠ',
+ 'Ꮡ' => 'ꮡ',
+ 'Ꮢ' => 'ꮢ',
+ 'Ꮣ' => 'ꮣ',
+ 'Ꮤ' => 'ꮤ',
+ 'Ꮥ' => 'ꮥ',
+ 'Ꮦ' => 'ꮦ',
+ 'Ꮧ' => 'ꮧ',
+ 'Ꮨ' => 'ꮨ',
+ 'Ꮩ' => 'ꮩ',
+ 'Ꮪ' => 'ꮪ',
+ 'Ꮫ' => 'ꮫ',
+ 'Ꮬ' => 'ꮬ',
+ 'Ꮭ' => 'ꮭ',
+ 'Ꮮ' => 'ꮮ',
+ 'Ꮯ' => 'ꮯ',
+ 'Ꮰ' => 'ꮰ',
+ 'Ꮱ' => 'ꮱ',
+ 'Ꮲ' => 'ꮲ',
+ 'Ꮳ' => 'ꮳ',
+ 'Ꮴ' => 'ꮴ',
+ 'Ꮵ' => 'ꮵ',
+ 'Ꮶ' => 'ꮶ',
+ 'Ꮷ' => 'ꮷ',
+ 'Ꮸ' => 'ꮸ',
+ 'Ꮹ' => 'ꮹ',
+ 'Ꮺ' => 'ꮺ',
+ 'Ꮻ' => 'ꮻ',
+ 'Ꮼ' => 'ꮼ',
+ 'Ꮽ' => 'ꮽ',
+ 'Ꮾ' => 'ꮾ',
+ 'Ꮿ' => 'ꮿ',
+ 'Ᏸ' => 'ᏸ',
+ 'Ᏹ' => 'ᏹ',
+ 'Ᏺ' => 'ᏺ',
+ 'Ᏻ' => 'ᏻ',
+ 'Ᏼ' => 'ᏼ',
+ 'Ᏽ' => 'ᏽ',
+ 'Ა' => 'ა',
+ 'Ბ' => 'ბ',
+ 'Გ' => 'გ',
+ 'Დ' => 'დ',
+ 'Ე' => 'ე',
+ 'Ვ' => 'ვ',
+ 'Ზ' => 'ზ',
+ 'Თ' => 'თ',
+ 'Ი' => 'ი',
+ 'Კ' => 'კ',
+ 'Ლ' => 'ლ',
+ 'Მ' => 'მ',
+ 'Ნ' => 'ნ',
+ 'Ო' => 'ო',
+ 'Პ' => 'პ',
+ 'Ჟ' => 'ჟ',
+ 'Რ' => 'რ',
+ 'Ს' => 'ს',
+ 'Ტ' => 'ტ',
+ 'Უ' => 'უ',
+ 'Ფ' => 'ფ',
+ 'Ქ' => 'ქ',
+ 'Ღ' => 'ღ',
+ 'Ყ' => 'ყ',
+ 'Შ' => 'შ',
+ 'Ჩ' => 'ჩ',
+ 'Ც' => 'ც',
+ 'Ძ' => 'ძ',
+ 'Წ' => 'წ',
+ 'Ჭ' => 'ჭ',
+ 'Ხ' => 'ხ',
+ 'Ჯ' => 'ჯ',
+ 'Ჰ' => 'ჰ',
+ 'Ჱ' => 'ჱ',
+ 'Ჲ' => 'ჲ',
+ 'Ჳ' => 'ჳ',
+ 'Ჴ' => 'ჴ',
+ 'Ჵ' => 'ჵ',
+ 'Ჶ' => 'ჶ',
+ 'Ჷ' => 'ჷ',
+ 'Ჸ' => 'ჸ',
+ 'Ჹ' => 'ჹ',
+ 'Ჺ' => 'ჺ',
+ 'Ჽ' => 'ჽ',
+ 'Ჾ' => 'ჾ',
+ 'Ჿ' => 'ჿ',
+ 'Ḁ' => 'ḁ',
+ 'Ḃ' => 'ḃ',
+ 'Ḅ' => 'ḅ',
+ 'Ḇ' => 'ḇ',
+ 'Ḉ' => 'ḉ',
+ 'Ḋ' => 'ḋ',
+ 'Ḍ' => 'ḍ',
+ 'Ḏ' => 'ḏ',
+ 'Ḑ' => 'ḑ',
+ 'Ḓ' => 'ḓ',
+ 'Ḕ' => 'ḕ',
+ 'Ḗ' => 'ḗ',
+ 'Ḙ' => 'ḙ',
+ 'Ḛ' => 'ḛ',
+ 'Ḝ' => 'ḝ',
+ 'Ḟ' => 'ḟ',
+ 'Ḡ' => 'ḡ',
+ 'Ḣ' => 'ḣ',
+ 'Ḥ' => 'ḥ',
+ 'Ḧ' => 'ḧ',
+ 'Ḩ' => 'ḩ',
+ 'Ḫ' => 'ḫ',
+ 'Ḭ' => 'ḭ',
+ 'Ḯ' => 'ḯ',
+ 'Ḱ' => 'ḱ',
+ 'Ḳ' => 'ḳ',
+ 'Ḵ' => 'ḵ',
+ 'Ḷ' => 'ḷ',
+ 'Ḹ' => 'ḹ',
+ 'Ḻ' => 'ḻ',
+ 'Ḽ' => 'ḽ',
+ 'Ḿ' => 'ḿ',
+ 'Ṁ' => 'ṁ',
+ 'Ṃ' => 'ṃ',
+ 'Ṅ' => 'ṅ',
+ 'Ṇ' => 'ṇ',
+ 'Ṉ' => 'ṉ',
+ 'Ṋ' => 'ṋ',
+ 'Ṍ' => 'ṍ',
+ 'Ṏ' => 'ṏ',
+ 'Ṑ' => 'ṑ',
+ 'Ṓ' => 'ṓ',
+ 'Ṕ' => 'ṕ',
+ 'Ṗ' => 'ṗ',
+ 'Ṙ' => 'ṙ',
+ 'Ṛ' => 'ṛ',
+ 'Ṝ' => 'ṝ',
+ 'Ṟ' => 'ṟ',
+ 'Ṡ' => 'ṡ',
+ 'Ṣ' => 'ṣ',
+ 'Ṥ' => 'ṥ',
+ 'Ṧ' => 'ṧ',
+ 'Ṩ' => 'ṩ',
+ 'Ṫ' => 'ṫ',
+ 'Ṭ' => 'ṭ',
+ 'Ṯ' => 'ṯ',
+ 'Ṱ' => 'ṱ',
+ 'Ṳ' => 'ṳ',
+ 'Ṵ' => 'ṵ',
+ 'Ṷ' => 'ṷ',
+ 'Ṹ' => 'ṹ',
+ 'Ṻ' => 'ṻ',
+ 'Ṽ' => 'ṽ',
+ 'Ṿ' => 'ṿ',
+ 'Ẁ' => 'ẁ',
+ 'Ẃ' => 'ẃ',
+ 'Ẅ' => 'ẅ',
+ 'Ẇ' => 'ẇ',
+ 'Ẉ' => 'ẉ',
+ 'Ẋ' => 'ẋ',
+ 'Ẍ' => 'ẍ',
+ 'Ẏ' => 'ẏ',
+ 'Ẑ' => 'ẑ',
+ 'Ẓ' => 'ẓ',
+ 'Ẕ' => 'ẕ',
+ 'ẞ' => 'ß',
+ 'Ạ' => 'ạ',
+ 'Ả' => 'ả',
+ 'Ấ' => 'ấ',
+ 'Ầ' => 'ầ',
+ 'Ẩ' => 'ẩ',
+ 'Ẫ' => 'ẫ',
+ 'Ậ' => 'ậ',
+ 'Ắ' => 'ắ',
+ 'Ằ' => 'ằ',
+ 'Ẳ' => 'ẳ',
+ 'Ẵ' => 'ẵ',
+ 'Ặ' => 'ặ',
+ 'Ẹ' => 'ẹ',
+ 'Ẻ' => 'ẻ',
+ 'Ẽ' => 'ẽ',
+ 'Ế' => 'ế',
+ 'Ề' => 'ề',
+ 'Ể' => 'ể',
+ 'Ễ' => 'ễ',
+ 'Ệ' => 'ệ',
+ 'Ỉ' => 'ỉ',
+ 'Ị' => 'ị',
+ 'Ọ' => 'ọ',
+ 'Ỏ' => 'ỏ',
+ 'Ố' => 'ố',
+ 'Ồ' => 'ồ',
+ 'Ổ' => 'ổ',
+ 'Ỗ' => 'ỗ',
+ 'Ộ' => 'ộ',
+ 'Ớ' => 'ớ',
+ 'Ờ' => 'ờ',
+ 'Ở' => 'ở',
+ 'Ỡ' => 'ỡ',
+ 'Ợ' => 'ợ',
+ 'Ụ' => 'ụ',
+ 'Ủ' => 'ủ',
+ 'Ứ' => 'ứ',
+ 'Ừ' => 'ừ',
+ 'Ử' => 'ử',
+ 'Ữ' => 'ữ',
+ 'Ự' => 'ự',
+ 'Ỳ' => 'ỳ',
+ 'Ỵ' => 'ỵ',
+ 'Ỷ' => 'ỷ',
+ 'Ỹ' => 'ỹ',
+ 'Ỻ' => 'ỻ',
+ 'Ỽ' => 'ỽ',
+ 'Ỿ' => 'ỿ',
+ 'Ἀ' => 'ἀ',
+ 'Ἁ' => 'ἁ',
+ 'Ἂ' => 'ἂ',
+ 'Ἃ' => 'ἃ',
+ 'Ἄ' => 'ἄ',
+ 'Ἅ' => 'ἅ',
+ 'Ἆ' => 'ἆ',
+ 'Ἇ' => 'ἇ',
+ 'Ἐ' => 'ἐ',
+ 'Ἑ' => 'ἑ',
+ 'Ἒ' => 'ἒ',
+ 'Ἓ' => 'ἓ',
+ 'Ἔ' => 'ἔ',
+ 'Ἕ' => 'ἕ',
+ 'Ἠ' => 'ἠ',
+ 'Ἡ' => 'ἡ',
+ 'Ἢ' => 'ἢ',
+ 'Ἣ' => 'ἣ',
+ 'Ἤ' => 'ἤ',
+ 'Ἥ' => 'ἥ',
+ 'Ἦ' => 'ἦ',
+ 'Ἧ' => 'ἧ',
+ 'Ἰ' => 'ἰ',
+ 'Ἱ' => 'ἱ',
+ 'Ἲ' => 'ἲ',
+ 'Ἳ' => 'ἳ',
+ 'Ἴ' => 'ἴ',
+ 'Ἵ' => 'ἵ',
+ 'Ἶ' => 'ἶ',
+ 'Ἷ' => 'ἷ',
+ 'Ὀ' => 'ὀ',
+ 'Ὁ' => 'ὁ',
+ 'Ὂ' => 'ὂ',
+ 'Ὃ' => 'ὃ',
+ 'Ὄ' => 'ὄ',
+ 'Ὅ' => 'ὅ',
+ 'Ὑ' => 'ὑ',
+ 'Ὓ' => 'ὓ',
+ 'Ὕ' => 'ὕ',
+ 'Ὗ' => 'ὗ',
+ 'Ὠ' => 'ὠ',
+ 'Ὡ' => 'ὡ',
+ 'Ὢ' => 'ὢ',
+ 'Ὣ' => 'ὣ',
+ 'Ὤ' => 'ὤ',
+ 'Ὥ' => 'ὥ',
+ 'Ὦ' => 'ὦ',
+ 'Ὧ' => 'ὧ',
+ 'ᾈ' => 'ᾀ',
+ 'ᾉ' => 'ᾁ',
+ 'ᾊ' => 'ᾂ',
+ 'ᾋ' => 'ᾃ',
+ 'ᾌ' => 'ᾄ',
+ 'ᾍ' => 'ᾅ',
+ 'ᾎ' => 'ᾆ',
+ 'ᾏ' => 'ᾇ',
+ 'ᾘ' => 'ᾐ',
+ 'ᾙ' => 'ᾑ',
+ 'ᾚ' => 'ᾒ',
+ 'ᾛ' => 'ᾓ',
+ 'ᾜ' => 'ᾔ',
+ 'ᾝ' => 'ᾕ',
+ 'ᾞ' => 'ᾖ',
+ 'ᾟ' => 'ᾗ',
+ 'ᾨ' => 'ᾠ',
+ 'ᾩ' => 'ᾡ',
+ 'ᾪ' => 'ᾢ',
+ 'ᾫ' => 'ᾣ',
+ 'ᾬ' => 'ᾤ',
+ 'ᾭ' => 'ᾥ',
+ 'ᾮ' => 'ᾦ',
+ 'ᾯ' => 'ᾧ',
+ 'Ᾰ' => 'ᾰ',
+ 'Ᾱ' => 'ᾱ',
+ 'Ὰ' => 'ὰ',
+ 'Ά' => 'ά',
+ 'ᾼ' => 'ᾳ',
+ 'Ὲ' => 'ὲ',
+ 'Έ' => 'έ',
+ 'Ὴ' => 'ὴ',
+ 'Ή' => 'ή',
+ 'ῌ' => 'ῃ',
+ 'Ῐ' => 'ῐ',
+ 'Ῑ' => 'ῑ',
+ 'Ὶ' => 'ὶ',
+ 'Ί' => 'ί',
+ 'Ῠ' => 'ῠ',
+ 'Ῡ' => 'ῡ',
+ 'Ὺ' => 'ὺ',
+ 'Ύ' => 'ύ',
+ 'Ῥ' => 'ῥ',
+ 'Ὸ' => 'ὸ',
+ 'Ό' => 'ό',
+ 'Ὼ' => 'ὼ',
+ 'Ώ' => 'ώ',
+ 'ῼ' => 'ῳ',
+ 'Ω' => 'ω',
+ 'K' => 'k',
+ 'Å' => 'å',
+ 'Ⅎ' => 'ⅎ',
+ 'Ⅰ' => 'ⅰ',
+ 'Ⅱ' => 'ⅱ',
+ 'Ⅲ' => 'ⅲ',
+ 'Ⅳ' => 'ⅳ',
+ 'Ⅴ' => 'ⅴ',
+ 'Ⅵ' => 'ⅵ',
+ 'Ⅶ' => 'ⅶ',
+ 'Ⅷ' => 'ⅷ',
+ 'Ⅸ' => 'ⅸ',
+ 'Ⅹ' => 'ⅹ',
+ 'Ⅺ' => 'ⅺ',
+ 'Ⅻ' => 'ⅻ',
+ 'Ⅼ' => 'ⅼ',
+ 'Ⅽ' => 'ⅽ',
+ 'Ⅾ' => 'ⅾ',
+ 'Ⅿ' => 'ⅿ',
+ 'Ↄ' => 'ↄ',
+ 'Ⓐ' => 'ⓐ',
+ 'Ⓑ' => 'ⓑ',
+ 'Ⓒ' => 'ⓒ',
+ 'Ⓓ' => 'ⓓ',
+ 'Ⓔ' => 'ⓔ',
+ 'Ⓕ' => 'ⓕ',
+ 'Ⓖ' => 'ⓖ',
+ 'Ⓗ' => 'ⓗ',
+ 'Ⓘ' => 'ⓘ',
+ 'Ⓙ' => 'ⓙ',
+ 'Ⓚ' => 'ⓚ',
+ 'Ⓛ' => 'ⓛ',
+ 'Ⓜ' => 'ⓜ',
+ 'Ⓝ' => 'ⓝ',
+ 'Ⓞ' => 'ⓞ',
+ 'Ⓟ' => 'ⓟ',
+ 'Ⓠ' => 'ⓠ',
+ 'Ⓡ' => 'ⓡ',
+ 'Ⓢ' => 'ⓢ',
+ 'Ⓣ' => 'ⓣ',
+ 'Ⓤ' => 'ⓤ',
+ 'Ⓥ' => 'ⓥ',
+ 'Ⓦ' => 'ⓦ',
+ 'Ⓧ' => 'ⓧ',
+ 'Ⓨ' => 'ⓨ',
+ 'Ⓩ' => 'ⓩ',
+ 'Ⰰ' => 'ⰰ',
+ 'Ⰱ' => 'ⰱ',
+ 'Ⰲ' => 'ⰲ',
+ 'Ⰳ' => 'ⰳ',
+ 'Ⰴ' => 'ⰴ',
+ 'Ⰵ' => 'ⰵ',
+ 'Ⰶ' => 'ⰶ',
+ 'Ⰷ' => 'ⰷ',
+ 'Ⰸ' => 'ⰸ',
+ 'Ⰹ' => 'ⰹ',
+ 'Ⰺ' => 'ⰺ',
+ 'Ⰻ' => 'ⰻ',
+ 'Ⰼ' => 'ⰼ',
+ 'Ⰽ' => 'ⰽ',
+ 'Ⰾ' => 'ⰾ',
+ 'Ⰿ' => 'ⰿ',
+ 'Ⱀ' => 'ⱀ',
+ 'Ⱁ' => 'ⱁ',
+ 'Ⱂ' => 'ⱂ',
+ 'Ⱃ' => 'ⱃ',
+ 'Ⱄ' => 'ⱄ',
+ 'Ⱅ' => 'ⱅ',
+ 'Ⱆ' => 'ⱆ',
+ 'Ⱇ' => 'ⱇ',
+ 'Ⱈ' => 'ⱈ',
+ 'Ⱉ' => 'ⱉ',
+ 'Ⱊ' => 'ⱊ',
+ 'Ⱋ' => 'ⱋ',
+ 'Ⱌ' => 'ⱌ',
+ 'Ⱍ' => 'ⱍ',
+ 'Ⱎ' => 'ⱎ',
+ 'Ⱏ' => 'ⱏ',
+ 'Ⱐ' => 'ⱐ',
+ 'Ⱑ' => 'ⱑ',
+ 'Ⱒ' => 'ⱒ',
+ 'Ⱓ' => 'ⱓ',
+ 'Ⱔ' => 'ⱔ',
+ 'Ⱕ' => 'ⱕ',
+ 'Ⱖ' => 'ⱖ',
+ 'Ⱗ' => 'ⱗ',
+ 'Ⱘ' => 'ⱘ',
+ 'Ⱙ' => 'ⱙ',
+ 'Ⱚ' => 'ⱚ',
+ 'Ⱛ' => 'ⱛ',
+ 'Ⱜ' => 'ⱜ',
+ 'Ⱝ' => 'ⱝ',
+ 'Ⱞ' => 'ⱞ',
+ 'Ⱡ' => 'ⱡ',
+ 'Ɫ' => 'ɫ',
+ 'Ᵽ' => 'ᵽ',
+ 'Ɽ' => 'ɽ',
+ 'Ⱨ' => 'ⱨ',
+ 'Ⱪ' => 'ⱪ',
+ 'Ⱬ' => 'ⱬ',
+ 'Ɑ' => 'ɑ',
+ 'Ɱ' => 'ɱ',
+ 'Ɐ' => 'ɐ',
+ 'Ɒ' => 'ɒ',
+ 'Ⱳ' => 'ⱳ',
+ 'Ⱶ' => 'ⱶ',
+ 'Ȿ' => 'ȿ',
+ 'Ɀ' => 'ɀ',
+ 'Ⲁ' => 'ⲁ',
+ 'Ⲃ' => 'ⲃ',
+ 'Ⲅ' => 'ⲅ',
+ 'Ⲇ' => 'ⲇ',
+ 'Ⲉ' => 'ⲉ',
+ 'Ⲋ' => 'ⲋ',
+ 'Ⲍ' => 'ⲍ',
+ 'Ⲏ' => 'ⲏ',
+ 'Ⲑ' => 'ⲑ',
+ 'Ⲓ' => 'ⲓ',
+ 'Ⲕ' => 'ⲕ',
+ 'Ⲗ' => 'ⲗ',
+ 'Ⲙ' => 'ⲙ',
+ 'Ⲛ' => 'ⲛ',
+ 'Ⲝ' => 'ⲝ',
+ 'Ⲟ' => 'ⲟ',
+ 'Ⲡ' => 'ⲡ',
+ 'Ⲣ' => 'ⲣ',
+ 'Ⲥ' => 'ⲥ',
+ 'Ⲧ' => 'ⲧ',
+ 'Ⲩ' => 'ⲩ',
+ 'Ⲫ' => 'ⲫ',
+ 'Ⲭ' => 'ⲭ',
+ 'Ⲯ' => 'ⲯ',
+ 'Ⲱ' => 'ⲱ',
+ 'Ⲳ' => 'ⲳ',
+ 'Ⲵ' => 'ⲵ',
+ 'Ⲷ' => 'ⲷ',
+ 'Ⲹ' => 'ⲹ',
+ 'Ⲻ' => 'ⲻ',
+ 'Ⲽ' => 'ⲽ',
+ 'Ⲿ' => 'ⲿ',
+ 'Ⳁ' => 'ⳁ',
+ 'Ⳃ' => 'ⳃ',
+ 'Ⳅ' => 'ⳅ',
+ 'Ⳇ' => 'ⳇ',
+ 'Ⳉ' => 'ⳉ',
+ 'Ⳋ' => 'ⳋ',
+ 'Ⳍ' => 'ⳍ',
+ 'Ⳏ' => 'ⳏ',
+ 'Ⳑ' => 'ⳑ',
+ 'Ⳓ' => 'ⳓ',
+ 'Ⳕ' => 'ⳕ',
+ 'Ⳗ' => 'ⳗ',
+ 'Ⳙ' => 'ⳙ',
+ 'Ⳛ' => 'ⳛ',
+ 'Ⳝ' => 'ⳝ',
+ 'Ⳟ' => 'ⳟ',
+ 'Ⳡ' => 'ⳡ',
+ 'Ⳣ' => 'ⳣ',
+ 'Ⳬ' => 'ⳬ',
+ 'Ⳮ' => 'ⳮ',
+ 'Ⳳ' => 'ⳳ',
+ 'Ꙁ' => 'ꙁ',
+ 'Ꙃ' => 'ꙃ',
+ 'Ꙅ' => 'ꙅ',
+ 'Ꙇ' => 'ꙇ',
+ 'Ꙉ' => 'ꙉ',
+ 'Ꙋ' => 'ꙋ',
+ 'Ꙍ' => 'ꙍ',
+ 'Ꙏ' => 'ꙏ',
+ 'Ꙑ' => 'ꙑ',
+ 'Ꙓ' => 'ꙓ',
+ 'Ꙕ' => 'ꙕ',
+ 'Ꙗ' => 'ꙗ',
+ 'Ꙙ' => 'ꙙ',
+ 'Ꙛ' => 'ꙛ',
+ 'Ꙝ' => 'ꙝ',
+ 'Ꙟ' => 'ꙟ',
+ 'Ꙡ' => 'ꙡ',
+ 'Ꙣ' => 'ꙣ',
+ 'Ꙥ' => 'ꙥ',
+ 'Ꙧ' => 'ꙧ',
+ 'Ꙩ' => 'ꙩ',
+ 'Ꙫ' => 'ꙫ',
+ 'Ꙭ' => 'ꙭ',
+ 'Ꚁ' => 'ꚁ',
+ 'Ꚃ' => 'ꚃ',
+ 'Ꚅ' => 'ꚅ',
+ 'Ꚇ' => 'ꚇ',
+ 'Ꚉ' => 'ꚉ',
+ 'Ꚋ' => 'ꚋ',
+ 'Ꚍ' => 'ꚍ',
+ 'Ꚏ' => 'ꚏ',
+ 'Ꚑ' => 'ꚑ',
+ 'Ꚓ' => 'ꚓ',
+ 'Ꚕ' => 'ꚕ',
+ 'Ꚗ' => 'ꚗ',
+ 'Ꚙ' => 'ꚙ',
+ 'Ꚛ' => 'ꚛ',
+ 'Ꜣ' => 'ꜣ',
+ 'Ꜥ' => 'ꜥ',
+ 'Ꜧ' => 'ꜧ',
+ 'Ꜩ' => 'ꜩ',
+ 'Ꜫ' => 'ꜫ',
+ 'Ꜭ' => 'ꜭ',
+ 'Ꜯ' => 'ꜯ',
+ 'Ꜳ' => 'ꜳ',
+ 'Ꜵ' => 'ꜵ',
+ 'Ꜷ' => 'ꜷ',
+ 'Ꜹ' => 'ꜹ',
+ 'Ꜻ' => 'ꜻ',
+ 'Ꜽ' => 'ꜽ',
+ 'Ꜿ' => 'ꜿ',
+ 'Ꝁ' => 'ꝁ',
+ 'Ꝃ' => 'ꝃ',
+ 'Ꝅ' => 'ꝅ',
+ 'Ꝇ' => 'ꝇ',
+ 'Ꝉ' => 'ꝉ',
+ 'Ꝋ' => 'ꝋ',
+ 'Ꝍ' => 'ꝍ',
+ 'Ꝏ' => 'ꝏ',
+ 'Ꝑ' => 'ꝑ',
+ 'Ꝓ' => 'ꝓ',
+ 'Ꝕ' => 'ꝕ',
+ 'Ꝗ' => 'ꝗ',
+ 'Ꝙ' => 'ꝙ',
+ 'Ꝛ' => 'ꝛ',
+ 'Ꝝ' => 'ꝝ',
+ 'Ꝟ' => 'ꝟ',
+ 'Ꝡ' => 'ꝡ',
+ 'Ꝣ' => 'ꝣ',
+ 'Ꝥ' => 'ꝥ',
+ 'Ꝧ' => 'ꝧ',
+ 'Ꝩ' => 'ꝩ',
+ 'Ꝫ' => 'ꝫ',
+ 'Ꝭ' => 'ꝭ',
+ 'Ꝯ' => 'ꝯ',
+ 'Ꝺ' => 'ꝺ',
+ 'Ꝼ' => 'ꝼ',
+ 'Ᵹ' => 'ᵹ',
+ 'Ꝿ' => 'ꝿ',
+ 'Ꞁ' => 'ꞁ',
+ 'Ꞃ' => 'ꞃ',
+ 'Ꞅ' => 'ꞅ',
+ 'Ꞇ' => 'ꞇ',
+ 'Ꞌ' => 'ꞌ',
+ 'Ɥ' => 'ɥ',
+ 'Ꞑ' => 'ꞑ',
+ 'Ꞓ' => 'ꞓ',
+ 'Ꞗ' => 'ꞗ',
+ 'Ꞙ' => 'ꞙ',
+ 'Ꞛ' => 'ꞛ',
+ 'Ꞝ' => 'ꞝ',
+ 'Ꞟ' => 'ꞟ',
+ 'Ꞡ' => 'ꞡ',
+ 'Ꞣ' => 'ꞣ',
+ 'Ꞥ' => 'ꞥ',
+ 'Ꞧ' => 'ꞧ',
+ 'Ꞩ' => 'ꞩ',
+ 'Ɦ' => 'ɦ',
+ 'Ɜ' => 'ɜ',
+ 'Ɡ' => 'ɡ',
+ 'Ɬ' => 'ɬ',
+ 'Ɪ' => 'ɪ',
+ 'Ʞ' => 'ʞ',
+ 'Ʇ' => 'ʇ',
+ 'Ʝ' => 'ʝ',
+ 'Ꭓ' => 'ꭓ',
+ 'Ꞵ' => 'ꞵ',
+ 'Ꞷ' => 'ꞷ',
+ 'Ꞹ' => 'ꞹ',
+ 'Ꞻ' => 'ꞻ',
+ 'Ꞽ' => 'ꞽ',
+ 'Ꞿ' => 'ꞿ',
+ 'Ꟃ' => 'ꟃ',
+ 'Ꞔ' => 'ꞔ',
+ 'Ʂ' => 'ʂ',
+ 'Ᶎ' => 'ᶎ',
+ 'Ꟈ' => 'ꟈ',
+ 'Ꟊ' => 'ꟊ',
+ 'Ꟶ' => 'ꟶ',
+ 'A' => 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ '𐐀' => '𐐨',
+ '𐐁' => '𐐩',
+ '𐐂' => '𐐪',
+ '𐐃' => '𐐫',
+ '𐐄' => '𐐬',
+ '𐐅' => '𐐭',
+ '𐐆' => '𐐮',
+ '𐐇' => '𐐯',
+ '𐐈' => '𐐰',
+ '𐐉' => '𐐱',
+ '𐐊' => '𐐲',
+ '𐐋' => '𐐳',
+ '𐐌' => '𐐴',
+ '𐐍' => '𐐵',
+ '𐐎' => '𐐶',
+ '𐐏' => '𐐷',
+ '𐐐' => '𐐸',
+ '𐐑' => '𐐹',
+ '𐐒' => '𐐺',
+ '𐐓' => '𐐻',
+ '𐐔' => '𐐼',
+ '𐐕' => '𐐽',
+ '𐐖' => '𐐾',
+ '𐐗' => '𐐿',
+ '𐐘' => '𐑀',
+ '𐐙' => '𐑁',
+ '𐐚' => '𐑂',
+ '𐐛' => '𐑃',
+ '𐐜' => '𐑄',
+ '𐐝' => '𐑅',
+ '𐐞' => '𐑆',
+ '𐐟' => '𐑇',
+ '𐐠' => '𐑈',
+ '𐐡' => '𐑉',
+ '𐐢' => '𐑊',
+ '𐐣' => '𐑋',
+ '𐐤' => '𐑌',
+ '𐐥' => '𐑍',
+ '𐐦' => '𐑎',
+ '𐐧' => '𐑏',
+ '𐒰' => '𐓘',
+ '𐒱' => '𐓙',
+ '𐒲' => '𐓚',
+ '𐒳' => '𐓛',
+ '𐒴' => '𐓜',
+ '𐒵' => '𐓝',
+ '𐒶' => '𐓞',
+ '𐒷' => '𐓟',
+ '𐒸' => '𐓠',
+ '𐒹' => '𐓡',
+ '𐒺' => '𐓢',
+ '𐒻' => '𐓣',
+ '𐒼' => '𐓤',
+ '𐒽' => '𐓥',
+ '𐒾' => '𐓦',
+ '𐒿' => '𐓧',
+ '𐓀' => '𐓨',
+ '𐓁' => '𐓩',
+ '𐓂' => '𐓪',
+ '𐓃' => '𐓫',
+ '𐓄' => '𐓬',
+ '𐓅' => '𐓭',
+ '𐓆' => '𐓮',
+ '𐓇' => '𐓯',
+ '𐓈' => '𐓰',
+ '𐓉' => '𐓱',
+ '𐓊' => '𐓲',
+ '𐓋' => '𐓳',
+ '𐓌' => '𐓴',
+ '𐓍' => '𐓵',
+ '𐓎' => '𐓶',
+ '𐓏' => '𐓷',
+ '𐓐' => '𐓸',
+ '𐓑' => '𐓹',
+ '𐓒' => '𐓺',
+ '𐓓' => '𐓻',
+ '𐲀' => '𐳀',
+ '𐲁' => '𐳁',
+ '𐲂' => '𐳂',
+ '𐲃' => '𐳃',
+ '𐲄' => '𐳄',
+ '𐲅' => '𐳅',
+ '𐲆' => '𐳆',
+ '𐲇' => '𐳇',
+ '𐲈' => '𐳈',
+ '𐲉' => '𐳉',
+ '𐲊' => '𐳊',
+ '𐲋' => '𐳋',
+ '𐲌' => '𐳌',
+ '𐲍' => '𐳍',
+ '𐲎' => '𐳎',
+ '𐲏' => '𐳏',
+ '𐲐' => '𐳐',
+ '𐲑' => '𐳑',
+ '𐲒' => '𐳒',
+ '𐲓' => '𐳓',
+ '𐲔' => '𐳔',
+ '𐲕' => '𐳕',
+ '𐲖' => '𐳖',
+ '𐲗' => '𐳗',
+ '𐲘' => '𐳘',
+ '𐲙' => '𐳙',
+ '𐲚' => '𐳚',
+ '𐲛' => '𐳛',
+ '𐲜' => '𐳜',
+ '𐲝' => '𐳝',
+ '𐲞' => '𐳞',
+ '𐲟' => '𐳟',
+ '𐲠' => '𐳠',
+ '𐲡' => '𐳡',
+ '𐲢' => '𐳢',
+ '𐲣' => '𐳣',
+ '𐲤' => '𐳤',
+ '𐲥' => '𐳥',
+ '𐲦' => '𐳦',
+ '𐲧' => '𐳧',
+ '𐲨' => '𐳨',
+ '𐲩' => '𐳩',
+ '𐲪' => '𐳪',
+ '𐲫' => '𐳫',
+ '𐲬' => '𐳬',
+ '𐲭' => '𐳭',
+ '𐲮' => '𐳮',
+ '𐲯' => '𐳯',
+ '𐲰' => '𐳰',
+ '𐲱' => '𐳱',
+ '𐲲' => '𐳲',
+ '𑢠' => '𑣀',
+ '𑢡' => '𑣁',
+ '𑢢' => '𑣂',
+ '𑢣' => '𑣃',
+ '𑢤' => '𑣄',
+ '𑢥' => '𑣅',
+ '𑢦' => '𑣆',
+ '𑢧' => '𑣇',
+ '𑢨' => '𑣈',
+ '𑢩' => '𑣉',
+ '𑢪' => '𑣊',
+ '𑢫' => '𑣋',
+ '𑢬' => '𑣌',
+ '𑢭' => '𑣍',
+ '𑢮' => '𑣎',
+ '𑢯' => '𑣏',
+ '𑢰' => '𑣐',
+ '𑢱' => '𑣑',
+ '𑢲' => '𑣒',
+ '𑢳' => '𑣓',
+ '𑢴' => '𑣔',
+ '𑢵' => '𑣕',
+ '𑢶' => '𑣖',
+ '𑢷' => '𑣗',
+ '𑢸' => '𑣘',
+ '𑢹' => '𑣙',
+ '𑢺' => '𑣚',
+ '𑢻' => '𑣛',
+ '𑢼' => '𑣜',
+ '𑢽' => '𑣝',
+ '𑢾' => '𑣞',
+ '𑢿' => '𑣟',
+ '𖹀' => '𖹠',
+ '𖹁' => '𖹡',
+ '𖹂' => '𖹢',
+ '𖹃' => '𖹣',
+ '𖹄' => '𖹤',
+ '𖹅' => '𖹥',
+ '𖹆' => '𖹦',
+ '𖹇' => '𖹧',
+ '𖹈' => '𖹨',
+ '𖹉' => '𖹩',
+ '𖹊' => '𖹪',
+ '𖹋' => '𖹫',
+ '𖹌' => '𖹬',
+ '𖹍' => '𖹭',
+ '𖹎' => '𖹮',
+ '𖹏' => '𖹯',
+ '𖹐' => '𖹰',
+ '𖹑' => '𖹱',
+ '𖹒' => '𖹲',
+ '𖹓' => '𖹳',
+ '𖹔' => '𖹴',
+ '𖹕' => '𖹵',
+ '𖹖' => '𖹶',
+ '𖹗' => '𖹷',
+ '𖹘' => '𖹸',
+ '𖹙' => '𖹹',
+ '𖹚' => '𖹺',
+ '𖹛' => '𖹻',
+ '𖹜' => '𖹼',
+ '𖹝' => '𖹽',
+ '𖹞' => '𖹾',
+ '𖹟' => '𖹿',
+ '𞤀' => '𞤢',
+ '𞤁' => '𞤣',
+ '𞤂' => '𞤤',
+ '𞤃' => '𞤥',
+ '𞤄' => '𞤦',
+ '𞤅' => '𞤧',
+ '𞤆' => '𞤨',
+ '𞤇' => '𞤩',
+ '𞤈' => '𞤪',
+ '𞤉' => '𞤫',
+ '𞤊' => '𞤬',
+ '𞤋' => '𞤭',
+ '𞤌' => '𞤮',
+ '𞤍' => '𞤯',
+ '𞤎' => '𞤰',
+ '𞤏' => '𞤱',
+ '𞤐' => '𞤲',
+ '𞤑' => '𞤳',
+ '𞤒' => '𞤴',
+ '𞤓' => '𞤵',
+ '𞤔' => '𞤶',
+ '𞤕' => '𞤷',
+ '𞤖' => '𞤸',
+ '𞤗' => '𞤹',
+ '𞤘' => '𞤺',
+ '𞤙' => '𞤻',
+ '𞤚' => '𞤼',
+ '𞤛' => '𞤽',
+ '𞤜' => '𞤾',
+ '𞤝' => '𞤿',
+ '𞤞' => '𞥀',
+ '𞤟' => '𞥁',
+ '𞤠' => '𞥂',
+ '𞤡' => '𞥃',
+);
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
new file mode 100644
index 00000000..2a8f6e73
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
@@ -0,0 +1,5 @@
+ 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ 'µ' => 'Μ',
+ 'à' => 'À',
+ 'á' => 'Á',
+ 'â' => 'Â',
+ 'ã' => 'Ã',
+ 'ä' => 'Ä',
+ 'å' => 'Å',
+ 'æ' => 'Æ',
+ 'ç' => 'Ç',
+ 'è' => 'È',
+ 'é' => 'É',
+ 'ê' => 'Ê',
+ 'ë' => 'Ë',
+ 'ì' => 'Ì',
+ 'í' => 'Í',
+ 'î' => 'Î',
+ 'ï' => 'Ï',
+ 'ð' => 'Ð',
+ 'ñ' => 'Ñ',
+ 'ò' => 'Ò',
+ 'ó' => 'Ó',
+ 'ô' => 'Ô',
+ 'õ' => 'Õ',
+ 'ö' => 'Ö',
+ 'ø' => 'Ø',
+ 'ù' => 'Ù',
+ 'ú' => 'Ú',
+ 'û' => 'Û',
+ 'ü' => 'Ü',
+ 'ý' => 'Ý',
+ 'þ' => 'Þ',
+ 'ÿ' => 'Ÿ',
+ 'ā' => 'Ā',
+ 'ă' => 'Ă',
+ 'ą' => 'Ą',
+ 'ć' => 'Ć',
+ 'ĉ' => 'Ĉ',
+ 'ċ' => 'Ċ',
+ 'č' => 'Č',
+ 'ď' => 'Ď',
+ 'đ' => 'Đ',
+ 'ē' => 'Ē',
+ 'ĕ' => 'Ĕ',
+ 'ė' => 'Ė',
+ 'ę' => 'Ę',
+ 'ě' => 'Ě',
+ 'ĝ' => 'Ĝ',
+ 'ğ' => 'Ğ',
+ 'ġ' => 'Ġ',
+ 'ģ' => 'Ģ',
+ 'ĥ' => 'Ĥ',
+ 'ħ' => 'Ħ',
+ 'ĩ' => 'Ĩ',
+ 'ī' => 'Ī',
+ 'ĭ' => 'Ĭ',
+ 'į' => 'Į',
+ 'ı' => 'I',
+ 'ij' => 'IJ',
+ 'ĵ' => 'Ĵ',
+ 'ķ' => 'Ķ',
+ 'ĺ' => 'Ĺ',
+ 'ļ' => 'Ļ',
+ 'ľ' => 'Ľ',
+ 'ŀ' => 'Ŀ',
+ 'ł' => 'Ł',
+ 'ń' => 'Ń',
+ 'ņ' => 'Ņ',
+ 'ň' => 'Ň',
+ 'ŋ' => 'Ŋ',
+ 'ō' => 'Ō',
+ 'ŏ' => 'Ŏ',
+ 'ő' => 'Ő',
+ 'œ' => 'Œ',
+ 'ŕ' => 'Ŕ',
+ 'ŗ' => 'Ŗ',
+ 'ř' => 'Ř',
+ 'ś' => 'Ś',
+ 'ŝ' => 'Ŝ',
+ 'ş' => 'Ş',
+ 'š' => 'Š',
+ 'ţ' => 'Ţ',
+ 'ť' => 'Ť',
+ 'ŧ' => 'Ŧ',
+ 'ũ' => 'Ũ',
+ 'ū' => 'Ū',
+ 'ŭ' => 'Ŭ',
+ 'ů' => 'Ů',
+ 'ű' => 'Ű',
+ 'ų' => 'Ų',
+ 'ŵ' => 'Ŵ',
+ 'ŷ' => 'Ŷ',
+ 'ź' => 'Ź',
+ 'ż' => 'Ż',
+ 'ž' => 'Ž',
+ 'ſ' => 'S',
+ 'ƀ' => 'Ƀ',
+ 'ƃ' => 'Ƃ',
+ 'ƅ' => 'Ƅ',
+ 'ƈ' => 'Ƈ',
+ 'ƌ' => 'Ƌ',
+ 'ƒ' => 'Ƒ',
+ 'ƕ' => 'Ƕ',
+ 'ƙ' => 'Ƙ',
+ 'ƚ' => 'Ƚ',
+ 'ƞ' => 'Ƞ',
+ 'ơ' => 'Ơ',
+ 'ƣ' => 'Ƣ',
+ 'ƥ' => 'Ƥ',
+ 'ƨ' => 'Ƨ',
+ 'ƭ' => 'Ƭ',
+ 'ư' => 'Ư',
+ 'ƴ' => 'Ƴ',
+ 'ƶ' => 'Ƶ',
+ 'ƹ' => 'Ƹ',
+ 'ƽ' => 'Ƽ',
+ 'ƿ' => 'Ƿ',
+ 'Dž' => 'DŽ',
+ 'dž' => 'DŽ',
+ 'Lj' => 'LJ',
+ 'lj' => 'LJ',
+ 'Nj' => 'NJ',
+ 'nj' => 'NJ',
+ 'ǎ' => 'Ǎ',
+ 'ǐ' => 'Ǐ',
+ 'ǒ' => 'Ǒ',
+ 'ǔ' => 'Ǔ',
+ 'ǖ' => 'Ǖ',
+ 'ǘ' => 'Ǘ',
+ 'ǚ' => 'Ǚ',
+ 'ǜ' => 'Ǜ',
+ 'ǝ' => 'Ǝ',
+ 'ǟ' => 'Ǟ',
+ 'ǡ' => 'Ǡ',
+ 'ǣ' => 'Ǣ',
+ 'ǥ' => 'Ǥ',
+ 'ǧ' => 'Ǧ',
+ 'ǩ' => 'Ǩ',
+ 'ǫ' => 'Ǫ',
+ 'ǭ' => 'Ǭ',
+ 'ǯ' => 'Ǯ',
+ 'Dz' => 'DZ',
+ 'dz' => 'DZ',
+ 'ǵ' => 'Ǵ',
+ 'ǹ' => 'Ǹ',
+ 'ǻ' => 'Ǻ',
+ 'ǽ' => 'Ǽ',
+ 'ǿ' => 'Ǿ',
+ 'ȁ' => 'Ȁ',
+ 'ȃ' => 'Ȃ',
+ 'ȅ' => 'Ȅ',
+ 'ȇ' => 'Ȇ',
+ 'ȉ' => 'Ȉ',
+ 'ȋ' => 'Ȋ',
+ 'ȍ' => 'Ȍ',
+ 'ȏ' => 'Ȏ',
+ 'ȑ' => 'Ȑ',
+ 'ȓ' => 'Ȓ',
+ 'ȕ' => 'Ȕ',
+ 'ȗ' => 'Ȗ',
+ 'ș' => 'Ș',
+ 'ț' => 'Ț',
+ 'ȝ' => 'Ȝ',
+ 'ȟ' => 'Ȟ',
+ 'ȣ' => 'Ȣ',
+ 'ȥ' => 'Ȥ',
+ 'ȧ' => 'Ȧ',
+ 'ȩ' => 'Ȩ',
+ 'ȫ' => 'Ȫ',
+ 'ȭ' => 'Ȭ',
+ 'ȯ' => 'Ȯ',
+ 'ȱ' => 'Ȱ',
+ 'ȳ' => 'Ȳ',
+ 'ȼ' => 'Ȼ',
+ 'ȿ' => 'Ȿ',
+ 'ɀ' => 'Ɀ',
+ 'ɂ' => 'Ɂ',
+ 'ɇ' => 'Ɇ',
+ 'ɉ' => 'Ɉ',
+ 'ɋ' => 'Ɋ',
+ 'ɍ' => 'Ɍ',
+ 'ɏ' => 'Ɏ',
+ 'ɐ' => 'Ɐ',
+ 'ɑ' => 'Ɑ',
+ 'ɒ' => 'Ɒ',
+ 'ɓ' => 'Ɓ',
+ 'ɔ' => 'Ɔ',
+ 'ɖ' => 'Ɖ',
+ 'ɗ' => 'Ɗ',
+ 'ə' => 'Ə',
+ 'ɛ' => 'Ɛ',
+ 'ɜ' => 'Ɜ',
+ 'ɠ' => 'Ɠ',
+ 'ɡ' => 'Ɡ',
+ 'ɣ' => 'Ɣ',
+ 'ɥ' => 'Ɥ',
+ 'ɦ' => 'Ɦ',
+ 'ɨ' => 'Ɨ',
+ 'ɩ' => 'Ɩ',
+ 'ɪ' => 'Ɪ',
+ 'ɫ' => 'Ɫ',
+ 'ɬ' => 'Ɬ',
+ 'ɯ' => 'Ɯ',
+ 'ɱ' => 'Ɱ',
+ 'ɲ' => 'Ɲ',
+ 'ɵ' => 'Ɵ',
+ 'ɽ' => 'Ɽ',
+ 'ʀ' => 'Ʀ',
+ 'ʂ' => 'Ʂ',
+ 'ʃ' => 'Ʃ',
+ 'ʇ' => 'Ʇ',
+ 'ʈ' => 'Ʈ',
+ 'ʉ' => 'Ʉ',
+ 'ʊ' => 'Ʊ',
+ 'ʋ' => 'Ʋ',
+ 'ʌ' => 'Ʌ',
+ 'ʒ' => 'Ʒ',
+ 'ʝ' => 'Ʝ',
+ 'ʞ' => 'Ʞ',
+ 'ͅ' => 'Ι',
+ 'ͱ' => 'Ͱ',
+ 'ͳ' => 'Ͳ',
+ 'ͷ' => 'Ͷ',
+ 'ͻ' => 'Ͻ',
+ 'ͼ' => 'Ͼ',
+ 'ͽ' => 'Ͽ',
+ 'ά' => 'Ά',
+ 'έ' => 'Έ',
+ 'ή' => 'Ή',
+ 'ί' => 'Ί',
+ 'α' => 'Α',
+ 'β' => 'Β',
+ 'γ' => 'Γ',
+ 'δ' => 'Δ',
+ 'ε' => 'Ε',
+ 'ζ' => 'Ζ',
+ 'η' => 'Η',
+ 'θ' => 'Θ',
+ 'ι' => 'Ι',
+ 'κ' => 'Κ',
+ 'λ' => 'Λ',
+ 'μ' => 'Μ',
+ 'ν' => 'Ν',
+ 'ξ' => 'Ξ',
+ 'ο' => 'Ο',
+ 'π' => 'Π',
+ 'ρ' => 'Ρ',
+ 'ς' => 'Σ',
+ 'σ' => 'Σ',
+ 'τ' => 'Τ',
+ 'υ' => 'Υ',
+ 'φ' => 'Φ',
+ 'χ' => 'Χ',
+ 'ψ' => 'Ψ',
+ 'ω' => 'Ω',
+ 'ϊ' => 'Ϊ',
+ 'ϋ' => 'Ϋ',
+ 'ό' => 'Ό',
+ 'ύ' => 'Ύ',
+ 'ώ' => 'Ώ',
+ 'ϐ' => 'Β',
+ 'ϑ' => 'Θ',
+ 'ϕ' => 'Φ',
+ 'ϖ' => 'Π',
+ 'ϗ' => 'Ϗ',
+ 'ϙ' => 'Ϙ',
+ 'ϛ' => 'Ϛ',
+ 'ϝ' => 'Ϝ',
+ 'ϟ' => 'Ϟ',
+ 'ϡ' => 'Ϡ',
+ 'ϣ' => 'Ϣ',
+ 'ϥ' => 'Ϥ',
+ 'ϧ' => 'Ϧ',
+ 'ϩ' => 'Ϩ',
+ 'ϫ' => 'Ϫ',
+ 'ϭ' => 'Ϭ',
+ 'ϯ' => 'Ϯ',
+ 'ϰ' => 'Κ',
+ 'ϱ' => 'Ρ',
+ 'ϲ' => 'Ϲ',
+ 'ϳ' => 'Ϳ',
+ 'ϵ' => 'Ε',
+ 'ϸ' => 'Ϸ',
+ 'ϻ' => 'Ϻ',
+ 'а' => 'А',
+ 'б' => 'Б',
+ 'в' => 'В',
+ 'г' => 'Г',
+ 'д' => 'Д',
+ 'е' => 'Е',
+ 'ж' => 'Ж',
+ 'з' => 'З',
+ 'и' => 'И',
+ 'й' => 'Й',
+ 'к' => 'К',
+ 'л' => 'Л',
+ 'м' => 'М',
+ 'н' => 'Н',
+ 'о' => 'О',
+ 'п' => 'П',
+ 'р' => 'Р',
+ 'с' => 'С',
+ 'т' => 'Т',
+ 'у' => 'У',
+ 'ф' => 'Ф',
+ 'х' => 'Х',
+ 'ц' => 'Ц',
+ 'ч' => 'Ч',
+ 'ш' => 'Ш',
+ 'щ' => 'Щ',
+ 'ъ' => 'Ъ',
+ 'ы' => 'Ы',
+ 'ь' => 'Ь',
+ 'э' => 'Э',
+ 'ю' => 'Ю',
+ 'я' => 'Я',
+ 'ѐ' => 'Ѐ',
+ 'ё' => 'Ё',
+ 'ђ' => 'Ђ',
+ 'ѓ' => 'Ѓ',
+ 'є' => 'Є',
+ 'ѕ' => 'Ѕ',
+ 'і' => 'І',
+ 'ї' => 'Ї',
+ 'ј' => 'Ј',
+ 'љ' => 'Љ',
+ 'њ' => 'Њ',
+ 'ћ' => 'Ћ',
+ 'ќ' => 'Ќ',
+ 'ѝ' => 'Ѝ',
+ 'ў' => 'Ў',
+ 'џ' => 'Џ',
+ 'ѡ' => 'Ѡ',
+ 'ѣ' => 'Ѣ',
+ 'ѥ' => 'Ѥ',
+ 'ѧ' => 'Ѧ',
+ 'ѩ' => 'Ѩ',
+ 'ѫ' => 'Ѫ',
+ 'ѭ' => 'Ѭ',
+ 'ѯ' => 'Ѯ',
+ 'ѱ' => 'Ѱ',
+ 'ѳ' => 'Ѳ',
+ 'ѵ' => 'Ѵ',
+ 'ѷ' => 'Ѷ',
+ 'ѹ' => 'Ѹ',
+ 'ѻ' => 'Ѻ',
+ 'ѽ' => 'Ѽ',
+ 'ѿ' => 'Ѿ',
+ 'ҁ' => 'Ҁ',
+ 'ҋ' => 'Ҋ',
+ 'ҍ' => 'Ҍ',
+ 'ҏ' => 'Ҏ',
+ 'ґ' => 'Ґ',
+ 'ғ' => 'Ғ',
+ 'ҕ' => 'Ҕ',
+ 'җ' => 'Җ',
+ 'ҙ' => 'Ҙ',
+ 'қ' => 'Қ',
+ 'ҝ' => 'Ҝ',
+ 'ҟ' => 'Ҟ',
+ 'ҡ' => 'Ҡ',
+ 'ң' => 'Ң',
+ 'ҥ' => 'Ҥ',
+ 'ҧ' => 'Ҧ',
+ 'ҩ' => 'Ҩ',
+ 'ҫ' => 'Ҫ',
+ 'ҭ' => 'Ҭ',
+ 'ү' => 'Ү',
+ 'ұ' => 'Ұ',
+ 'ҳ' => 'Ҳ',
+ 'ҵ' => 'Ҵ',
+ 'ҷ' => 'Ҷ',
+ 'ҹ' => 'Ҹ',
+ 'һ' => 'Һ',
+ 'ҽ' => 'Ҽ',
+ 'ҿ' => 'Ҿ',
+ 'ӂ' => 'Ӂ',
+ 'ӄ' => 'Ӄ',
+ 'ӆ' => 'Ӆ',
+ 'ӈ' => 'Ӈ',
+ 'ӊ' => 'Ӊ',
+ 'ӌ' => 'Ӌ',
+ 'ӎ' => 'Ӎ',
+ 'ӏ' => 'Ӏ',
+ 'ӑ' => 'Ӑ',
+ 'ӓ' => 'Ӓ',
+ 'ӕ' => 'Ӕ',
+ 'ӗ' => 'Ӗ',
+ 'ә' => 'Ә',
+ 'ӛ' => 'Ӛ',
+ 'ӝ' => 'Ӝ',
+ 'ӟ' => 'Ӟ',
+ 'ӡ' => 'Ӡ',
+ 'ӣ' => 'Ӣ',
+ 'ӥ' => 'Ӥ',
+ 'ӧ' => 'Ӧ',
+ 'ө' => 'Ө',
+ 'ӫ' => 'Ӫ',
+ 'ӭ' => 'Ӭ',
+ 'ӯ' => 'Ӯ',
+ 'ӱ' => 'Ӱ',
+ 'ӳ' => 'Ӳ',
+ 'ӵ' => 'Ӵ',
+ 'ӷ' => 'Ӷ',
+ 'ӹ' => 'Ӹ',
+ 'ӻ' => 'Ӻ',
+ 'ӽ' => 'Ӽ',
+ 'ӿ' => 'Ӿ',
+ 'ԁ' => 'Ԁ',
+ 'ԃ' => 'Ԃ',
+ 'ԅ' => 'Ԅ',
+ 'ԇ' => 'Ԇ',
+ 'ԉ' => 'Ԉ',
+ 'ԋ' => 'Ԋ',
+ 'ԍ' => 'Ԍ',
+ 'ԏ' => 'Ԏ',
+ 'ԑ' => 'Ԑ',
+ 'ԓ' => 'Ԓ',
+ 'ԕ' => 'Ԕ',
+ 'ԗ' => 'Ԗ',
+ 'ԙ' => 'Ԙ',
+ 'ԛ' => 'Ԛ',
+ 'ԝ' => 'Ԝ',
+ 'ԟ' => 'Ԟ',
+ 'ԡ' => 'Ԡ',
+ 'ԣ' => 'Ԣ',
+ 'ԥ' => 'Ԥ',
+ 'ԧ' => 'Ԧ',
+ 'ԩ' => 'Ԩ',
+ 'ԫ' => 'Ԫ',
+ 'ԭ' => 'Ԭ',
+ 'ԯ' => 'Ԯ',
+ 'ա' => 'Ա',
+ 'բ' => 'Բ',
+ 'գ' => 'Գ',
+ 'դ' => 'Դ',
+ 'ե' => 'Ե',
+ 'զ' => 'Զ',
+ 'է' => 'Է',
+ 'ը' => 'Ը',
+ 'թ' => 'Թ',
+ 'ժ' => 'Ժ',
+ 'ի' => 'Ի',
+ 'լ' => 'Լ',
+ 'խ' => 'Խ',
+ 'ծ' => 'Ծ',
+ 'կ' => 'Կ',
+ 'հ' => 'Հ',
+ 'ձ' => 'Ձ',
+ 'ղ' => 'Ղ',
+ 'ճ' => 'Ճ',
+ 'մ' => 'Մ',
+ 'յ' => 'Յ',
+ 'ն' => 'Ն',
+ 'շ' => 'Շ',
+ 'ո' => 'Ո',
+ 'չ' => 'Չ',
+ 'պ' => 'Պ',
+ 'ջ' => 'Ջ',
+ 'ռ' => 'Ռ',
+ 'ս' => 'Ս',
+ 'վ' => 'Վ',
+ 'տ' => 'Տ',
+ 'ր' => 'Ր',
+ 'ց' => 'Ց',
+ 'ւ' => 'Ւ',
+ 'փ' => 'Փ',
+ 'ք' => 'Ք',
+ 'օ' => 'Օ',
+ 'ֆ' => 'Ֆ',
+ 'ა' => 'Ა',
+ 'ბ' => 'Ბ',
+ 'გ' => 'Გ',
+ 'დ' => 'Დ',
+ 'ე' => 'Ე',
+ 'ვ' => 'Ვ',
+ 'ზ' => 'Ზ',
+ 'თ' => 'Თ',
+ 'ი' => 'Ი',
+ 'კ' => 'Კ',
+ 'ლ' => 'Ლ',
+ 'მ' => 'Მ',
+ 'ნ' => 'Ნ',
+ 'ო' => 'Ო',
+ 'პ' => 'Პ',
+ 'ჟ' => 'Ჟ',
+ 'რ' => 'Რ',
+ 'ს' => 'Ს',
+ 'ტ' => 'Ტ',
+ 'უ' => 'Უ',
+ 'ფ' => 'Ფ',
+ 'ქ' => 'Ქ',
+ 'ღ' => 'Ღ',
+ 'ყ' => 'Ყ',
+ 'შ' => 'Შ',
+ 'ჩ' => 'Ჩ',
+ 'ც' => 'Ც',
+ 'ძ' => 'Ძ',
+ 'წ' => 'Წ',
+ 'ჭ' => 'Ჭ',
+ 'ხ' => 'Ხ',
+ 'ჯ' => 'Ჯ',
+ 'ჰ' => 'Ჰ',
+ 'ჱ' => 'Ჱ',
+ 'ჲ' => 'Ჲ',
+ 'ჳ' => 'Ჳ',
+ 'ჴ' => 'Ჴ',
+ 'ჵ' => 'Ჵ',
+ 'ჶ' => 'Ჶ',
+ 'ჷ' => 'Ჷ',
+ 'ჸ' => 'Ჸ',
+ 'ჹ' => 'Ჹ',
+ 'ჺ' => 'Ჺ',
+ 'ჽ' => 'Ჽ',
+ 'ჾ' => 'Ჾ',
+ 'ჿ' => 'Ჿ',
+ 'ᏸ' => 'Ᏸ',
+ 'ᏹ' => 'Ᏹ',
+ 'ᏺ' => 'Ᏺ',
+ 'ᏻ' => 'Ᏻ',
+ 'ᏼ' => 'Ᏼ',
+ 'ᏽ' => 'Ᏽ',
+ 'ᲀ' => 'В',
+ 'ᲁ' => 'Д',
+ 'ᲂ' => 'О',
+ 'ᲃ' => 'С',
+ 'ᲄ' => 'Т',
+ 'ᲅ' => 'Т',
+ 'ᲆ' => 'Ъ',
+ 'ᲇ' => 'Ѣ',
+ 'ᲈ' => 'Ꙋ',
+ 'ᵹ' => 'Ᵹ',
+ 'ᵽ' => 'Ᵽ',
+ 'ᶎ' => 'Ᶎ',
+ 'ḁ' => 'Ḁ',
+ 'ḃ' => 'Ḃ',
+ 'ḅ' => 'Ḅ',
+ 'ḇ' => 'Ḇ',
+ 'ḉ' => 'Ḉ',
+ 'ḋ' => 'Ḋ',
+ 'ḍ' => 'Ḍ',
+ 'ḏ' => 'Ḏ',
+ 'ḑ' => 'Ḑ',
+ 'ḓ' => 'Ḓ',
+ 'ḕ' => 'Ḕ',
+ 'ḗ' => 'Ḗ',
+ 'ḙ' => 'Ḙ',
+ 'ḛ' => 'Ḛ',
+ 'ḝ' => 'Ḝ',
+ 'ḟ' => 'Ḟ',
+ 'ḡ' => 'Ḡ',
+ 'ḣ' => 'Ḣ',
+ 'ḥ' => 'Ḥ',
+ 'ḧ' => 'Ḧ',
+ 'ḩ' => 'Ḩ',
+ 'ḫ' => 'Ḫ',
+ 'ḭ' => 'Ḭ',
+ 'ḯ' => 'Ḯ',
+ 'ḱ' => 'Ḱ',
+ 'ḳ' => 'Ḳ',
+ 'ḵ' => 'Ḵ',
+ 'ḷ' => 'Ḷ',
+ 'ḹ' => 'Ḹ',
+ 'ḻ' => 'Ḻ',
+ 'ḽ' => 'Ḽ',
+ 'ḿ' => 'Ḿ',
+ 'ṁ' => 'Ṁ',
+ 'ṃ' => 'Ṃ',
+ 'ṅ' => 'Ṅ',
+ 'ṇ' => 'Ṇ',
+ 'ṉ' => 'Ṉ',
+ 'ṋ' => 'Ṋ',
+ 'ṍ' => 'Ṍ',
+ 'ṏ' => 'Ṏ',
+ 'ṑ' => 'Ṑ',
+ 'ṓ' => 'Ṓ',
+ 'ṕ' => 'Ṕ',
+ 'ṗ' => 'Ṗ',
+ 'ṙ' => 'Ṙ',
+ 'ṛ' => 'Ṛ',
+ 'ṝ' => 'Ṝ',
+ 'ṟ' => 'Ṟ',
+ 'ṡ' => 'Ṡ',
+ 'ṣ' => 'Ṣ',
+ 'ṥ' => 'Ṥ',
+ 'ṧ' => 'Ṧ',
+ 'ṩ' => 'Ṩ',
+ 'ṫ' => 'Ṫ',
+ 'ṭ' => 'Ṭ',
+ 'ṯ' => 'Ṯ',
+ 'ṱ' => 'Ṱ',
+ 'ṳ' => 'Ṳ',
+ 'ṵ' => 'Ṵ',
+ 'ṷ' => 'Ṷ',
+ 'ṹ' => 'Ṹ',
+ 'ṻ' => 'Ṻ',
+ 'ṽ' => 'Ṽ',
+ 'ṿ' => 'Ṿ',
+ 'ẁ' => 'Ẁ',
+ 'ẃ' => 'Ẃ',
+ 'ẅ' => 'Ẅ',
+ 'ẇ' => 'Ẇ',
+ 'ẉ' => 'Ẉ',
+ 'ẋ' => 'Ẋ',
+ 'ẍ' => 'Ẍ',
+ 'ẏ' => 'Ẏ',
+ 'ẑ' => 'Ẑ',
+ 'ẓ' => 'Ẓ',
+ 'ẕ' => 'Ẕ',
+ 'ẛ' => 'Ṡ',
+ 'ạ' => 'Ạ',
+ 'ả' => 'Ả',
+ 'ấ' => 'Ấ',
+ 'ầ' => 'Ầ',
+ 'ẩ' => 'Ẩ',
+ 'ẫ' => 'Ẫ',
+ 'ậ' => 'Ậ',
+ 'ắ' => 'Ắ',
+ 'ằ' => 'Ằ',
+ 'ẳ' => 'Ẳ',
+ 'ẵ' => 'Ẵ',
+ 'ặ' => 'Ặ',
+ 'ẹ' => 'Ẹ',
+ 'ẻ' => 'Ẻ',
+ 'ẽ' => 'Ẽ',
+ 'ế' => 'Ế',
+ 'ề' => 'Ề',
+ 'ể' => 'Ể',
+ 'ễ' => 'Ễ',
+ 'ệ' => 'Ệ',
+ 'ỉ' => 'Ỉ',
+ 'ị' => 'Ị',
+ 'ọ' => 'Ọ',
+ 'ỏ' => 'Ỏ',
+ 'ố' => 'Ố',
+ 'ồ' => 'Ồ',
+ 'ổ' => 'Ổ',
+ 'ỗ' => 'Ỗ',
+ 'ộ' => 'Ộ',
+ 'ớ' => 'Ớ',
+ 'ờ' => 'Ờ',
+ 'ở' => 'Ở',
+ 'ỡ' => 'Ỡ',
+ 'ợ' => 'Ợ',
+ 'ụ' => 'Ụ',
+ 'ủ' => 'Ủ',
+ 'ứ' => 'Ứ',
+ 'ừ' => 'Ừ',
+ 'ử' => 'Ử',
+ 'ữ' => 'Ữ',
+ 'ự' => 'Ự',
+ 'ỳ' => 'Ỳ',
+ 'ỵ' => 'Ỵ',
+ 'ỷ' => 'Ỷ',
+ 'ỹ' => 'Ỹ',
+ 'ỻ' => 'Ỻ',
+ 'ỽ' => 'Ỽ',
+ 'ỿ' => 'Ỿ',
+ 'ἀ' => 'Ἀ',
+ 'ἁ' => 'Ἁ',
+ 'ἂ' => 'Ἂ',
+ 'ἃ' => 'Ἃ',
+ 'ἄ' => 'Ἄ',
+ 'ἅ' => 'Ἅ',
+ 'ἆ' => 'Ἆ',
+ 'ἇ' => 'Ἇ',
+ 'ἐ' => 'Ἐ',
+ 'ἑ' => 'Ἑ',
+ 'ἒ' => 'Ἒ',
+ 'ἓ' => 'Ἓ',
+ 'ἔ' => 'Ἔ',
+ 'ἕ' => 'Ἕ',
+ 'ἠ' => 'Ἠ',
+ 'ἡ' => 'Ἡ',
+ 'ἢ' => 'Ἢ',
+ 'ἣ' => 'Ἣ',
+ 'ἤ' => 'Ἤ',
+ 'ἥ' => 'Ἥ',
+ 'ἦ' => 'Ἦ',
+ 'ἧ' => 'Ἧ',
+ 'ἰ' => 'Ἰ',
+ 'ἱ' => 'Ἱ',
+ 'ἲ' => 'Ἲ',
+ 'ἳ' => 'Ἳ',
+ 'ἴ' => 'Ἴ',
+ 'ἵ' => 'Ἵ',
+ 'ἶ' => 'Ἶ',
+ 'ἷ' => 'Ἷ',
+ 'ὀ' => 'Ὀ',
+ 'ὁ' => 'Ὁ',
+ 'ὂ' => 'Ὂ',
+ 'ὃ' => 'Ὃ',
+ 'ὄ' => 'Ὄ',
+ 'ὅ' => 'Ὅ',
+ 'ὑ' => 'Ὑ',
+ 'ὓ' => 'Ὓ',
+ 'ὕ' => 'Ὕ',
+ 'ὗ' => 'Ὗ',
+ 'ὠ' => 'Ὠ',
+ 'ὡ' => 'Ὡ',
+ 'ὢ' => 'Ὢ',
+ 'ὣ' => 'Ὣ',
+ 'ὤ' => 'Ὤ',
+ 'ὥ' => 'Ὥ',
+ 'ὦ' => 'Ὦ',
+ 'ὧ' => 'Ὧ',
+ 'ὰ' => 'Ὰ',
+ 'ά' => 'Ά',
+ 'ὲ' => 'Ὲ',
+ 'έ' => 'Έ',
+ 'ὴ' => 'Ὴ',
+ 'ή' => 'Ή',
+ 'ὶ' => 'Ὶ',
+ 'ί' => 'Ί',
+ 'ὸ' => 'Ὸ',
+ 'ό' => 'Ό',
+ 'ὺ' => 'Ὺ',
+ 'ύ' => 'Ύ',
+ 'ὼ' => 'Ὼ',
+ 'ώ' => 'Ώ',
+ 'ᾀ' => 'ἈΙ',
+ 'ᾁ' => 'ἉΙ',
+ 'ᾂ' => 'ἊΙ',
+ 'ᾃ' => 'ἋΙ',
+ 'ᾄ' => 'ἌΙ',
+ 'ᾅ' => 'ἍΙ',
+ 'ᾆ' => 'ἎΙ',
+ 'ᾇ' => 'ἏΙ',
+ 'ᾐ' => 'ἨΙ',
+ 'ᾑ' => 'ἩΙ',
+ 'ᾒ' => 'ἪΙ',
+ 'ᾓ' => 'ἫΙ',
+ 'ᾔ' => 'ἬΙ',
+ 'ᾕ' => 'ἭΙ',
+ 'ᾖ' => 'ἮΙ',
+ 'ᾗ' => 'ἯΙ',
+ 'ᾠ' => 'ὨΙ',
+ 'ᾡ' => 'ὩΙ',
+ 'ᾢ' => 'ὪΙ',
+ 'ᾣ' => 'ὫΙ',
+ 'ᾤ' => 'ὬΙ',
+ 'ᾥ' => 'ὭΙ',
+ 'ᾦ' => 'ὮΙ',
+ 'ᾧ' => 'ὯΙ',
+ 'ᾰ' => 'Ᾰ',
+ 'ᾱ' => 'Ᾱ',
+ 'ᾳ' => 'ΑΙ',
+ 'ι' => 'Ι',
+ 'ῃ' => 'ΗΙ',
+ 'ῐ' => 'Ῐ',
+ 'ῑ' => 'Ῑ',
+ 'ῠ' => 'Ῠ',
+ 'ῡ' => 'Ῡ',
+ 'ῥ' => 'Ῥ',
+ 'ῳ' => 'ΩΙ',
+ 'ⅎ' => 'Ⅎ',
+ 'ⅰ' => 'Ⅰ',
+ 'ⅱ' => 'Ⅱ',
+ 'ⅲ' => 'Ⅲ',
+ 'ⅳ' => 'Ⅳ',
+ 'ⅴ' => 'Ⅴ',
+ 'ⅵ' => 'Ⅵ',
+ 'ⅶ' => 'Ⅶ',
+ 'ⅷ' => 'Ⅷ',
+ 'ⅸ' => 'Ⅸ',
+ 'ⅹ' => 'Ⅹ',
+ 'ⅺ' => 'Ⅺ',
+ 'ⅻ' => 'Ⅻ',
+ 'ⅼ' => 'Ⅼ',
+ 'ⅽ' => 'Ⅽ',
+ 'ⅾ' => 'Ⅾ',
+ 'ⅿ' => 'Ⅿ',
+ 'ↄ' => 'Ↄ',
+ 'ⓐ' => 'Ⓐ',
+ 'ⓑ' => 'Ⓑ',
+ 'ⓒ' => 'Ⓒ',
+ 'ⓓ' => 'Ⓓ',
+ 'ⓔ' => 'Ⓔ',
+ 'ⓕ' => 'Ⓕ',
+ 'ⓖ' => 'Ⓖ',
+ 'ⓗ' => 'Ⓗ',
+ 'ⓘ' => 'Ⓘ',
+ 'ⓙ' => 'Ⓙ',
+ 'ⓚ' => 'Ⓚ',
+ 'ⓛ' => 'Ⓛ',
+ 'ⓜ' => 'Ⓜ',
+ 'ⓝ' => 'Ⓝ',
+ 'ⓞ' => 'Ⓞ',
+ 'ⓟ' => 'Ⓟ',
+ 'ⓠ' => 'Ⓠ',
+ 'ⓡ' => 'Ⓡ',
+ 'ⓢ' => 'Ⓢ',
+ 'ⓣ' => 'Ⓣ',
+ 'ⓤ' => 'Ⓤ',
+ 'ⓥ' => 'Ⓥ',
+ 'ⓦ' => 'Ⓦ',
+ 'ⓧ' => 'Ⓧ',
+ 'ⓨ' => 'Ⓨ',
+ 'ⓩ' => 'Ⓩ',
+ 'ⰰ' => 'Ⰰ',
+ 'ⰱ' => 'Ⰱ',
+ 'ⰲ' => 'Ⰲ',
+ 'ⰳ' => 'Ⰳ',
+ 'ⰴ' => 'Ⰴ',
+ 'ⰵ' => 'Ⰵ',
+ 'ⰶ' => 'Ⰶ',
+ 'ⰷ' => 'Ⰷ',
+ 'ⰸ' => 'Ⰸ',
+ 'ⰹ' => 'Ⰹ',
+ 'ⰺ' => 'Ⰺ',
+ 'ⰻ' => 'Ⰻ',
+ 'ⰼ' => 'Ⰼ',
+ 'ⰽ' => 'Ⰽ',
+ 'ⰾ' => 'Ⰾ',
+ 'ⰿ' => 'Ⰿ',
+ 'ⱀ' => 'Ⱀ',
+ 'ⱁ' => 'Ⱁ',
+ 'ⱂ' => 'Ⱂ',
+ 'ⱃ' => 'Ⱃ',
+ 'ⱄ' => 'Ⱄ',
+ 'ⱅ' => 'Ⱅ',
+ 'ⱆ' => 'Ⱆ',
+ 'ⱇ' => 'Ⱇ',
+ 'ⱈ' => 'Ⱈ',
+ 'ⱉ' => 'Ⱉ',
+ 'ⱊ' => 'Ⱊ',
+ 'ⱋ' => 'Ⱋ',
+ 'ⱌ' => 'Ⱌ',
+ 'ⱍ' => 'Ⱍ',
+ 'ⱎ' => 'Ⱎ',
+ 'ⱏ' => 'Ⱏ',
+ 'ⱐ' => 'Ⱐ',
+ 'ⱑ' => 'Ⱑ',
+ 'ⱒ' => 'Ⱒ',
+ 'ⱓ' => 'Ⱓ',
+ 'ⱔ' => 'Ⱔ',
+ 'ⱕ' => 'Ⱕ',
+ 'ⱖ' => 'Ⱖ',
+ 'ⱗ' => 'Ⱗ',
+ 'ⱘ' => 'Ⱘ',
+ 'ⱙ' => 'Ⱙ',
+ 'ⱚ' => 'Ⱚ',
+ 'ⱛ' => 'Ⱛ',
+ 'ⱜ' => 'Ⱜ',
+ 'ⱝ' => 'Ⱝ',
+ 'ⱞ' => 'Ⱞ',
+ 'ⱡ' => 'Ⱡ',
+ 'ⱥ' => 'Ⱥ',
+ 'ⱦ' => 'Ⱦ',
+ 'ⱨ' => 'Ⱨ',
+ 'ⱪ' => 'Ⱪ',
+ 'ⱬ' => 'Ⱬ',
+ 'ⱳ' => 'Ⱳ',
+ 'ⱶ' => 'Ⱶ',
+ 'ⲁ' => 'Ⲁ',
+ 'ⲃ' => 'Ⲃ',
+ 'ⲅ' => 'Ⲅ',
+ 'ⲇ' => 'Ⲇ',
+ 'ⲉ' => 'Ⲉ',
+ 'ⲋ' => 'Ⲋ',
+ 'ⲍ' => 'Ⲍ',
+ 'ⲏ' => 'Ⲏ',
+ 'ⲑ' => 'Ⲑ',
+ 'ⲓ' => 'Ⲓ',
+ 'ⲕ' => 'Ⲕ',
+ 'ⲗ' => 'Ⲗ',
+ 'ⲙ' => 'Ⲙ',
+ 'ⲛ' => 'Ⲛ',
+ 'ⲝ' => 'Ⲝ',
+ 'ⲟ' => 'Ⲟ',
+ 'ⲡ' => 'Ⲡ',
+ 'ⲣ' => 'Ⲣ',
+ 'ⲥ' => 'Ⲥ',
+ 'ⲧ' => 'Ⲧ',
+ 'ⲩ' => 'Ⲩ',
+ 'ⲫ' => 'Ⲫ',
+ 'ⲭ' => 'Ⲭ',
+ 'ⲯ' => 'Ⲯ',
+ 'ⲱ' => 'Ⲱ',
+ 'ⲳ' => 'Ⲳ',
+ 'ⲵ' => 'Ⲵ',
+ 'ⲷ' => 'Ⲷ',
+ 'ⲹ' => 'Ⲹ',
+ 'ⲻ' => 'Ⲻ',
+ 'ⲽ' => 'Ⲽ',
+ 'ⲿ' => 'Ⲿ',
+ 'ⳁ' => 'Ⳁ',
+ 'ⳃ' => 'Ⳃ',
+ 'ⳅ' => 'Ⳅ',
+ 'ⳇ' => 'Ⳇ',
+ 'ⳉ' => 'Ⳉ',
+ 'ⳋ' => 'Ⳋ',
+ 'ⳍ' => 'Ⳍ',
+ 'ⳏ' => 'Ⳏ',
+ 'ⳑ' => 'Ⳑ',
+ 'ⳓ' => 'Ⳓ',
+ 'ⳕ' => 'Ⳕ',
+ 'ⳗ' => 'Ⳗ',
+ 'ⳙ' => 'Ⳙ',
+ 'ⳛ' => 'Ⳛ',
+ 'ⳝ' => 'Ⳝ',
+ 'ⳟ' => 'Ⳟ',
+ 'ⳡ' => 'Ⳡ',
+ 'ⳣ' => 'Ⳣ',
+ 'ⳬ' => 'Ⳬ',
+ 'ⳮ' => 'Ⳮ',
+ 'ⳳ' => 'Ⳳ',
+ 'ⴀ' => 'Ⴀ',
+ 'ⴁ' => 'Ⴁ',
+ 'ⴂ' => 'Ⴂ',
+ 'ⴃ' => 'Ⴃ',
+ 'ⴄ' => 'Ⴄ',
+ 'ⴅ' => 'Ⴅ',
+ 'ⴆ' => 'Ⴆ',
+ 'ⴇ' => 'Ⴇ',
+ 'ⴈ' => 'Ⴈ',
+ 'ⴉ' => 'Ⴉ',
+ 'ⴊ' => 'Ⴊ',
+ 'ⴋ' => 'Ⴋ',
+ 'ⴌ' => 'Ⴌ',
+ 'ⴍ' => 'Ⴍ',
+ 'ⴎ' => 'Ⴎ',
+ 'ⴏ' => 'Ⴏ',
+ 'ⴐ' => 'Ⴐ',
+ 'ⴑ' => 'Ⴑ',
+ 'ⴒ' => 'Ⴒ',
+ 'ⴓ' => 'Ⴓ',
+ 'ⴔ' => 'Ⴔ',
+ 'ⴕ' => 'Ⴕ',
+ 'ⴖ' => 'Ⴖ',
+ 'ⴗ' => 'Ⴗ',
+ 'ⴘ' => 'Ⴘ',
+ 'ⴙ' => 'Ⴙ',
+ 'ⴚ' => 'Ⴚ',
+ 'ⴛ' => 'Ⴛ',
+ 'ⴜ' => 'Ⴜ',
+ 'ⴝ' => 'Ⴝ',
+ 'ⴞ' => 'Ⴞ',
+ 'ⴟ' => 'Ⴟ',
+ 'ⴠ' => 'Ⴠ',
+ 'ⴡ' => 'Ⴡ',
+ 'ⴢ' => 'Ⴢ',
+ 'ⴣ' => 'Ⴣ',
+ 'ⴤ' => 'Ⴤ',
+ 'ⴥ' => 'Ⴥ',
+ 'ⴧ' => 'Ⴧ',
+ 'ⴭ' => 'Ⴭ',
+ 'ꙁ' => 'Ꙁ',
+ 'ꙃ' => 'Ꙃ',
+ 'ꙅ' => 'Ꙅ',
+ 'ꙇ' => 'Ꙇ',
+ 'ꙉ' => 'Ꙉ',
+ 'ꙋ' => 'Ꙋ',
+ 'ꙍ' => 'Ꙍ',
+ 'ꙏ' => 'Ꙏ',
+ 'ꙑ' => 'Ꙑ',
+ 'ꙓ' => 'Ꙓ',
+ 'ꙕ' => 'Ꙕ',
+ 'ꙗ' => 'Ꙗ',
+ 'ꙙ' => 'Ꙙ',
+ 'ꙛ' => 'Ꙛ',
+ 'ꙝ' => 'Ꙝ',
+ 'ꙟ' => 'Ꙟ',
+ 'ꙡ' => 'Ꙡ',
+ 'ꙣ' => 'Ꙣ',
+ 'ꙥ' => 'Ꙥ',
+ 'ꙧ' => 'Ꙧ',
+ 'ꙩ' => 'Ꙩ',
+ 'ꙫ' => 'Ꙫ',
+ 'ꙭ' => 'Ꙭ',
+ 'ꚁ' => 'Ꚁ',
+ 'ꚃ' => 'Ꚃ',
+ 'ꚅ' => 'Ꚅ',
+ 'ꚇ' => 'Ꚇ',
+ 'ꚉ' => 'Ꚉ',
+ 'ꚋ' => 'Ꚋ',
+ 'ꚍ' => 'Ꚍ',
+ 'ꚏ' => 'Ꚏ',
+ 'ꚑ' => 'Ꚑ',
+ 'ꚓ' => 'Ꚓ',
+ 'ꚕ' => 'Ꚕ',
+ 'ꚗ' => 'Ꚗ',
+ 'ꚙ' => 'Ꚙ',
+ 'ꚛ' => 'Ꚛ',
+ 'ꜣ' => 'Ꜣ',
+ 'ꜥ' => 'Ꜥ',
+ 'ꜧ' => 'Ꜧ',
+ 'ꜩ' => 'Ꜩ',
+ 'ꜫ' => 'Ꜫ',
+ 'ꜭ' => 'Ꜭ',
+ 'ꜯ' => 'Ꜯ',
+ 'ꜳ' => 'Ꜳ',
+ 'ꜵ' => 'Ꜵ',
+ 'ꜷ' => 'Ꜷ',
+ 'ꜹ' => 'Ꜹ',
+ 'ꜻ' => 'Ꜻ',
+ 'ꜽ' => 'Ꜽ',
+ 'ꜿ' => 'Ꜿ',
+ 'ꝁ' => 'Ꝁ',
+ 'ꝃ' => 'Ꝃ',
+ 'ꝅ' => 'Ꝅ',
+ 'ꝇ' => 'Ꝇ',
+ 'ꝉ' => 'Ꝉ',
+ 'ꝋ' => 'Ꝋ',
+ 'ꝍ' => 'Ꝍ',
+ 'ꝏ' => 'Ꝏ',
+ 'ꝑ' => 'Ꝑ',
+ 'ꝓ' => 'Ꝓ',
+ 'ꝕ' => 'Ꝕ',
+ 'ꝗ' => 'Ꝗ',
+ 'ꝙ' => 'Ꝙ',
+ 'ꝛ' => 'Ꝛ',
+ 'ꝝ' => 'Ꝝ',
+ 'ꝟ' => 'Ꝟ',
+ 'ꝡ' => 'Ꝡ',
+ 'ꝣ' => 'Ꝣ',
+ 'ꝥ' => 'Ꝥ',
+ 'ꝧ' => 'Ꝧ',
+ 'ꝩ' => 'Ꝩ',
+ 'ꝫ' => 'Ꝫ',
+ 'ꝭ' => 'Ꝭ',
+ 'ꝯ' => 'Ꝯ',
+ 'ꝺ' => 'Ꝺ',
+ 'ꝼ' => 'Ꝼ',
+ 'ꝿ' => 'Ꝿ',
+ 'ꞁ' => 'Ꞁ',
+ 'ꞃ' => 'Ꞃ',
+ 'ꞅ' => 'Ꞅ',
+ 'ꞇ' => 'Ꞇ',
+ 'ꞌ' => 'Ꞌ',
+ 'ꞑ' => 'Ꞑ',
+ 'ꞓ' => 'Ꞓ',
+ 'ꞔ' => 'Ꞔ',
+ 'ꞗ' => 'Ꞗ',
+ 'ꞙ' => 'Ꞙ',
+ 'ꞛ' => 'Ꞛ',
+ 'ꞝ' => 'Ꞝ',
+ 'ꞟ' => 'Ꞟ',
+ 'ꞡ' => 'Ꞡ',
+ 'ꞣ' => 'Ꞣ',
+ 'ꞥ' => 'Ꞥ',
+ 'ꞧ' => 'Ꞧ',
+ 'ꞩ' => 'Ꞩ',
+ 'ꞵ' => 'Ꞵ',
+ 'ꞷ' => 'Ꞷ',
+ 'ꞹ' => 'Ꞹ',
+ 'ꞻ' => 'Ꞻ',
+ 'ꞽ' => 'Ꞽ',
+ 'ꞿ' => 'Ꞿ',
+ 'ꟃ' => 'Ꟃ',
+ 'ꟈ' => 'Ꟈ',
+ 'ꟊ' => 'Ꟊ',
+ 'ꟶ' => 'Ꟶ',
+ 'ꭓ' => 'Ꭓ',
+ 'ꭰ' => 'Ꭰ',
+ 'ꭱ' => 'Ꭱ',
+ 'ꭲ' => 'Ꭲ',
+ 'ꭳ' => 'Ꭳ',
+ 'ꭴ' => 'Ꭴ',
+ 'ꭵ' => 'Ꭵ',
+ 'ꭶ' => 'Ꭶ',
+ 'ꭷ' => 'Ꭷ',
+ 'ꭸ' => 'Ꭸ',
+ 'ꭹ' => 'Ꭹ',
+ 'ꭺ' => 'Ꭺ',
+ 'ꭻ' => 'Ꭻ',
+ 'ꭼ' => 'Ꭼ',
+ 'ꭽ' => 'Ꭽ',
+ 'ꭾ' => 'Ꭾ',
+ 'ꭿ' => 'Ꭿ',
+ 'ꮀ' => 'Ꮀ',
+ 'ꮁ' => 'Ꮁ',
+ 'ꮂ' => 'Ꮂ',
+ 'ꮃ' => 'Ꮃ',
+ 'ꮄ' => 'Ꮄ',
+ 'ꮅ' => 'Ꮅ',
+ 'ꮆ' => 'Ꮆ',
+ 'ꮇ' => 'Ꮇ',
+ 'ꮈ' => 'Ꮈ',
+ 'ꮉ' => 'Ꮉ',
+ 'ꮊ' => 'Ꮊ',
+ 'ꮋ' => 'Ꮋ',
+ 'ꮌ' => 'Ꮌ',
+ 'ꮍ' => 'Ꮍ',
+ 'ꮎ' => 'Ꮎ',
+ 'ꮏ' => 'Ꮏ',
+ 'ꮐ' => 'Ꮐ',
+ 'ꮑ' => 'Ꮑ',
+ 'ꮒ' => 'Ꮒ',
+ 'ꮓ' => 'Ꮓ',
+ 'ꮔ' => 'Ꮔ',
+ 'ꮕ' => 'Ꮕ',
+ 'ꮖ' => 'Ꮖ',
+ 'ꮗ' => 'Ꮗ',
+ 'ꮘ' => 'Ꮘ',
+ 'ꮙ' => 'Ꮙ',
+ 'ꮚ' => 'Ꮚ',
+ 'ꮛ' => 'Ꮛ',
+ 'ꮜ' => 'Ꮜ',
+ 'ꮝ' => 'Ꮝ',
+ 'ꮞ' => 'Ꮞ',
+ 'ꮟ' => 'Ꮟ',
+ 'ꮠ' => 'Ꮠ',
+ 'ꮡ' => 'Ꮡ',
+ 'ꮢ' => 'Ꮢ',
+ 'ꮣ' => 'Ꮣ',
+ 'ꮤ' => 'Ꮤ',
+ 'ꮥ' => 'Ꮥ',
+ 'ꮦ' => 'Ꮦ',
+ 'ꮧ' => 'Ꮧ',
+ 'ꮨ' => 'Ꮨ',
+ 'ꮩ' => 'Ꮩ',
+ 'ꮪ' => 'Ꮪ',
+ 'ꮫ' => 'Ꮫ',
+ 'ꮬ' => 'Ꮬ',
+ 'ꮭ' => 'Ꮭ',
+ 'ꮮ' => 'Ꮮ',
+ 'ꮯ' => 'Ꮯ',
+ 'ꮰ' => 'Ꮰ',
+ 'ꮱ' => 'Ꮱ',
+ 'ꮲ' => 'Ꮲ',
+ 'ꮳ' => 'Ꮳ',
+ 'ꮴ' => 'Ꮴ',
+ 'ꮵ' => 'Ꮵ',
+ 'ꮶ' => 'Ꮶ',
+ 'ꮷ' => 'Ꮷ',
+ 'ꮸ' => 'Ꮸ',
+ 'ꮹ' => 'Ꮹ',
+ 'ꮺ' => 'Ꮺ',
+ 'ꮻ' => 'Ꮻ',
+ 'ꮼ' => 'Ꮼ',
+ 'ꮽ' => 'Ꮽ',
+ 'ꮾ' => 'Ꮾ',
+ 'ꮿ' => 'Ꮿ',
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ '𐐨' => '𐐀',
+ '𐐩' => '𐐁',
+ '𐐪' => '𐐂',
+ '𐐫' => '𐐃',
+ '𐐬' => '𐐄',
+ '𐐭' => '𐐅',
+ '𐐮' => '𐐆',
+ '𐐯' => '𐐇',
+ '𐐰' => '𐐈',
+ '𐐱' => '𐐉',
+ '𐐲' => '𐐊',
+ '𐐳' => '𐐋',
+ '𐐴' => '𐐌',
+ '𐐵' => '𐐍',
+ '𐐶' => '𐐎',
+ '𐐷' => '𐐏',
+ '𐐸' => '𐐐',
+ '𐐹' => '𐐑',
+ '𐐺' => '𐐒',
+ '𐐻' => '𐐓',
+ '𐐼' => '𐐔',
+ '𐐽' => '𐐕',
+ '𐐾' => '𐐖',
+ '𐐿' => '𐐗',
+ '𐑀' => '𐐘',
+ '𐑁' => '𐐙',
+ '𐑂' => '𐐚',
+ '𐑃' => '𐐛',
+ '𐑄' => '𐐜',
+ '𐑅' => '𐐝',
+ '𐑆' => '𐐞',
+ '𐑇' => '𐐟',
+ '𐑈' => '𐐠',
+ '𐑉' => '𐐡',
+ '𐑊' => '𐐢',
+ '𐑋' => '𐐣',
+ '𐑌' => '𐐤',
+ '𐑍' => '𐐥',
+ '𐑎' => '𐐦',
+ '𐑏' => '𐐧',
+ '𐓘' => '𐒰',
+ '𐓙' => '𐒱',
+ '𐓚' => '𐒲',
+ '𐓛' => '𐒳',
+ '𐓜' => '𐒴',
+ '𐓝' => '𐒵',
+ '𐓞' => '𐒶',
+ '𐓟' => '𐒷',
+ '𐓠' => '𐒸',
+ '𐓡' => '𐒹',
+ '𐓢' => '𐒺',
+ '𐓣' => '𐒻',
+ '𐓤' => '𐒼',
+ '𐓥' => '𐒽',
+ '𐓦' => '𐒾',
+ '𐓧' => '𐒿',
+ '𐓨' => '𐓀',
+ '𐓩' => '𐓁',
+ '𐓪' => '𐓂',
+ '𐓫' => '𐓃',
+ '𐓬' => '𐓄',
+ '𐓭' => '𐓅',
+ '𐓮' => '𐓆',
+ '𐓯' => '𐓇',
+ '𐓰' => '𐓈',
+ '𐓱' => '𐓉',
+ '𐓲' => '𐓊',
+ '𐓳' => '𐓋',
+ '𐓴' => '𐓌',
+ '𐓵' => '𐓍',
+ '𐓶' => '𐓎',
+ '𐓷' => '𐓏',
+ '𐓸' => '𐓐',
+ '𐓹' => '𐓑',
+ '𐓺' => '𐓒',
+ '𐓻' => '𐓓',
+ '𐳀' => '𐲀',
+ '𐳁' => '𐲁',
+ '𐳂' => '𐲂',
+ '𐳃' => '𐲃',
+ '𐳄' => '𐲄',
+ '𐳅' => '𐲅',
+ '𐳆' => '𐲆',
+ '𐳇' => '𐲇',
+ '𐳈' => '𐲈',
+ '𐳉' => '𐲉',
+ '𐳊' => '𐲊',
+ '𐳋' => '𐲋',
+ '𐳌' => '𐲌',
+ '𐳍' => '𐲍',
+ '𐳎' => '𐲎',
+ '𐳏' => '𐲏',
+ '𐳐' => '𐲐',
+ '𐳑' => '𐲑',
+ '𐳒' => '𐲒',
+ '𐳓' => '𐲓',
+ '𐳔' => '𐲔',
+ '𐳕' => '𐲕',
+ '𐳖' => '𐲖',
+ '𐳗' => '𐲗',
+ '𐳘' => '𐲘',
+ '𐳙' => '𐲙',
+ '𐳚' => '𐲚',
+ '𐳛' => '𐲛',
+ '𐳜' => '𐲜',
+ '𐳝' => '𐲝',
+ '𐳞' => '𐲞',
+ '𐳟' => '𐲟',
+ '𐳠' => '𐲠',
+ '𐳡' => '𐲡',
+ '𐳢' => '𐲢',
+ '𐳣' => '𐲣',
+ '𐳤' => '𐲤',
+ '𐳥' => '𐲥',
+ '𐳦' => '𐲦',
+ '𐳧' => '𐲧',
+ '𐳨' => '𐲨',
+ '𐳩' => '𐲩',
+ '𐳪' => '𐲪',
+ '𐳫' => '𐲫',
+ '𐳬' => '𐲬',
+ '𐳭' => '𐲭',
+ '𐳮' => '𐲮',
+ '𐳯' => '𐲯',
+ '𐳰' => '𐲰',
+ '𐳱' => '𐲱',
+ '𐳲' => '𐲲',
+ '𑣀' => '𑢠',
+ '𑣁' => '𑢡',
+ '𑣂' => '𑢢',
+ '𑣃' => '𑢣',
+ '𑣄' => '𑢤',
+ '𑣅' => '𑢥',
+ '𑣆' => '𑢦',
+ '𑣇' => '𑢧',
+ '𑣈' => '𑢨',
+ '𑣉' => '𑢩',
+ '𑣊' => '𑢪',
+ '𑣋' => '𑢫',
+ '𑣌' => '𑢬',
+ '𑣍' => '𑢭',
+ '𑣎' => '𑢮',
+ '𑣏' => '𑢯',
+ '𑣐' => '𑢰',
+ '𑣑' => '𑢱',
+ '𑣒' => '𑢲',
+ '𑣓' => '𑢳',
+ '𑣔' => '𑢴',
+ '𑣕' => '𑢵',
+ '𑣖' => '𑢶',
+ '𑣗' => '𑢷',
+ '𑣘' => '𑢸',
+ '𑣙' => '𑢹',
+ '𑣚' => '𑢺',
+ '𑣛' => '𑢻',
+ '𑣜' => '𑢼',
+ '𑣝' => '𑢽',
+ '𑣞' => '𑢾',
+ '𑣟' => '𑢿',
+ '𖹠' => '𖹀',
+ '𖹡' => '𖹁',
+ '𖹢' => '𖹂',
+ '𖹣' => '𖹃',
+ '𖹤' => '𖹄',
+ '𖹥' => '𖹅',
+ '𖹦' => '𖹆',
+ '𖹧' => '𖹇',
+ '𖹨' => '𖹈',
+ '𖹩' => '𖹉',
+ '𖹪' => '𖹊',
+ '𖹫' => '𖹋',
+ '𖹬' => '𖹌',
+ '𖹭' => '𖹍',
+ '𖹮' => '𖹎',
+ '𖹯' => '𖹏',
+ '𖹰' => '𖹐',
+ '𖹱' => '𖹑',
+ '𖹲' => '𖹒',
+ '𖹳' => '𖹓',
+ '𖹴' => '𖹔',
+ '𖹵' => '𖹕',
+ '𖹶' => '𖹖',
+ '𖹷' => '𖹗',
+ '𖹸' => '𖹘',
+ '𖹹' => '𖹙',
+ '𖹺' => '𖹚',
+ '𖹻' => '𖹛',
+ '𖹼' => '𖹜',
+ '𖹽' => '𖹝',
+ '𖹾' => '𖹞',
+ '𖹿' => '𖹟',
+ '𞤢' => '𞤀',
+ '𞤣' => '𞤁',
+ '𞤤' => '𞤂',
+ '𞤥' => '𞤃',
+ '𞤦' => '𞤄',
+ '𞤧' => '𞤅',
+ '𞤨' => '𞤆',
+ '𞤩' => '𞤇',
+ '𞤪' => '𞤈',
+ '𞤫' => '𞤉',
+ '𞤬' => '𞤊',
+ '𞤭' => '𞤋',
+ '𞤮' => '𞤌',
+ '𞤯' => '𞤍',
+ '𞤰' => '𞤎',
+ '𞤱' => '𞤏',
+ '𞤲' => '𞤐',
+ '𞤳' => '𞤑',
+ '𞤴' => '𞤒',
+ '𞤵' => '𞤓',
+ '𞤶' => '𞤔',
+ '𞤷' => '𞤕',
+ '𞤸' => '𞤖',
+ '𞤹' => '𞤗',
+ '𞤺' => '𞤘',
+ '𞤻' => '𞤙',
+ '𞤼' => '𞤚',
+ '𞤽' => '𞤛',
+ '𞤾' => '𞤜',
+ '𞤿' => '𞤝',
+ '𞥀' => '𞤞',
+ '𞥁' => '𞤟',
+ '𞥂' => '𞤠',
+ '𞥃' => '𞤡',
+ 'ß' => 'SS',
+ 'ff' => 'FF',
+ 'fi' => 'FI',
+ 'fl' => 'FL',
+ 'ffi' => 'FFI',
+ 'ffl' => 'FFL',
+ 'ſt' => 'ST',
+ 'st' => 'ST',
+ 'և' => 'ԵՒ',
+ 'ﬓ' => 'ՄՆ',
+ 'ﬔ' => 'ՄԵ',
+ 'ﬕ' => 'ՄԻ',
+ 'ﬖ' => 'ՎՆ',
+ 'ﬗ' => 'ՄԽ',
+ 'ʼn' => 'ʼN',
+ 'ΐ' => 'Ϊ́',
+ 'ΰ' => 'Ϋ́',
+ 'ǰ' => 'J̌',
+ 'ẖ' => 'H̱',
+ 'ẗ' => 'T̈',
+ 'ẘ' => 'W̊',
+ 'ẙ' => 'Y̊',
+ 'ẚ' => 'Aʾ',
+ 'ὐ' => 'Υ̓',
+ 'ὒ' => 'Υ̓̀',
+ 'ὔ' => 'Υ̓́',
+ 'ὖ' => 'Υ̓͂',
+ 'ᾶ' => 'Α͂',
+ 'ῆ' => 'Η͂',
+ 'ῒ' => 'Ϊ̀',
+ 'ΐ' => 'Ϊ́',
+ 'ῖ' => 'Ι͂',
+ 'ῗ' => 'Ϊ͂',
+ 'ῢ' => 'Ϋ̀',
+ 'ΰ' => 'Ϋ́',
+ 'ῤ' => 'Ρ̓',
+ 'ῦ' => 'Υ͂',
+ 'ῧ' => 'Ϋ͂',
+ 'ῶ' => 'Ω͂',
+ 'ᾈ' => 'ἈΙ',
+ 'ᾉ' => 'ἉΙ',
+ 'ᾊ' => 'ἊΙ',
+ 'ᾋ' => 'ἋΙ',
+ 'ᾌ' => 'ἌΙ',
+ 'ᾍ' => 'ἍΙ',
+ 'ᾎ' => 'ἎΙ',
+ 'ᾏ' => 'ἏΙ',
+ 'ᾘ' => 'ἨΙ',
+ 'ᾙ' => 'ἩΙ',
+ 'ᾚ' => 'ἪΙ',
+ 'ᾛ' => 'ἫΙ',
+ 'ᾜ' => 'ἬΙ',
+ 'ᾝ' => 'ἭΙ',
+ 'ᾞ' => 'ἮΙ',
+ 'ᾟ' => 'ἯΙ',
+ 'ᾨ' => 'ὨΙ',
+ 'ᾩ' => 'ὩΙ',
+ 'ᾪ' => 'ὪΙ',
+ 'ᾫ' => 'ὫΙ',
+ 'ᾬ' => 'ὬΙ',
+ 'ᾭ' => 'ὭΙ',
+ 'ᾮ' => 'ὮΙ',
+ 'ᾯ' => 'ὯΙ',
+ 'ᾼ' => 'ΑΙ',
+ 'ῌ' => 'ΗΙ',
+ 'ῼ' => 'ΩΙ',
+ 'ᾲ' => 'ᾺΙ',
+ 'ᾴ' => 'ΆΙ',
+ 'ῂ' => 'ῊΙ',
+ 'ῄ' => 'ΉΙ',
+ 'ῲ' => 'ῺΙ',
+ 'ῴ' => 'ΏΙ',
+ 'ᾷ' => 'Α͂Ι',
+ 'ῇ' => 'Η͂Ι',
+ 'ῷ' => 'Ω͂Ι',
+);
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php
new file mode 100644
index 00000000..ff51ae07
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap.php
@@ -0,0 +1,172 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (\PHP_VERSION_ID >= 80000) {
+ return require __DIR__.'/bootstrap80.php';
+}
+
+if (!function_exists('mb_convert_encoding')) {
+ function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
+}
+if (!function_exists('mb_decode_mimeheader')) {
+ function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
+}
+if (!function_exists('mb_encode_mimeheader')) {
+ function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
+}
+if (!function_exists('mb_decode_numericentity')) {
+ function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
+}
+if (!function_exists('mb_encode_numericentity')) {
+ function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
+}
+if (!function_exists('mb_convert_case')) {
+ function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
+}
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
+}
+if (!function_exists('mb_language')) {
+ function mb_language($language = null) { return p\Mbstring::mb_language($language); }
+}
+if (!function_exists('mb_list_encodings')) {
+ function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+}
+if (!function_exists('mb_encoding_aliases')) {
+ function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+}
+if (!function_exists('mb_check_encoding')) {
+ function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
+}
+if (!function_exists('mb_detect_encoding')) {
+ function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
+}
+if (!function_exists('mb_detect_order')) {
+ function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
+}
+if (!function_exists('mb_parse_str')) {
+ function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
+}
+if (!function_exists('mb_strlen')) {
+ function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
+}
+if (!function_exists('mb_strpos')) {
+ function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strtolower')) {
+ function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
+}
+if (!function_exists('mb_strtoupper')) {
+ function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
+}
+if (!function_exists('mb_substitute_character')) {
+ function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
+}
+if (!function_exists('mb_substr')) {
+ function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
+}
+if (!function_exists('mb_stripos')) {
+ function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_stristr')) {
+ function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrchr')) {
+ function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrichr')) {
+ function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_strripos')) {
+ function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strrpos')) {
+ function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
+}
+if (!function_exists('mb_strstr')) {
+ function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
+}
+if (!function_exists('mb_get_info')) {
+ function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+}
+if (!function_exists('mb_http_output')) {
+ function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
+}
+if (!function_exists('mb_strwidth')) {
+ function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
+}
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
+}
+if (!function_exists('mb_output_handler')) {
+ function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
+}
+if (!function_exists('mb_http_input')) {
+ function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
+}
+
+if (!function_exists('mb_convert_variables')) {
+ function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
+}
+
+if (!function_exists('mb_ord')) {
+ function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
+}
+if (!function_exists('mb_str_split')) {
+ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
+}
+
+if (!function_exists('mb_str_pad')) {
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+}
+
+if (!function_exists('mb_ucfirst')) {
+ function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
+}
+
+if (!function_exists('mb_lcfirst')) {
+ function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
+}
+
+if (!function_exists('mb_trim')) {
+ function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_ltrim')) {
+ function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_rtrim')) {
+ function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
+}
+
+
+if (extension_loaded('mbstring')) {
+ return;
+}
+
+if (!defined('MB_CASE_UPPER')) {
+ define('MB_CASE_UPPER', 0);
+}
+if (!defined('MB_CASE_LOWER')) {
+ define('MB_CASE_LOWER', 1);
+}
+if (!defined('MB_CASE_TITLE')) {
+ define('MB_CASE_TITLE', 2);
+}
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php
new file mode 100644
index 00000000..5be7d201
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/bootstrap80.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_convert_encoding')) {
+ function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); }
+}
+if (!function_exists('mb_decode_mimeheader')) {
+ function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); }
+}
+if (!function_exists('mb_encode_mimeheader')) {
+ function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); }
+}
+if (!function_exists('mb_decode_numericentity')) {
+ function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); }
+}
+if (!function_exists('mb_encode_numericentity')) {
+ function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); }
+}
+if (!function_exists('mb_convert_case')) {
+ function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); }
+}
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); }
+}
+if (!function_exists('mb_language')) {
+ function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); }
+}
+if (!function_exists('mb_list_encodings')) {
+ function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); }
+}
+if (!function_exists('mb_encoding_aliases')) {
+ function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); }
+}
+if (!function_exists('mb_check_encoding')) {
+ function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); }
+}
+if (!function_exists('mb_detect_encoding')) {
+ function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); }
+}
+if (!function_exists('mb_detect_order')) {
+ function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); }
+}
+if (!function_exists('mb_parse_str')) {
+ function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; }
+}
+if (!function_exists('mb_strlen')) {
+ function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); }
+}
+if (!function_exists('mb_strpos')) {
+ function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strtolower')) {
+ function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); }
+}
+if (!function_exists('mb_strtoupper')) {
+ function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); }
+}
+if (!function_exists('mb_substitute_character')) {
+ function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); }
+}
+if (!function_exists('mb_substr')) {
+ function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); }
+}
+if (!function_exists('mb_stripos')) {
+ function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_stristr')) {
+ function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrchr')) {
+ function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strrichr')) {
+ function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_strripos')) {
+ function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strrpos')) {
+ function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
+}
+if (!function_exists('mb_strstr')) {
+ function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
+}
+if (!function_exists('mb_get_info')) {
+ function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); }
+}
+if (!function_exists('mb_http_output')) {
+ function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
+}
+if (!function_exists('mb_strwidth')) {
+ function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); }
+}
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); }
+}
+if (!function_exists('mb_output_handler')) {
+ function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); }
+}
+if (!function_exists('mb_http_input')) {
+ function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); }
+}
+
+if (!function_exists('mb_convert_variables')) {
+ function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); }
+}
+
+if (!function_exists('mb_ord')) {
+ function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); }
+}
+if (!function_exists('mb_scrub')) {
+ function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); }
+}
+if (!function_exists('mb_str_split')) {
+ function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); }
+}
+
+if (!function_exists('mb_str_pad')) {
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+}
+
+if (!function_exists('mb_ucfirst')) {
+ function mb_ucfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
+}
+
+if (!function_exists('mb_lcfirst')) {
+ function mb_lcfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
+}
+
+if (!function_exists('mb_trim')) {
+ function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_ltrim')) {
+ function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
+}
+
+if (!function_exists('mb_rtrim')) {
+ function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
+}
+
+if (extension_loaded('mbstring')) {
+ return;
+}
+
+if (!defined('MB_CASE_UPPER')) {
+ define('MB_CASE_UPPER', 0);
+}
+if (!defined('MB_CASE_LOWER')) {
+ define('MB_CASE_LOWER', 1);
+}
+if (!defined('MB_CASE_TITLE')) {
+ define('MB_CASE_TITLE', 2);
+}
diff --git a/www/libs/vendor/symfony/polyfill-mbstring/composer.json b/www/libs/vendor/symfony/polyfill-mbstring/composer.json
new file mode 100644
index 00000000..4ed241a3
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-mbstring/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/polyfill-mbstring",
+ "type": "library",
+ "description": "Symfony polyfill for the Mbstring extension",
+ "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/LICENSE b/www/libs/vendor/symfony/polyfill-php83/LICENSE
new file mode 100644
index 00000000..733c826e
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2022-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+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.
diff --git a/www/libs/vendor/symfony/polyfill-php83/Php83.php b/www/libs/vendor/symfony/polyfill-php83/Php83.php
new file mode 100644
index 00000000..3d94b6c3
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Php83.php
@@ -0,0 +1,197 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php83;
+
+/**
+ * @author Ion Bazan
+ * @author Pierre Ambroise
+ *
+ * @internal
+ */
+final class Php83
+{
+ private const JSON_MAX_DEPTH = 0x7FFFFFFF; // see https://www.php.net/manual/en/function.json-decode.php
+
+ public static function json_validate(string $json, int $depth = 512, int $flags = 0): bool
+ {
+ if (0 !== $flags && \defined('JSON_INVALID_UTF8_IGNORE') && \JSON_INVALID_UTF8_IGNORE !== $flags) {
+ throw new \ValueError('json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)');
+ }
+
+ if ($depth <= 0) {
+ throw new \ValueError('json_validate(): Argument #2 ($depth) must be greater than 0');
+ }
+
+ if ($depth > self::JSON_MAX_DEPTH) {
+ throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
+ }
+
+ json_decode($json, null, $depth, $flags);
+
+ return \JSON_ERROR_NONE === json_last_error();
+ }
+
+ public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
+ {
+ if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
+ throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
+ }
+
+ if (null === $encoding) {
+ $encoding = mb_internal_encoding();
+ }
+
+ try {
+ $validEncoding = @mb_check_encoding('', $encoding);
+ } catch (\ValueError $e) {
+ throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ }
+
+ // BC for PHP 7.3 and lower
+ if (!$validEncoding) {
+ throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ }
+
+ if (mb_strlen($pad_string, $encoding) <= 0) {
+ throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
+ }
+
+ $paddingRequired = $length - mb_strlen($string, $encoding);
+
+ if ($paddingRequired < 1) {
+ return $string;
+ }
+
+ switch ($pad_type) {
+ case \STR_PAD_LEFT:
+ return mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
+ case \STR_PAD_RIGHT:
+ return $string.mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
+ default:
+ $leftPaddingLength = floor($paddingRequired / 2);
+ $rightPaddingLength = $paddingRequired - $leftPaddingLength;
+
+ return mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
+ }
+ }
+
+ public static function str_increment(string $string): string
+ {
+ if ('' === $string) {
+ throw new \ValueError('str_increment(): Argument #1 ($string) cannot be empty');
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) {
+ throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
+ }
+
+ if (is_numeric($string)) {
+ $offset = stripos($string, 'e');
+ if (false !== $offset) {
+ $char = $string[$offset];
+ ++$char;
+ $string[$offset] = $char;
+ ++$string;
+
+ switch ($string[$offset]) {
+ case 'f':
+ $string[$offset] = 'e';
+ break;
+ case 'F':
+ $string[$offset] = 'E';
+ break;
+ case 'g':
+ $string[$offset] = 'f';
+ break;
+ case 'G':
+ $string[$offset] = 'F';
+ break;
+ }
+
+ return $string;
+ }
+ }
+
+ return ++$string;
+ }
+
+ public static function str_decrement(string $string): string
+ {
+ if ('' === $string) {
+ throw new \ValueError('str_decrement(): Argument #1 ($string) cannot be empty');
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) {
+ throw new \ValueError('str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
+ }
+
+ if (preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) {
+ throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
+ }
+
+ if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) {
+ return implode('', \array_slice(str_split($string), 0, -1)).\chr(\ord(substr($string, -1)) - 1);
+ }
+
+ $carry = '';
+ $decremented = '';
+
+ for ($i = \strlen($string) - 1; $i >= 0; --$i) {
+ $char = $string[$i];
+
+ switch ($char) {
+ case 'A':
+ if ('' !== $carry) {
+ $decremented = $carry.$decremented;
+ $carry = '';
+ }
+ $carry = 'Z';
+
+ break;
+ case 'a':
+ if ('' !== $carry) {
+ $decremented = $carry.$decremented;
+ $carry = '';
+ }
+ $carry = 'z';
+
+ break;
+ case '0':
+ if ('' !== $carry) {
+ $decremented = $carry.$decremented;
+ $carry = '';
+ }
+ $carry = '9';
+
+ break;
+ case '1':
+ if ('' !== $carry) {
+ $decremented = $carry.$decremented;
+ $carry = '';
+ }
+
+ break;
+ default:
+ if ('' !== $carry) {
+ $decremented = $carry.$decremented;
+ $carry = '';
+ }
+
+ if (!\in_array($char, ['A', 'a', '0'], true)) {
+ $decremented = \chr(\ord($char) - 1).$decremented;
+ }
+ }
+ }
+
+ return $decremented;
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/README.md b/www/libs/vendor/symfony/polyfill-php83/README.md
new file mode 100644
index 00000000..f2987768
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/README.md
@@ -0,0 +1,22 @@
+Symfony Polyfill / Php83
+========================
+
+This component provides features added to PHP 8.3 core:
+
+- [`json_validate`](https://wiki.php.net/rfc/json_validate)
+- [`Override`](https://wiki.php.net/rfc/marking_overriden_methods)
+- [`mb_str_pad`](https://wiki.php.net/rfc/mb_str_pad)
+- [`ldap_exop_sync`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
+- [`ldap_connect_wallet`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
+- [`stream_context_set_options`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
+- [`str_increment` and `str_decrement`](https://wiki.php.net/rfc/saner-inc-dec-operators)
+- [`Date*Exception/Error classes`](https://wiki.php.net/rfc/datetime-exceptions)
+- [`SQLite3Exception`](https://wiki.php.net/rfc/sqlite3_exceptions)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php
new file mode 100644
index 00000000..6e7ed8c8
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateError extends Error
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php
new file mode 100644
index 00000000..041710af
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateException extends Exception
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php
new file mode 100644
index 00000000..e2e9dfc9
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateInvalidOperationException extends DateException
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php
new file mode 100644
index 00000000..75bcd267
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateInvalidTimeZoneException extends DateException
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php
new file mode 100644
index 00000000..af91b8e4
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateMalformedIntervalStringException extends DateException
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php
new file mode 100644
index 00000000..9b6d2764
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateMalformedPeriodStringException extends DateException
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php
new file mode 100644
index 00000000..7ad04849
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateMalformedStringException extends DateException
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php
new file mode 100644
index 00000000..11f0edc6
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateObjectError extends DateError
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php
new file mode 100644
index 00000000..98e67036
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class DateRangeError extends DateError
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/Override.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/Override.php
new file mode 100644
index 00000000..d3e6b3e1
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/Override.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ #[Attribute(Attribute::TARGET_METHOD)]
+ final class Override
+ {
+ public function __construct()
+ {
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php
new file mode 100644
index 00000000..ecb7c98e
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+ class SQLite3Exception extends Exception
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap.php
new file mode 100644
index 00000000..a92799cb
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/bootstrap.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php83 as p;
+
+if (\PHP_VERSION_ID >= 80300) {
+ return;
+}
+
+if (!function_exists('json_validate')) {
+ function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
+}
+
+if (extension_loaded('mbstring')) {
+ if (!function_exists('mb_str_pad')) {
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ }
+}
+
+if (!function_exists('stream_context_set_options')) {
+ function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
+}
+
+if (!function_exists('str_increment')) {
+ function str_increment(string $string): string { return p\Php83::str_increment($string); }
+}
+
+if (!function_exists('str_decrement')) {
+ function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
+}
+
+if (\PHP_VERSION_ID >= 80100) {
+ return require __DIR__.'/bootstrap81.php';
+}
+
+if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
+ function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+}
+
+if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
+ function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php b/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php
new file mode 100644
index 00000000..68395b43
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/bootstrap81.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if (\PHP_VERSION_ID >= 80300) {
+ return;
+}
+
+if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
+ function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+}
+
+if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
+ function ldap_connect_wallet(?string $uri, string $wallet, #[\SensitiveParameter] string $password, int $auth_mode = \GSLC_SSL_NO_AUTH): \LDAP\Connection|false { return ldap_connect($uri, $wallet, $password, $auth_mode); }
+}
diff --git a/www/libs/vendor/symfony/polyfill-php83/composer.json b/www/libs/vendor/symfony/polyfill-php83/composer.json
new file mode 100644
index 00000000..a8b8ba70
--- /dev/null
+++ b/www/libs/vendor/symfony/polyfill-php83/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/polyfill-php83",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php83\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Alias.php b/www/libs/vendor/symfony/routing/Alias.php
new file mode 100644
index 00000000..7627f12c
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Alias.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\Routing\Exception\InvalidArgumentException;
+
+class Alias
+{
+ private string $id;
+ private array $deprecation = [];
+
+ public function __construct(string $id)
+ {
+ $this->id = $id;
+ }
+
+ public function withId(string $id): static
+ {
+ $new = clone $this;
+
+ $new->id = $id;
+
+ return $new;
+ }
+
+ /**
+ * Returns the target name of this alias.
+ *
+ * @return string The target name
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * Whether this alias is deprecated, that means it should not be referenced anymore.
+ *
+ * @param string $package The name of the composer package that is triggering the deprecation
+ * @param string $version The version of the package that introduced the deprecation
+ * @param string $message The deprecation message to use
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException when the message template is invalid
+ */
+ public function setDeprecated(string $package, string $version, string $message): static
+ {
+ if ('' !== $message) {
+ if (preg_match('#[\r\n]|\*/#', $message)) {
+ throw new InvalidArgumentException('Invalid characters found in deprecation template.');
+ }
+
+ if (!str_contains($message, '%alias_id%')) {
+ throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.');
+ }
+ }
+
+ $this->deprecation = [
+ 'package' => $package,
+ 'version' => $version,
+ 'message' => $message ?: 'The "%alias_id%" route alias is deprecated. You should stop using it, as it will be removed in the future.',
+ ];
+
+ return $this;
+ }
+
+ public function isDeprecated(): bool
+ {
+ return (bool) $this->deprecation;
+ }
+
+ /**
+ * @param string $name Route name relying on this alias
+ */
+ public function getDeprecation(string $name): array
+ {
+ return [
+ 'package' => $this->deprecation['package'],
+ 'version' => $this->deprecation['version'],
+ 'message' => str_replace('%alias_id%', $name, $this->deprecation['message']),
+ ];
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Annotation/Route.php b/www/libs/vendor/symfony/routing/Annotation/Route.php
new file mode 100644
index 00000000..dda3bdad
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Annotation/Route.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Annotation;
+
+// do not deprecate in 6.4/7.0, to make it easier for the ecosystem to support 6.4, 7.4 and 8.0 simultaneously
+
+class_exists(\Symfony\Component\Routing\Attribute\Route::class);
+
+if (false) {
+ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+ class Route extends \Symfony\Component\Routing\Attribute\Route
+ {
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Attribute/Route.php b/www/libs/vendor/symfony/routing/Attribute/Route.php
new file mode 100644
index 00000000..a1d86fe6
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Attribute/Route.php
@@ -0,0 +1,259 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Attribute;
+
+/**
+ * Annotation class for @Route().
+ *
+ * @Annotation
+ * @NamedArgumentConstructor
+ * @Target({"CLASS", "METHOD"})
+ *
+ * @author Fabien Potencier
+ * @author Alexander M. Turek
+ */
+#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class Route
+{
+ private ?string $path = null;
+ private array $localizedPaths = [];
+ private array $methods;
+ private array $schemes;
+
+ /**
+ * @param array $requirements
+ * @param string[]|string $methods
+ * @param string[]|string $schemes
+ */
+ public function __construct(
+ string|array|null $path = null,
+ private ?string $name = null,
+ private array $requirements = [],
+ private array $options = [],
+ private array $defaults = [],
+ private ?string $host = null,
+ array|string $methods = [],
+ array|string $schemes = [],
+ private ?string $condition = null,
+ private ?int $priority = null,
+ ?string $locale = null,
+ ?string $format = null,
+ ?bool $utf8 = null,
+ ?bool $stateless = null,
+ private ?string $env = null
+ ) {
+ if (\is_array($path)) {
+ $this->localizedPaths = $path;
+ } else {
+ $this->path = $path;
+ }
+ $this->setMethods($methods);
+ $this->setSchemes($schemes);
+
+ if (null !== $locale) {
+ $this->defaults['_locale'] = $locale;
+ }
+
+ if (null !== $format) {
+ $this->defaults['_format'] = $format;
+ }
+
+ if (null !== $utf8) {
+ $this->options['utf8'] = $utf8;
+ }
+
+ if (null !== $stateless) {
+ $this->defaults['_stateless'] = $stateless;
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function setPath(string $path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * @return void
+ */
+ public function setLocalizedPaths(array $localizedPaths)
+ {
+ $this->localizedPaths = $localizedPaths;
+ }
+
+ public function getLocalizedPaths(): array
+ {
+ return $this->localizedPaths;
+ }
+
+ /**
+ * @return void
+ */
+ public function setHost(string $pattern)
+ {
+ $this->host = $pattern;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * @return void
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return void
+ */
+ public function setRequirements(array $requirements)
+ {
+ $this->requirements = $requirements;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRequirements()
+ {
+ return $this->requirements;
+ }
+
+ /**
+ * @return void
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @return void
+ */
+ public function setDefaults(array $defaults)
+ {
+ $this->defaults = $defaults;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDefaults()
+ {
+ return $this->defaults;
+ }
+
+ /**
+ * @return void
+ */
+ public function setSchemes(array|string $schemes)
+ {
+ $this->schemes = (array) $schemes;
+ }
+
+ /**
+ * @return array
+ */
+ public function getSchemes()
+ {
+ return $this->schemes;
+ }
+
+ /**
+ * @return void
+ */
+ public function setMethods(array|string $methods)
+ {
+ $this->methods = (array) $methods;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * @return void
+ */
+ public function setCondition(?string $condition)
+ {
+ $this->condition = $condition;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCondition()
+ {
+ return $this->condition;
+ }
+
+ public function setPriority(int $priority): void
+ {
+ $this->priority = $priority;
+ }
+
+ public function getPriority(): ?int
+ {
+ return $this->priority;
+ }
+
+ public function setEnv(?string $env): void
+ {
+ $this->env = $env;
+ }
+
+ public function getEnv(): ?string
+ {
+ return $this->env;
+ }
+}
+
+if (!class_exists(\Symfony\Component\Routing\Annotation\Route::class, false)) {
+ class_alias(Route::class, \Symfony\Component\Routing\Annotation\Route::class);
+}
diff --git a/www/libs/vendor/symfony/routing/CHANGELOG.md b/www/libs/vendor/symfony/routing/CHANGELOG.md
new file mode 100644
index 00000000..693ab8bf
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/CHANGELOG.md
@@ -0,0 +1,326 @@
+CHANGELOG
+=========
+
+6.4
+---
+
+ * Add FQCN and FQCN::method aliases for routes loaded from attributes/annotations when applicable
+ * Add native return type to `AnnotationClassLoader::setResolver()`
+ * Deprecate Doctrine annotations support in favor of native attributes
+ * Change the constructor signature of `AnnotationClassLoader` to `__construct(?string $env = null)`, passing an annotation reader as first argument is deprecated
+ * Deprecate `AnnotationClassLoader`, use `AttributeClassLoader` instead
+ * Deprecate `AnnotationDirectoryLoader`, use `AttributeDirectoryLoader` instead
+ * Deprecate `AnnotationFileLoader`, use `AttributeFileLoader` instead
+ * Add `AddExpressionLanguageProvidersPass` (moved from `FrameworkBundle`)
+ * Add aliases for all classes in the `Annotation` namespace to `Attribute`
+
+6.2
+---
+
+ * Add `Requirement::POSITIVE_INT` for common ids and pagination
+
+6.1
+---
+
+ * Add `getMissingParameters` and `getRouteName` methods on `MissingMandatoryParametersException`
+ * Allow using UTF-8 parameter names
+ * Support the `attribute` type (alias of `annotation`) in annotation loaders
+ * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs (query parameters)
+ * Add `EnumRequirement` to help generate route requirements from a `\BackedEnum`
+ * Add `Requirement`, a collection of universal regular-expression constants to use as route parameter requirements
+ * Add `params` variable to condition expression
+ * Deprecate not passing route parameters as the fourth argument to `UrlMatcher::handleRouteRequirements()`
+
+5.3
+---
+
+ * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs
+ * Add support for per-env configuration in XML and Yaml loaders
+ * Deprecate creating instances of the `Route` annotation class by passing an array of parameters
+ * Add `RoutingConfigurator::env()` to get the current environment
+
+5.2.0
+-----
+
+ * Added support for inline definition of requirements and defaults for host
+ * Added support for `\A` and `\z` as regex start and end for route requirement
+ * Added support for `#[Route]` attributes
+
+5.1.0
+-----
+
+ * added the protected method `PhpFileLoader::callConfigurator()` as extension point to ease custom routing configuration
+ * deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`.
+ * added "priority" option to annotated routes
+ * added argument `$priority` to `RouteCollection::add()`
+ * deprecated the `RouteCompiler::REGEX_DELIMITER` constant
+ * added `ExpressionLanguageProvider` to expose extra functions to route conditions
+ * added support for a `stateless` keyword for configuring route stateless in PHP, YAML and XML configurations.
+ * added the "hosts" option to be able to configure the host per locale.
+ * added `RequestContext::fromUri()` to ease building the default context
+
+5.0.0
+-----
+
+ * removed `PhpGeneratorDumper` and `PhpMatcherDumper`
+ * removed `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options
+ * `Serializable` implementing methods for `Route` and `CompiledRoute` are final
+ * removed referencing service route loaders with a single colon
+ * Removed `ServiceRouterLoader` and `ObjectRouteLoader`.
+
+4.4.0
+-----
+
+ * Deprecated `ServiceRouterLoader` in favor of `ContainerLoader`.
+ * Deprecated `ObjectRouteLoader` in favor of `ObjectLoader`.
+ * Added a way to exclude patterns of resources from being imported by the `import()` method
+
+4.3.0
+-----
+
+ * added `CompiledUrlMatcher` and `CompiledUrlMatcherDumper`
+ * added `CompiledUrlGenerator` and `CompiledUrlGeneratorDumper`
+ * deprecated `PhpGeneratorDumper` and `PhpMatcherDumper`
+ * deprecated `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options
+ * `Serializable` implementing methods for `Route` and `CompiledRoute` are marked as `@internal` and `@final`.
+ Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible
+ with the new serialization methods in PHP 7.4.
+ * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators
+ * added support for invokable service route loaders
+
+4.2.0
+-----
+
+ * added fallback to cultureless locale for internationalized routes
+
+4.0.0
+-----
+
+ * dropped support for using UTF-8 route patterns without using the `utf8` option
+ * dropped support for using UTF-8 route requirements without using the `utf8` option
+
+3.4.0
+-----
+
+ * Added `NoConfigurationException`.
+ * Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_")
+ * Added support for prioritized routing loaders.
+ * Add matched and default parameters to redirect responses
+ * Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations.
+
+3.3.0
+-----
+
+ * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0.
+ * router.options.generator_class
+ * router.options.generator_base_class
+ * router.options.generator_dumper_class
+ * router.options.matcher_class
+ * router.options.matcher_base_class
+ * router.options.matcher_dumper_class
+ * router.options.matcher.cache_class
+ * router.options.generator.cache_class
+
+3.2.0
+-----
+
+ * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations.
+ * Added support for UTF-8 requirements
+
+2.8.0
+-----
+
+ * allowed specifying a directory to recursively load all routing configuration files it contains
+ * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded
+ by calling a method on an object/service.
+ * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method.
+ Use the constants defined in the `UrlGeneratorInterface` instead.
+
+ Before:
+
+ ```php
+ $router->generate('blog_show', ['slug' => 'my-blog-post'], true);
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+ $router->generate('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL);
+ ```
+
+2.5.0
+-----
+
+ * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and
+ will be removed in Symfony 3.0, since the performance gains were minimal and
+ it's hard to replicate the behavior of PHP implementation.
+
+2.3.0
+-----
+
+ * added RequestContext::getQueryString()
+
+2.2.0
+-----
+
+ * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0):
+
+ * The `pattern` setting for a route has been deprecated in favor of `path`
+ * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings
+
+ Before:
+
+ ```yaml
+ article_edit:
+ pattern: /article/{id}
+ requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' }
+ ```
+
+ ```xml
+
+ POST|PUT
+ https
+ \d+
+
+ ```
+
+ ```php
+ $route = new Route();
+ $route->setPattern('/article/{id}');
+ $route->setRequirement('_method', 'POST|PUT');
+ $route->setRequirement('_scheme', 'https');
+ ```
+
+ After:
+
+ ```yaml
+ article_edit:
+ path: /article/{id}
+ methods: [POST, PUT]
+ schemes: https
+ requirements: { 'id': '\d+' }
+ ```
+
+ ```xml
+
+ \d+
+
+ ```
+
+ ```php
+ $route = new Route();
+ $route->setPath('/article/{id}');
+ $route->setMethods(['POST', 'PUT']);
+ $route->setSchemes('https');
+ ```
+
+ * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as
+ a flat array of Routes. So when using PHP to build the RouteCollection, you must
+ make sure to add routes to the sub-collection before adding it to the parent
+ collection (this is not relevant when using YAML or XML for Route definitions).
+
+ Before:
+
+ ```php
+ $rootCollection = new RouteCollection();
+ $subCollection = new RouteCollection();
+ $rootCollection->addCollection($subCollection);
+ $subCollection->add('foo', new Route('/foo'));
+ ```
+
+ After:
+
+ ```php
+ $rootCollection = new RouteCollection();
+ $subCollection = new RouteCollection();
+ $subCollection->add('foo', new Route('/foo'));
+ $rootCollection->addCollection($subCollection);
+ ```
+
+ Also one must call `addCollection` from the bottom to the top hierarchy.
+ So the correct sequence is the following (and not the reverse):
+
+ ```php
+ $childCollection->addCollection($grandchildCollection);
+ $rootCollection->addCollection($childCollection);
+ ```
+
+ * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()`
+ have been deprecated and will be removed in Symfony 2.3.
+ * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements
+ or options without adding a prefix is not supported anymore. So if you called `addPrefix`
+ with an empty prefix or `/` only (both have no relevance), like
+ `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)`
+ you need to use the new dedicated methods `addDefaults($defaultsArray)`,
+ `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead.
+ * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated
+ because adding options has nothing to do with adding a path prefix. If you want to add options
+ to all child routes of a RouteCollection, you can use `addOptions()`.
+ * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated
+ because it suggested that all routes in the collection would have this prefix, which is
+ not necessarily true. On top of that, since there is no tree structure anymore, this method
+ is also useless. Don't worry about performance, prefix optimization for matching is still done
+ in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities.
+ * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be
+ used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options`
+ will still work, but have been deprecated. The `addPrefix` method should be used for this
+ use-case instead.
+ Before: `$parentCollection->addCollection($collection, '/prefix', [...], [...])`
+ After:
+ ```php
+ $collection->addPrefix('/prefix', [...], [...]);
+ $parentCollection->addCollection($collection);
+ ```
+ * added support for the method default argument values when defining a @Route
+ * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`.
+ * Characters that function as separator between placeholders are now whitelisted
+ to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`.
+ * [BC BREAK] The default requirement of a variable has been changed slightly.
+ Previously it disallowed the previous and the next char around a variable. Now
+ it disallows the slash (`/`) and the next char. Using the previous char added
+ no value and was problematic because the route `/index.{_format}` would be
+ matched by `/index.ht/ml`.
+ * The default requirement now uses possessive quantifiers when possible which
+ improves matching performance by up to 20% because it prevents backtracking
+ when it's not needed.
+ * The ConfigurableRequirementsInterface can now also be used to disable the requirements
+ check on URL generation completely by calling `setStrictRequirements(null)`. It
+ improves performance in production environment as you should know that params always
+ pass the requirements (otherwise it would break your link anyway).
+ * There is no restriction on the route name anymore. So non-alphanumeric characters
+ are now also allowed.
+ * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static
+ (only relevant if you implemented your own RouteCompiler).
+ * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g.
+ "../parent-file" and "//example.com/dir/file". The third parameter in
+ `UrlGeneratorInterface::generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)`
+ now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for
+ claritiy. The old method calls with a Boolean parameter will continue to work because they
+ equal the signature using the constants.
+
+2.1.0
+-----
+
+ * added RequestMatcherInterface
+ * added RequestContext::fromRequest()
+ * the UrlMatcher does not throw a \LogicException anymore when the required
+ scheme is not the current one
+ * added TraceableUrlMatcher
+ * added the possibility to define options, default values and requirements
+ for placeholders in prefix, including imported routes
+ * added RouterInterface::getRouteCollection
+ * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they
+ were decoded twice before. Note that the `urldecode()` calls have been
+ changed for a single `rawurldecode()` in order to support `+` for input
+ paths.
+ * added RouteCollection::getRoot method to retrieve the root of a
+ RouteCollection tree
+ * [BC BREAK] made RouteCollection::setParent private which could not have
+ been used anyway without creating inconsistencies
+ * [BC BREAK] RouteCollection::remove also removes a route from parent
+ collections (not only from its children)
+ * added ConfigurableRequirementsInterface that allows to disable exceptions
+ (and generate empty URLs instead) when generating a route with an invalid
+ parameter value
diff --git a/www/libs/vendor/symfony/routing/CompiledRoute.php b/www/libs/vendor/symfony/routing/CompiledRoute.php
new file mode 100644
index 00000000..03215e36
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/CompiledRoute.php
@@ -0,0 +1,157 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+/**
+ * CompiledRoutes are returned by the RouteCompiler class.
+ *
+ * @author Fabien Potencier
+ */
+class CompiledRoute implements \Serializable
+{
+ private array $variables;
+ private array $tokens;
+ private string $staticPrefix;
+ private string $regex;
+ private array $pathVariables;
+ private array $hostVariables;
+ private ?string $hostRegex;
+ private array $hostTokens;
+
+ /**
+ * @param string $staticPrefix The static prefix of the compiled route
+ * @param string $regex The regular expression to use to match this route
+ * @param array $tokens An array of tokens to use to generate URL for this route
+ * @param array $pathVariables An array of path variables
+ * @param string|null $hostRegex Host regex
+ * @param array $hostTokens Host tokens
+ * @param array $hostVariables An array of host variables
+ * @param array $variables An array of variables (variables defined in the path and in the host patterns)
+ */
+ public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, ?string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = [])
+ {
+ $this->staticPrefix = $staticPrefix;
+ $this->regex = $regex;
+ $this->tokens = $tokens;
+ $this->pathVariables = $pathVariables;
+ $this->hostRegex = $hostRegex;
+ $this->hostTokens = $hostTokens;
+ $this->hostVariables = $hostVariables;
+ $this->variables = $variables;
+ }
+
+ public function __serialize(): array
+ {
+ return [
+ 'vars' => $this->variables,
+ 'path_prefix' => $this->staticPrefix,
+ 'path_regex' => $this->regex,
+ 'path_tokens' => $this->tokens,
+ 'path_vars' => $this->pathVariables,
+ 'host_regex' => $this->hostRegex,
+ 'host_tokens' => $this->hostTokens,
+ 'host_vars' => $this->hostVariables,
+ ];
+ }
+
+ /**
+ * @internal
+ */
+ final public function serialize(): string
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->variables = $data['vars'];
+ $this->staticPrefix = $data['path_prefix'];
+ $this->regex = $data['path_regex'];
+ $this->tokens = $data['path_tokens'];
+ $this->pathVariables = $data['path_vars'];
+ $this->hostRegex = $data['host_regex'];
+ $this->hostTokens = $data['host_tokens'];
+ $this->hostVariables = $data['host_vars'];
+ }
+
+ /**
+ * @internal
+ */
+ final public function unserialize(string $serialized): void
+ {
+ $this->__unserialize(unserialize($serialized, ['allowed_classes' => false]));
+ }
+
+ /**
+ * Returns the static prefix.
+ */
+ public function getStaticPrefix(): string
+ {
+ return $this->staticPrefix;
+ }
+
+ /**
+ * Returns the regex.
+ */
+ public function getRegex(): string
+ {
+ return $this->regex;
+ }
+
+ /**
+ * Returns the host regex.
+ */
+ public function getHostRegex(): ?string
+ {
+ return $this->hostRegex;
+ }
+
+ /**
+ * Returns the tokens.
+ */
+ public function getTokens(): array
+ {
+ return $this->tokens;
+ }
+
+ /**
+ * Returns the host tokens.
+ */
+ public function getHostTokens(): array
+ {
+ return $this->hostTokens;
+ }
+
+ /**
+ * Returns the variables.
+ */
+ public function getVariables(): array
+ {
+ return $this->variables;
+ }
+
+ /**
+ * Returns the path variables.
+ */
+ public function getPathVariables(): array
+ {
+ return $this->pathVariables;
+ }
+
+ /**
+ * Returns the host variables.
+ */
+ public function getHostVariables(): array
+ {
+ return $this->hostVariables;
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php b/www/libs/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php
new file mode 100644
index 00000000..619fa67f
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/DependencyInjection/AddExpressionLanguageProvidersPass.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Registers the expression language providers.
+ *
+ * @author Fabien Potencier
+ */
+class AddExpressionLanguageProvidersPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container): void
+ {
+ if (!$container->has('router.default')) {
+ return;
+ }
+
+ $definition = $container->findDefinition('router.default');
+ foreach ($container->findTaggedServiceIds('routing.expression_language_provider', true) as $id => $attributes) {
+ $definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]);
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/www/libs/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php
new file mode 100644
index 00000000..edbecc1f
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds tagged routing.loader services to routing.resolver service.
+ *
+ * @author Fabien Potencier
+ */
+class RoutingResolverPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (false === $container->hasDefinition('routing.resolver')) {
+ return;
+ }
+
+ $definition = $container->getDefinition('routing.resolver');
+
+ foreach ($this->findAndSortTaggedServices('routing.loader', $container) as $id) {
+ $definition->addMethodCall('addLoader', [new Reference($id)]);
+ }
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/ExceptionInterface.php b/www/libs/vendor/symfony/routing/Exception/ExceptionInterface.php
new file mode 100644
index 00000000..22e72b16
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * ExceptionInterface.
+ *
+ * @author Alexandre Salomé
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/InvalidArgumentException.php b/www/libs/vendor/symfony/routing/Exception/InvalidArgumentException.php
new file mode 100644
index 00000000..950b9b15
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/InvalidArgumentException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/InvalidParameterException.php b/www/libs/vendor/symfony/routing/Exception/InvalidParameterException.php
new file mode 100644
index 00000000..94d841f4
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/InvalidParameterException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a parameter is not valid.
+ *
+ * @author Alexandre Salomé
+ */
+class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/www/libs/vendor/symfony/routing/Exception/MethodNotAllowedException.php
new file mode 100644
index 00000000..c96ae9b1
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/MethodNotAllowedException.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * The resource was found but the request method is not allowed.
+ *
+ * This exception should trigger an HTTP 405 response in your application code.
+ *
+ * @author Kris Wallsmith
+ */
+class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface
+{
+ protected $allowedMethods = [];
+
+ /**
+ * @param string[] $allowedMethods
+ */
+ public function __construct(array $allowedMethods, string $message = '', int $code = 0, ?\Throwable $previous = null)
+ {
+ $this->allowedMethods = array_map('strtoupper', $allowedMethods);
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * Gets the allowed HTTP methods.
+ *
+ * @return string[]
+ */
+ public function getAllowedMethods(): array
+ {
+ return $this->allowedMethods;
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/www/libs/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php
new file mode 100644
index 00000000..72d063ab
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a route cannot be generated because of missing
+ * mandatory parameters.
+ *
+ * @author Alexandre Salomé
+ */
+class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface
+{
+ private string $routeName = '';
+ private array $missingParameters = [];
+
+ /**
+ * @param string[] $missingParameters
+ * @param int $code
+ */
+ public function __construct(string $routeName = '', $missingParameters = null, $code = 0, ?\Throwable $previous = null)
+ {
+ if (\is_array($missingParameters)) {
+ $this->routeName = $routeName;
+ $this->missingParameters = $missingParameters;
+ $message = sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', $missingParameters), $routeName);
+ } else {
+ trigger_deprecation('symfony/routing', '6.1', 'Construction of "%s" with an exception message is deprecated, provide the route name and an array of missing parameters instead.', __CLASS__);
+ $message = $routeName;
+ $previous = $code instanceof \Throwable ? $code : null;
+ $code = (int) $missingParameters;
+ }
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getMissingParameters(): array
+ {
+ return $this->missingParameters;
+ }
+
+ public function getRouteName(): string
+ {
+ return $this->routeName;
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/NoConfigurationException.php b/www/libs/vendor/symfony/routing/Exception/NoConfigurationException.php
new file mode 100644
index 00000000..333bc743
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/NoConfigurationException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when no routes are configured.
+ *
+ * @author Yonel Ceruto
+ */
+class NoConfigurationException extends ResourceNotFoundException
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/www/libs/vendor/symfony/routing/Exception/ResourceNotFoundException.php
new file mode 100644
index 00000000..ccbca152
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/ResourceNotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * The resource was not found.
+ *
+ * This exception should trigger an HTTP 404 response in your application code.
+ *
+ * @author Kris Wallsmith
+ */
+class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/RouteCircularReferenceException.php b/www/libs/vendor/symfony/routing/Exception/RouteCircularReferenceException.php
new file mode 100644
index 00000000..841e3598
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/RouteCircularReferenceException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+class RouteCircularReferenceException extends RuntimeException
+{
+ public function __construct(string $routeId, array $path)
+ {
+ parent::__construct(sprintf('Circular reference detected for route "%s", path: "%s".', $routeId, implode(' -> ', $path)));
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/RouteNotFoundException.php b/www/libs/vendor/symfony/routing/Exception/RouteNotFoundException.php
new file mode 100644
index 00000000..24ab0b44
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/RouteNotFoundException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a route does not exist.
+ *
+ * @author Alexandre Salomé
+ */
+class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Exception/RuntimeException.php b/www/libs/vendor/symfony/routing/Exception/RuntimeException.php
new file mode 100644
index 00000000..48da62ec
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Exception/RuntimeException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/www/libs/vendor/symfony/routing/Generator/CompiledUrlGenerator.php b/www/libs/vendor/symfony/routing/Generator/CompiledUrlGenerator.php
new file mode 100644
index 00000000..de209cdc
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Generator/CompiledUrlGenerator.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * Generates URLs based on rules dumped by CompiledUrlGeneratorDumper.
+ */
+class CompiledUrlGenerator extends UrlGenerator
+{
+ private array $compiledRoutes = [];
+ private ?string $defaultLocale;
+
+ public function __construct(array $compiledRoutes, RequestContext $context, ?LoggerInterface $logger = null, ?string $defaultLocale = null)
+ {
+ $this->compiledRoutes = $compiledRoutes;
+ $this->context = $context;
+ $this->logger = $logger;
+ $this->defaultLocale = $defaultLocale;
+ }
+
+ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
+ {
+ $locale = $parameters['_locale']
+ ?? $this->context->getParameter('_locale')
+ ?: $this->defaultLocale;
+
+ if (null !== $locale) {
+ do {
+ if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
+ $name .= '.'.$locale;
+ break;
+ }
+ } while (false !== $locale = strstr($locale, '_', true));
+ }
+
+ if (!isset($this->compiledRoutes[$name])) {
+ throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
+ }
+
+ [$variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes, $deprecations] = $this->compiledRoutes[$name] + [6 => []];
+
+ foreach ($deprecations as $deprecation) {
+ trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
+ }
+
+ if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
+ if (!\in_array('_locale', $variables, true)) {
+ unset($parameters['_locale']);
+ } elseif (!isset($parameters['_locale'])) {
+ $parameters['_locale'] = $defaults['_locale'];
+ }
+ }
+
+ return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
+ }
+}
diff --git a/www/libs/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/www/libs/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php
new file mode 100644
index 00000000..cbbbf045
--- /dev/null
+++ b/www/libs/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+/**
+ * ConfigurableRequirementsInterface must be implemented by URL generators that
+ * can be configured whether an exception should be generated when the parameters
+ * do not match the requirements. It is also possible to disable the requirements
+ * check for URL generation completely.
+ *
+ * The possible configurations and use-cases:
+ * - setStrictRequirements(true): Throw an exception for mismatching requirements. This
+ * is mostly useful in development environment.
+ * - setStrictRequirements(false): Don't throw an exception but return an empty string as URL for
+ * mismatching requirements and log the problem. Useful when you cannot control all
+ * params because they come from third party libs but don't want to have a 404 in
+ * production environment. It should log the mismatch so one can review it.
+ * - setStrictRequirements(null): Return the URL with the given parameters without
+ * checking the requirements at all. When generating a URL you should either trust
+ * your params or you validated them beforehand because otherwise it would break your
+ * link anyway. So in production environment you should know that params always pass
+ * the requirements. Thus this option allows to disable the check on URL generation for
+ * performance reasons (saving a preg_match for each requirement every time a URL is
+ * generated).
+ *
+ * @author Fabien Potencier