diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 0d2aa264adce2..421f669c952b0 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -108,6 +108,7 @@ public function getConfigTreeBuilder()
$this->addWebLinkSection($rootNode);
$this->addLockSection($rootNode);
$this->addMessengerSection($rootNode);
+ $this->addRobotsIndexSection($rootNode);
return $treeBuilder;
}
@@ -1156,4 +1157,17 @@ function ($a) {
->end()
;
}
+
+ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->booleanNode('disallow_search_engine_index')
+ ->info('Enabled by default when debug is enabled.')
+ ->defaultValue($this->debug)
+ ->treatNullLike($this->debug)
+ ->end()
+ ->end()
+ ;
+ }
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 714d92379dd26..874aeaa9773b9 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -394,6 +394,10 @@ public function load(array $configs, ContainerBuilder $container)
// remove tagged iterator argument for resource checkers
$container->getDefinition('config_cache_factory')->setArguments([]);
}
+
+ if (!$config['disallow_search_engine_index'] ?? false) {
+ $container->removeDefinition('disallow_search_engine_index_response_listener');
+ }
}
/**
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
index 41c682c2111c7..07aa84c9cc033 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
@@ -85,5 +85,8 @@
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index 2ddf9175f310b..589ddf50a63fe 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -330,6 +330,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'default_bus' => null,
'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]],
],
+ 'disallow_search_engine_index' => true,
];
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index 6d3563d0781c9..586a3811e2277 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -1327,6 +1327,27 @@ public function testSessionCookieSecureAuto()
$this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues()));
}
+ public function testRobotsTagListenerIsRegisteredInDebugMode()
+ {
+ $container = $this->createContainer(['kernel.debug' => true]);
+ (new FrameworkExtension())->load([], $container);
+ $this->assertTrue($container->has('disallow_search_engine_index_response_listener'), 'DisallowRobotsIndexingListener should be registered');
+
+ $definition = $container->getDefinition('disallow_search_engine_index_response_listener');
+ $this->assertTrue($definition->hasTag('kernel.event_subscriber'), 'DisallowRobotsIndexingListener should have the correct tag');
+
+ $container = $this->createContainer(['kernel.debug' => true]);
+ (new FrameworkExtension())->load([['disallow_search_engine_index' => false]], $container);
+ $this->assertFalse(
+ $container->has('disallow_search_engine_index_response_listener'),
+ 'DisallowRobotsIndexingListener should not be registered when explicitly disabled'
+ );
+
+ $container = $this->createContainer(['kernel.debug' => false]);
+ (new FrameworkExtension())->load([], $container);
+ $this->assertFalse($container->has('disallow_search_engine_index_response_listener'), 'DisallowRobotsIndexingListener should NOT be registered');
+ }
+
protected function createContainer(array $data = [])
{
return new ContainerBuilder(new ParameterBag(array_merge([
diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md
index 8a923812b5285..0fe512b30a291 100644
--- a/src/Symfony/Component/HttpKernel/CHANGELOG.md
+++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md
@@ -12,6 +12,7 @@ CHANGELOG
* the base `DataCollector` doesn't implement `Serializable` anymore, you should
store all the serialized state in the data property instead
* `DumpDataCollector` has been marked as `final`
+ * added an event listener to prevent search engines from indexing applications in debug mode.
4.2.0
-----
diff --git a/src/Symfony/Component/HttpKernel/EventListener/DisallowRobotsIndexingListener.php b/src/Symfony/Component/HttpKernel/EventListener/DisallowRobotsIndexingListener.php
new file mode 100644
index 0000000000000..280bd90d069b6
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/EventListener/DisallowRobotsIndexingListener.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\HttpKernel\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Ensures that the application is not indexed by search engines.
+ *
+ * @author Gary PEGEOT
+ */
+class DisallowRobotsIndexingListener implements EventSubscriberInterface
+{
+ private const HEADER_NAME = 'X-Robots-Tag';
+
+ public function onResponse(FilterResponseEvent $event): void
+ {
+ if (!$event->getResponse()->headers->has(static::HEADER_NAME)) {
+ $event->getResponse()->headers->set(static::HEADER_NAME, 'noindex');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ KernelEvents::RESPONSE => ['onResponse', -255],
+ ];
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php
new file mode 100644
index 0000000000000..b50ace673b14b
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.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\HttpKernel\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelInterface;
+
+class DisallowRobotsIndexingListenerTest extends TestCase
+{
+ /**
+ * @dataProvider provideResponses
+ */
+ public function testInvoke(?string $expected, Response $response): void
+ {
+ $listener = new DisallowRobotsIndexingListener();
+
+ $event = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $this->createMock(Request::class), KernelInterface::MASTER_REQUEST, $response);
+
+ $listener->onResponse($event);
+
+ $this->assertSame($expected, $response->headers->get('X-Robots-Tag'), 'Header doesn\'t match expectations');
+ }
+
+ public function provideResponses(): iterable
+ {
+ yield 'No header' => ['noindex', new Response()];
+
+ yield 'Header already set' => [
+ 'something else',
+ new Response('', 204, ['X-Robots-Tag' => 'something else']),
+ ];
+ }
+}