MessengerTransportListener.php 0000644 00000002636 15124625734 0012650 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Mailer\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
use Symfony\Component\Mime\Message;
/**
* Allows messages to be sent to specific Messenger transports via the "X-Bus-Transport" MIME header.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class MessengerTransportListener implements EventSubscriberInterface
{
public function onMessage(MessageEvent $event): void
{
if (!$event->isQueued()) {
return;
}
$message = $event->getMessage();
if (!$message instanceof Message || !$message->getHeaders()->has('X-Bus-Transport')) {
return;
}
$names = $message->getHeaders()->get('X-Bus-Transport')->getBody();
$names = array_map('trim', explode(',', $names));
$event->addStamp(new TransportNamesStamp($names));
$message->getHeaders()->remove('X-Bus-Transport');
}
public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => 'onMessage',
];
}
}
MessageLoggerListener.php 0000644 00000002353 15124625734 0011523 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Mailer\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mailer\Event\MessageEvents;
use Symfony\Contracts\Service\ResetInterface;
/**
* Logs Messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageLoggerListener implements EventSubscriberInterface, ResetInterface
{
private MessageEvents $events;
public function __construct()
{
$this->events = new MessageEvents();
}
/**
* @return void
*/
public function reset()
{
$this->events = new MessageEvents();
}
public function onMessage(MessageEvent $event): void
{
$this->events->add($event);
}
public function getEvents(): MessageEvents
{
return $this->events;
}
public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => ['onMessage', -255],
];
}
}
MessageListener.php 0000644 00000007556 15124625734 0010375 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Mailer\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
use Symfony\Component\Mailer\Exception\RuntimeException;
use Symfony\Component\Mime\BodyRendererInterface;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\Header\MailboxListHeader;
use Symfony\Component\Mime\Message;
/**
* Manipulates the headers and the body of a Message.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageListener implements EventSubscriberInterface
{
public const HEADER_SET_IF_EMPTY = 1;
public const HEADER_ADD = 2;
public const HEADER_REPLACE = 3;
public const DEFAULT_RULES = [
'from' => self::HEADER_SET_IF_EMPTY,
'return-path' => self::HEADER_SET_IF_EMPTY,
'reply-to' => self::HEADER_ADD,
'to' => self::HEADER_SET_IF_EMPTY,
'cc' => self::HEADER_ADD,
'bcc' => self::HEADER_ADD,
];
private ?Headers $headers;
private array $headerRules = [];
private ?BodyRendererInterface $renderer;
public function __construct(?Headers $headers = null, ?BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
{
$this->headers = $headers;
$this->renderer = $renderer;
foreach ($headerRules as $headerName => $rule) {
$this->addHeaderRule($headerName, $rule);
}
}
public function addHeaderRule(string $headerName, int $rule): void
{
if ($rule < 1 || $rule > 3) {
throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule));
}
$this->headerRules[strtolower($headerName)] = $rule;
}
public function onMessage(MessageEvent $event): void
{
$message = $event->getMessage();
if (!$message instanceof Message) {
return;
}
$this->setHeaders($message);
$this->renderMessage($message);
}
private function setHeaders(Message $message): void
{
if (!$this->headers) {
return;
}
$headers = $message->getHeaders();
foreach ($this->headers->all() as $name => $header) {
if (!$headers->has($name)) {
$headers->add($header);
continue;
}
switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) {
case self::HEADER_SET_IF_EMPTY:
break;
case self::HEADER_REPLACE:
$headers->remove($name);
$headers->add($header);
break;
case self::HEADER_ADD:
if (!Headers::isUniqueHeader($name)) {
$headers->add($header);
break;
}
$h = $headers->get($name);
if (!$h instanceof MailboxListHeader) {
throw new RuntimeException(sprintf('Unable to set header "%s".', $name));
}
Headers::checkHeaderClass($header);
foreach ($header->getAddresses() as $address) {
$h->addAddress($address);
}
}
}
}
private function renderMessage(Message $message): void
{
if (!$this->renderer) {
return;
}
$this->renderer->render($message);
}
public static function getSubscribedEvents(): array
{
return [
MessageEvent::class => 'onMessage',
];
}
}
EnvelopeListener.php 0000644 00000003672 15124625734 0010561 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Mailer\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Message;
/**
* Manipulates the Envelope of a Message.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EnvelopeListener implements EventSubscriberInterface
{
private ?Address $sender = null;
/**
* @var Address[]|null
*/
private ?array $recipients = null;
/**
* @param array<Address|string> $recipients
*/
public function __construct(Address|string|null $sender = null, ?array $recipients = null)
{
if (null !== $sender) {
$this->sender = Address::create($sender);
}
if (null !== $recipients) {
$this->recipients = Address::createArray($recipients);
}
}
public function onMessage(MessageEvent $event): void
{
if ($this->sender) {
$event->getEnvelope()->setSender($this->sender);
$message = $event->getMessage();
if ($message instanceof Message) {
if (!$message->getHeaders()->has('Sender') && !$message->getHeaders()->has('From')) {
$message->getHeaders()->addMailboxHeader('Sender', $this->sender);
}
}
}
if ($this->recipients) {
$event->getEnvelope()->setRecipients($this->recipients);
}
}
public static function getSubscribedEvents(): array
{
return [
// should be the last one to allow header changes by other listeners first
MessageEvent::class => ['onMessage', -255],
];
}
}
DisallowRobotsIndexingListener.php 0000644 00000002114 15152054624 0013422 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Ensures that the application is not indexed by search engines.
*
* @author Gary PEGEOT <garypegeot@gmail.com>
*/
class DisallowRobotsIndexingListener implements EventSubscriberInterface
{
private const HEADER_NAME = 'X-Robots-Tag';
public function onResponse(ResponseEvent $event): void
{
if (!$event->getResponse()->headers->has(static::HEADER_NAME)) {
$event->getResponse()->headers->set(static::HEADER_NAME, 'noindex');
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['onResponse', -255],
];
}
}
CacheAttributeListener.php 0000644 00000015400 15152054624 0011656 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\Cache;
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Handles HTTP cache headers configured via the Cache attribute.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CacheAttributeListener implements EventSubscriberInterface
{
/**
* @var \SplObjectStorage<Request, \DateTimeInterface>
*/
private \SplObjectStorage $lastModified;
/**
* @var \SplObjectStorage<Request, string>
*/
private \SplObjectStorage $etags;
public function __construct(
private ?ExpressionLanguage $expressionLanguage = null,
) {
$this->lastModified = new \SplObjectStorage();
$this->etags = new \SplObjectStorage();
}
/**
* Handles HTTP validation headers.
*
* @return void
*/
public function onKernelControllerArguments(ControllerArgumentsEvent $event)
{
$request = $event->getRequest();
if (!\is_array($attributes = $request->attributes->get('_cache') ?? $event->getAttributes()[Cache::class] ?? null)) {
return;
}
$request->attributes->set('_cache', $attributes);
$response = null;
$lastModified = null;
$etag = null;
/** @var Cache[] $attributes */
foreach ($attributes as $cache) {
if (null !== $cache->lastModified) {
$lastModified = $this->getExpressionLanguage()->evaluate($cache->lastModified, array_merge($request->attributes->all(), $event->getNamedArguments()));
($response ??= new Response())->setLastModified($lastModified);
}
if (null !== $cache->etag) {
$etag = hash('sha256', $this->getExpressionLanguage()->evaluate($cache->etag, array_merge($request->attributes->all(), $event->getNamedArguments())));
($response ??= new Response())->setEtag($etag);
}
}
if ($response?->isNotModified($request)) {
$event->setController(static fn () => $response);
$event->stopPropagation();
return;
}
if (null !== $etag) {
$this->etags[$request] = $etag;
}
if (null !== $lastModified) {
$this->lastModified[$request] = $lastModified;
}
}
/**
* Modifies the response to apply HTTP cache headers when needed.
*
* @return void
*/
public function onKernelResponse(ResponseEvent $event)
{
$request = $event->getRequest();
/** @var Cache[] $attributes */
if (!\is_array($attributes = $request->attributes->get('_cache'))) {
return;
}
$response = $event->getResponse();
// http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
if (!\in_array($response->getStatusCode(), [200, 203, 300, 301, 302, 304, 404, 410])) {
unset($this->lastModified[$request]);
unset($this->etags[$request]);
return;
}
if (isset($this->lastModified[$request]) && !$response->headers->has('Last-Modified')) {
$response->setLastModified($this->lastModified[$request]);
}
if (isset($this->etags[$request]) && !$response->headers->has('Etag')) {
$response->setEtag($this->etags[$request]);
}
unset($this->lastModified[$request]);
unset($this->etags[$request]);
$hasVary = $response->headers->has('Vary');
foreach (array_reverse($attributes) as $cache) {
if (null !== $cache->smaxage && !$response->headers->hasCacheControlDirective('s-maxage')) {
$response->setSharedMaxAge($this->toSeconds($cache->smaxage));
}
if ($cache->mustRevalidate) {
$response->headers->addCacheControlDirective('must-revalidate');
}
if (null !== $cache->maxage && !$response->headers->hasCacheControlDirective('max-age')) {
$response->setMaxAge($this->toSeconds($cache->maxage));
}
if (null !== $cache->maxStale && !$response->headers->hasCacheControlDirective('max-stale')) {
$response->headers->addCacheControlDirective('max-stale', $this->toSeconds($cache->maxStale));
}
if (null !== $cache->staleWhileRevalidate && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) {
$response->headers->addCacheControlDirective('stale-while-revalidate', $this->toSeconds($cache->staleWhileRevalidate));
}
if (null !== $cache->staleIfError && !$response->headers->hasCacheControlDirective('stale-if-error')) {
$response->headers->addCacheControlDirective('stale-if-error', $this->toSeconds($cache->staleIfError));
}
if (null !== $cache->expires && !$response->headers->has('Expires')) {
$response->setExpires(new \DateTimeImmutable('@'.strtotime($cache->expires, time())));
}
if (!$hasVary && $cache->vary) {
$response->setVary($cache->vary, false);
}
}
foreach ($attributes as $cache) {
if (true === $cache->public) {
$response->setPublic();
}
if (false === $cache->public) {
$response->setPrivate();
}
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 10],
KernelEvents::RESPONSE => ['onKernelResponse', -10],
];
}
private function getExpressionLanguage(): ExpressionLanguage
{
return $this->expressionLanguage ??= class_exists(ExpressionLanguage::class)
? new ExpressionLanguage()
: throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
}
private function toSeconds(int|string $time): int
{
if (!is_numeric($time)) {
$now = time();
$time = strtotime($time, $now) - $now;
}
return $time;
}
}
DebugHandlersListener.php 0000644 00000010245 15152054624 0011500 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Sets an exception handler.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*
* @internal
*/
class DebugHandlersListener implements EventSubscriberInterface
{
private string|object|null $earlyHandler;
private ?\Closure $exceptionHandler;
private bool $webMode;
private bool $firstCall = true;
private bool $hasTerminatedWithException = false;
/**
* @param bool $webMode
* @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception
*/
public function __construct(?callable $exceptionHandler = null, bool|LoggerInterface|null $webMode = null)
{
if ($webMode instanceof LoggerInterface) {
// BC with Symfony 5
$webMode = null;
}
$handler = set_exception_handler('is_int');
$this->earlyHandler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler();
$this->exceptionHandler = null === $exceptionHandler ? null : $exceptionHandler(...);
$this->webMode = $webMode ?? !\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true);
}
/**
* Configures the error handler.
*/
public function configure(?object $event = null): void
{
if ($event instanceof ConsoleEvent && $this->webMode) {
return;
}
if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) {
return;
}
$this->firstCall = $this->hasTerminatedWithException = false;
if (!$this->exceptionHandler) {
if ($event instanceof KernelEvent) {
if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
$request = $event->getRequest();
$hasRun = &$this->hasTerminatedWithException;
$this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
if ($hasRun) {
throw $e;
}
$hasRun = true;
$kernel->terminateWithException($e, $request);
};
}
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
$output = $event->getOutput();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
$app->renderThrowable($e, $output);
};
}
}
if ($this->exceptionHandler) {
$handler = set_exception_handler(static fn () => null);
$handler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler();
if (!$handler instanceof ErrorHandler) {
$handler = $this->earlyHandler;
}
if ($handler instanceof ErrorHandler) {
$handler->setExceptionHandler($this->exceptionHandler);
}
$this->exceptionHandler = null;
}
}
public static function getSubscribedEvents(): array
{
$events = [KernelEvents::REQUEST => ['configure', 2048]];
if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
}
return $events;
}
}
LocaleListener.php 0000644 00000006202 15152054624 0010166 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the locale based on the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class LocaleListener implements EventSubscriberInterface
{
private ?RequestContextAwareInterface $router;
private string $defaultLocale;
private RequestStack $requestStack;
private bool $useAcceptLanguageHeader;
private array $enabledLocales;
public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', ?RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = [])
{
$this->defaultLocale = $defaultLocale;
$this->requestStack = $requestStack;
$this->router = $router;
$this->useAcceptLanguageHeader = $useAcceptLanguageHeader;
$this->enabledLocales = $enabledLocales;
}
public function setDefaultLocale(KernelEvent $event): void
{
$event->getRequest()->setDefaultLocale($this->defaultLocale);
}
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$this->setLocale($request);
$this->setRouterContext($request);
}
public function onKernelFinishRequest(FinishRequestEvent $event): void
{
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
$this->setRouterContext($parentRequest);
}
}
private function setLocale(Request $request): void
{
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
} elseif ($this->useAcceptLanguageHeader) {
if ($request->getLanguages() && $preferredLanguage = $request->getPreferredLanguage($this->enabledLocales)) {
$request->setLocale($preferredLanguage);
}
$request->attributes->set('_vary_by_language', true);
}
}
private function setRouterContext(Request $request): void
{
$this->router?->getContext()->setParameter('_locale', $request->getLocale());
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['setDefaultLocale', 100],
// must be registered after the Router to have access to the _locale
['onKernelRequest', 16],
],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
];
}
}
StreamedResponseListener.php 0000644 00000002561 15152054624 0012256 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
trigger_deprecation('symfony/http-kernel', '6.1', 'The "%s" class is deprecated.', StreamedResponseListener::class);
/**
* StreamedResponseListener is responsible for sending the Response
* to the client.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*
* @deprecated since Symfony 6.1
*/
class StreamedResponseListener implements EventSubscriberInterface
{
/**
* Filters the Response.
*/
public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$response = $event->getResponse();
if ($response instanceof StreamedResponse) {
$response->send();
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['onKernelResponse', -1024],
];
}
}
ValidateRequestListener.php 0000644 00000002225 15152054624 0012072 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Validates Requests.
*
* @author Magnus Nordlander <magnus@fervo.se>
*
* @final
*/
class ValidateRequestListener implements EventSubscriberInterface
{
/**
* Performs the validation.
*/
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if ($request::getTrustedProxies()) {
$request->getClientIps();
}
$request->getHost();
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['onKernelRequest', 256],
],
];
}
}
LocaleAwareListener.php 0000644 00000004621 15152054624 0011151 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Translation\LocaleAwareInterface;
/**
* Pass the current locale to the provided services.
*
* @author Pierre Bobiet <pierrebobiet@gmail.com>
*/
class LocaleAwareListener implements EventSubscriberInterface
{
private iterable $localeAwareServices;
private RequestStack $requestStack;
/**
* @param iterable<mixed, LocaleAwareInterface> $localeAwareServices
*/
public function __construct(iterable $localeAwareServices, RequestStack $requestStack)
{
$this->localeAwareServices = $localeAwareServices;
$this->requestStack = $requestStack;
}
public function onKernelRequest(RequestEvent $event): void
{
$this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale());
}
public function onKernelFinishRequest(FinishRequestEvent $event): void
{
if (null === $parentRequest = $this->requestStack->getParentRequest()) {
foreach ($this->localeAwareServices as $service) {
$service->setLocale($event->getRequest()->getDefaultLocale());
}
return;
}
$this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale());
}
public static function getSubscribedEvents(): array
{
return [
// must be registered after the Locale listener
KernelEvents::REQUEST => [['onKernelRequest', 15]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]],
];
}
private function setLocale(string $locale, string $defaultLocale): void
{
foreach ($this->localeAwareServices as $service) {
try {
$service->setLocale($locale);
} catch (\InvalidArgumentException) {
$service->setLocale($defaultLocale);
}
}
}
}
ErrorListener.php 0000644 00000005501 15152054624 0010061 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @author James Halsall <james.t.halsall@googlemail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ErrorListener implements EventSubscriberInterface
{
private ?LoggerInterface $logger;
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* @return void
*/
public function onConsoleError(ConsoleErrorEvent $event)
{
if (null === $this->logger) {
return;
}
$error = $event->getError();
if (!$inputString = $this->getInputString($event)) {
$this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
return;
}
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
/**
* @return void
*/
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {
return;
}
$exitCode = $event->getExitCode();
if (0 === $exitCode) {
return;
}
if (!$inputString = $this->getInputString($event)) {
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
return;
}
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents(): array
{
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
];
}
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand()?->getName();
$input = $event->getInput();
if ($input instanceof \Stringable) {
if ($commandName) {
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}
return (string) $input;
}
return $commandName;
}
}
DumpListener.php 0000644 00000003703 15152054624 0007677 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
use Symfony\Component\VarDumper\Server\Connection;
use Symfony\Component\VarDumper\VarDumper;
/**
* Configures dump() handler.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DumpListener implements EventSubscriberInterface
{
private ClonerInterface $cloner;
private DataDumperInterface $dumper;
private ?Connection $connection;
public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, ?Connection $connection = null)
{
$this->cloner = $cloner;
$this->dumper = $dumper;
$this->connection = $connection;
}
/**
* @return void
*/
public function configure()
{
$cloner = $this->cloner;
$dumper = $this->dumper;
$connection = $this->connection;
VarDumper::setHandler(static function ($var, ?string $label = null) use ($cloner, $dumper, $connection) {
$data = $cloner->cloneVar($var);
if (null !== $label) {
$data = $data->withContext(['label' => $label]);
}
if (!$connection || !$connection->write($data)) {
$dumper->dump($data);
}
});
}
public static function getSubscribedEvents(): array
{
if (!class_exists(ConsoleEvents::class)) {
return [];
}
// Register early to have a working dump() as early as possible
return [ConsoleEvents::COMMAND => ['configure', 1024]];
}
}
SessionListener.php 0000644 00000001365 15152054624 0010417 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* Sets the session in the request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class SessionListener extends AbstractSessionListener
{
protected function getSession(): ?SessionInterface
{
if ($this->container->has('session_factory')) {
return $this->container->get('session_factory')->createSession();
}
return null;
}
}
FragmentListener.php 0000644 00000005745 15152054624 0010545 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Handles content fragments represented by special URIs.
*
* All URL paths starting with /_fragment are handled as
* content fragments by this listener.
*
* Throws an AccessDeniedHttpException exception if the request
* is not signed or if it is not an internal sub-request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class FragmentListener implements EventSubscriberInterface
{
private UriSigner $signer;
private string $fragmentPath;
/**
* @param string $fragmentPath The path that triggers this listener
*/
public function __construct(UriSigner $signer, string $fragmentPath = '/_fragment')
{
$this->signer = $signer;
$this->fragmentPath = $fragmentPath;
}
/**
* Fixes request attributes when the path is '/_fragment'.
*
* @throws AccessDeniedHttpException if the request does not come from a trusted IP
*/
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) {
return;
}
if ($request->attributes->has('_controller')) {
// Is a sub-request: no need to parse _path but it should still be removed from query parameters as below.
$request->query->remove('_path');
return;
}
if ($event->isMainRequest()) {
$this->validateRequest($request);
}
parse_str($request->query->get('_path', ''), $attributes);
$attributes['_check_controller_is_allowed'] = -1; // @deprecated, switch to true in Symfony 7
$request->attributes->add($attributes);
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes));
$request->query->remove('_path');
}
protected function validateRequest(Request $request): void
{
// is the Request safe?
if (!$request->isMethodSafe()) {
throw new AccessDeniedHttpException();
}
// is the Request signed?
if ($this->signer->checkRequest($request)) {
return;
}
throw new AccessDeniedHttpException();
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 48]],
];
}
}
ProfilerListener.php 0000644 00000012211 15152054624 0010546 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
/**
* ProfilerListener collects data for the current request by listening to the kernel events.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ProfilerListener implements EventSubscriberInterface
{
private Profiler $profiler;
private ?RequestMatcherInterface $matcher;
private bool $onlyException;
private bool $onlyMainRequests;
private ?\Throwable $exception = null;
/** @var \SplObjectStorage<Request, Profile> */
private \SplObjectStorage $profiles;
private RequestStack $requestStack;
private ?string $collectParameter;
/** @var \SplObjectStorage<Request, Request|null> */
private \SplObjectStorage $parents;
/**
* @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise
* @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise
*/
public function __construct(Profiler $profiler, RequestStack $requestStack, ?RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMainRequests = false, ?string $collectParameter = null)
{
$this->profiler = $profiler;
$this->matcher = $matcher;
$this->onlyException = $onlyException;
$this->onlyMainRequests = $onlyMainRequests;
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
$this->requestStack = $requestStack;
$this->collectParameter = $collectParameter;
}
/**
* Handles the onKernelException event.
*/
public function onKernelException(ExceptionEvent $event): void
{
if ($this->onlyMainRequests && !$event->isMainRequest()) {
return;
}
$this->exception = $event->getThrowable();
}
/**
* Handles the onKernelResponse event.
*/
public function onKernelResponse(ResponseEvent $event): void
{
if ($this->onlyMainRequests && !$event->isMainRequest()) {
return;
}
if ($this->onlyException && null === $this->exception) {
return;
}
$request = $event->getRequest();
if (null !== $this->collectParameter && null !== $collectParameterValue = $request->get($this->collectParameter)) {
true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOL) ? $this->profiler->enable() : $this->profiler->disable();
}
$exception = $this->exception;
$this->exception = null;
if (null !== $this->matcher && !$this->matcher->matches($request)) {
return;
}
$session = $request->hasPreviousSession() ? $request->getSession() : null;
if ($session instanceof Session) {
$usageIndexValue = $usageIndexReference = &$session->getUsageIndex();
$usageIndexReference = \PHP_INT_MIN;
}
try {
if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) {
return;
}
} finally {
if ($session instanceof Session) {
$usageIndexReference = $usageIndexValue;
}
}
$this->profiles[$request] = $profile;
$this->parents[$request] = $this->requestStack->getParentRequest();
}
public function onKernelTerminate(TerminateEvent $event): void
{
// attach children to parents
foreach ($this->profiles as $request) {
if (null !== $parentRequest = $this->parents[$request]) {
if (isset($this->profiles[$parentRequest])) {
$this->profiles[$parentRequest]->addChild($this->profiles[$request]);
}
}
}
// save profiles
foreach ($this->profiles as $request) {
$this->profiler->saveProfile($this->profiles[$request]);
}
$this->profiles = new \SplObjectStorage();
$this->parents = new \SplObjectStorage();
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => ['onKernelResponse', -100],
KernelEvents::EXCEPTION => ['onKernelException', 0],
KernelEvents::TERMINATE => ['onKernelTerminate', -1024],
];
}
}
RouterListener.php 0000644 00000014517 15152054624 0010257 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\NoConfigurationException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* Initializes the context from the request and sets request attributes based on a matching route.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @final
*/
class RouterListener implements EventSubscriberInterface
{
private RequestMatcherInterface|UrlMatcherInterface $matcher;
private RequestContext $context;
private ?LoggerInterface $logger;
private RequestStack $requestStack;
private ?string $projectDir;
private bool $debug;
/**
* @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface)
*
* @throws \InvalidArgumentException
*/
public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher, RequestStack $requestStack, ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $projectDir = null, bool $debug = true)
{
if (null === $context && !$matcher instanceof RequestContextAwareInterface) {
throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.');
}
$this->matcher = $matcher;
$this->context = $context ?? $matcher->getContext();
$this->requestStack = $requestStack;
$this->logger = $logger;
$this->projectDir = $projectDir;
$this->debug = $debug;
}
private function setCurrentRequest(?Request $request): void
{
if (null !== $request) {
try {
$this->context->fromRequest($request);
} catch (\UnexpectedValueException $e) {
throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode());
}
}
}
/**
* After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator
* operates on the correct context again.
*/
public function onKernelFinishRequest(): void
{
$this->setCurrentRequest($this->requestStack->getParentRequest());
}
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$this->setCurrentRequest($request);
if ($request->attributes->has('_controller')) {
// routing is already done
return;
}
// add attributes based on the request (routing)
try {
// matching a request is more powerful than matching a URL path + context, so try that first
if ($this->matcher instanceof RequestMatcherInterface) {
$parameters = $this->matcher->matchRequest($request);
} else {
$parameters = $this->matcher->match($request->getPathInfo());
}
$this->logger?->info('Matched route "{route}".', [
'route' => $parameters['_route'] ?? 'n/a',
'route_parameters' => $parameters,
'request_uri' => $request->getUri(),
'method' => $request->getMethod(),
]);
$request->attributes->add($parameters);
unset($parameters['_route'], $parameters['_controller']);
$request->attributes->set('_route_params', $parameters);
} catch (ResourceNotFoundException $e) {
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUriForPath($request->getPathInfo()));
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message, $e);
} catch (MethodNotAllowedException $e) {
$message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getUriForPath($request->getPathInfo()), implode(', ', $e->getAllowedMethods()));
throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
}
}
public function onKernelException(ExceptionEvent $event): void
{
if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) {
return;
}
if ($e->getPrevious() instanceof NoConfigurationException) {
$event->setResponse($this->createWelcomeResponse());
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 32]],
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]],
KernelEvents::EXCEPTION => ['onKernelException', -64],
];
}
private function createWelcomeResponse(): Response
{
$version = Kernel::VERSION;
$projectDir = realpath((string) $this->projectDir).\DIRECTORY_SEPARATOR;
$docVersion = substr(Kernel::VERSION, 0, 3);
ob_start();
include \dirname(__DIR__).'/Resources/welcome.html.php';
return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND);
}
}
AbstractSessionListener.php 0000644 00000030715 15152054624 0012104 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\SessionUtils;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Service\ResetInterface;
/**
* Sets the session onto the request on the "kernel.request" event and saves
* it on the "kernel.response" event.
*
* In addition, if the session has been started it overrides the Cache-Control
* header in such a way that all caching is disabled in that case.
* If you have a scenario where caching responses with session information in
* them makes sense, you can disable this behaviour by setting the header
* AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*/
abstract class AbstractSessionListener implements EventSubscriberInterface, ResetInterface
{
public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl';
/**
* @internal
*/
protected ?ContainerInterface $container;
private bool $debug;
/**
* @var array<string, mixed>
*/
private array $sessionOptions;
/**
* @internal
*/
public function __construct(?ContainerInterface $container = null, bool $debug = false, array $sessionOptions = [])
{
$this->container = $container;
$this->debug = $debug;
$this->sessionOptions = $sessionOptions;
}
/**
* @internal
*/
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if (!$request->hasSession()) {
$request->setSessionFactory(function () use ($request) {
// Prevent calling `$this->getSession()` twice in case the Request (and the below factory) is cloned
static $sess;
if (!$sess) {
$sess = $this->getSession();
$request->setSession($sess);
/*
* For supporting sessions in php runtime with runners like roadrunner or swoole, the session
* cookie needs to be read from the cookie bag and set on the session storage.
*
* Do not set it when a native php session is active.
*/
if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) {
$sessionId = $sess->getId() ?: $request->cookies->get($sess->getName(), '');
$sess->setId($sessionId);
}
}
return $sess;
});
}
}
/**
* @internal
*/
public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) {
return;
}
$response = $event->getResponse();
$autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER);
// Always remove the internal header if present
$response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER);
if (!$event->getRequest()->hasSession(true)) {
return;
}
$session = $event->getRequest()->getSession();
if ($session->isStarted()) {
/*
* Saves the session, in case it is still open, before sending the response/headers.
*
* This ensures several things in case the developer did not save the session explicitly:
*
* * If a session save handler without locking is used, it ensures the data is available
* on the next request, e.g. after a redirect. PHPs auto-save at script end via
* session_register_shutdown is executed after fastcgi_finish_request. So in this case
* the data could be missing the next request because it might not be saved the moment
* the new request is processed.
* * A locking save handler (e.g. the native 'files') circumvents concurrency problems like
* the one above. But by saving the session before long-running things in the terminate event,
* we ensure the session is not blocked longer than needed.
* * When regenerating the session ID no locking is involved in PHPs session design. See
* https://bugs.php.net/61470 for a discussion. So in this case, the session must
* be saved anyway before sending the headers with the new session ID. 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.
*
* This listener should be executed as one of the last listeners, so that previous listeners
* can still operate on the open session. This prevents the overhead of restarting it.
* Listeners after closing the session can still work with the session as usual because
* Symfonys session implementation starts the session on demand. So writing to it after
* it is saved will just restart it.
*/
$session->save();
/*
* For supporting sessions in php runtime with runners like roadrunner or swoole the session
* cookie need to be written on the response object and should not be written by PHP itself.
*/
$sessionName = $session->getName();
$sessionId = $session->getId();
$sessionOptions = $this->getSessionOptions($this->sessionOptions);
$sessionCookiePath = $sessionOptions['cookie_path'] ?? '/';
$sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null;
$sessionCookieSecure = $sessionOptions['cookie_secure'] ?? false;
$sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? true;
$sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX;
$sessionUseCookies = $sessionOptions['use_cookies'] ?? true;
SessionUtils::popSessionCookie($sessionName, $sessionId);
if ($sessionUseCookies) {
$request = $event->getRequest();
$requestSessionCookieId = $request->cookies->get($sessionName);
$isSessionEmpty = ($session instanceof Session ? $session->isEmpty() : !$session->all()) && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions
if ($requestSessionCookieId && $isSessionEmpty) {
// PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument
// which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy
// when the session gets invalidated (for example on logout) so we must handle this case here too
// otherwise we would send two Set-Cookie headers back with the response
SessionUtils::popSessionCookie($sessionName, 'deleted');
$response->headers->clearCookie(
$sessionName,
$sessionCookiePath,
$sessionCookieDomain,
$sessionCookieSecure,
$sessionCookieHttpOnly,
$sessionCookieSameSite
);
} elseif ($sessionId !== $requestSessionCookieId && !$isSessionEmpty) {
$expire = 0;
$lifetime = $sessionOptions['cookie_lifetime'] ?? null;
if ($lifetime) {
$expire = time() + $lifetime;
}
$response->headers->setCookie(
Cookie::create(
$sessionName,
$sessionId,
$expire,
$sessionCookiePath,
$sessionCookieDomain,
$sessionCookieSecure,
$sessionCookieHttpOnly,
false,
$sessionCookieSameSite
)
);
}
}
}
if ($session instanceof Session ? 0 === $session->getUsageIndex() : !$session->isStarted()) {
return;
}
if ($autoCacheControl) {
$maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge();
$response
->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds'))
->setPrivate()
->setMaxAge($maxAge)
->headers->addCacheControlDirective('must-revalidate');
}
if (!$event->getRequest()->attributes->get('_stateless', false)) {
return;
}
if ($this->debug) {
throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
}
if ($this->container->has('logger')) {
$this->container->get('logger')->warning('Session was used while the request was declared stateless.');
}
}
/**
* @internal
*/
public function onSessionUsage(): void
{
if (!$this->debug) {
return;
}
if ($this->container?->has('session_collector')) {
$this->container->get('session_collector')();
}
if (!$requestStack = $this->container?->has('request_stack') ? $this->container->get('request_stack') : null) {
return;
}
$stateless = false;
$clonedRequestStack = clone $requestStack;
while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) {
$stateless = $request->attributes->get('_stateless');
}
if (!$stateless) {
return;
}
if (!$session = $requestStack->getCurrentRequest()->getSession()) {
return;
}
if ($session->isStarted()) {
$session->save();
}
throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.');
}
/**
* @internal
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 128],
// low priority to come after regular response listeners
KernelEvents::RESPONSE => ['onKernelResponse', -1000],
];
}
/**
* @internal
*/
public function reset(): void
{
if (\PHP_SESSION_ACTIVE === session_status()) {
session_abort();
}
session_unset();
$_SESSION = [];
if (!headers_sent()) { // session id can only be reset when no headers were so we check for headers_sent first
session_id('');
}
}
/**
* Gets the session object.
*
* @internal
*/
abstract protected function getSession(): ?SessionInterface;
private function getSessionOptions(array $sessionOptions): array
{
$mergedSessionOptions = [];
foreach (session_get_cookie_params() as $key => $value) {
$mergedSessionOptions['cookie_'.$key] = $value;
}
foreach ($sessionOptions as $key => $value) {
// do the same logic as in the NativeSessionStorage
if ('cookie_secure' === $key && 'auto' === $value) {
continue;
}
$mergedSessionOptions[$key] = $value;
}
return $mergedSessionOptions;
}
}
AddRequestFormatsListener.php 0000644 00000002260 15152054624 0012364 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Adds configured formats to each request.
*
* @author Gildas Quemener <gildas.quemener@gmail.com>
*
* @final
*/
class AddRequestFormatsListener implements EventSubscriberInterface
{
private array $formats;
public function __construct(array $formats)
{
$this->formats = $formats;
}
/**
* Adds request formats.
*/
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
foreach ($this->formats as $format => $mimeTypes) {
$request->setFormat($format, $mimeTypes);
}
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => ['onKernelRequest', 100]];
}
}
ResponseListener.php 0000644 00000003547 15152054624 0010576 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* ResponseListener fixes the Response headers based on the Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ResponseListener implements EventSubscriberInterface
{
private string $charset;
private bool $addContentLanguageHeader;
public function __construct(string $charset, bool $addContentLanguageHeader = false)
{
$this->charset = $charset;
$this->addContentLanguageHeader = $addContentLanguageHeader;
}
/**
* Filters the Response.
*/
public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$response = $event->getResponse();
if (null === $response->getCharset()) {
$response->setCharset($this->charset);
}
if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) {
$response->headers->set('Content-Language', $event->getRequest()->getLocale());
}
if ($event->getRequest()->attributes->get('_vary_by_language')) {
$response->setVary('Accept-Language', false);
}
$response->prepare($event->getRequest());
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
}
SurrogateListener.php 0000644 00000003430 15152054624 0010742 0 ustar 00 <?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class SurrogateListener implements EventSubscriberInterface
{
private ?SurrogateInterface $surrogate;
public function __construct(?SurrogateInterface $surrogate = null)
{
$this->surrogate = $surrogate;
}
/**
* Filters the Response.
*/
public function onKernelResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$kernel = $event->getKernel();
$surrogate = $this->surrogate;
if ($kernel instanceof HttpCache) {
$surrogate = $kernel->getSurrogate();
if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) {
$surrogate = $this->surrogate;
}
}
if (null === $surrogate) {
return;
}
$surrogate->addSurrogateControl($event->getResponse());
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
}