From 3dd86719bf64943b8415940c23beb72f3edaa333 Mon Sep 17 00:00:00 2001 From: Gary PEGEOT Date: Wed, 20 Feb 2019 22:09:05 +0000 Subject: [PATCH] [HttpKernel] Prevent search engines from indexing dev applications --- .../DependencyInjection/Configuration.php | 14 ++++++ .../FrameworkExtension.php | 4 ++ .../FrameworkBundle/Resources/config/web.xml | 3 ++ .../DependencyInjection/ConfigurationTest.php | 1 + .../FrameworkExtensionTest.php | 21 +++++++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../DisallowRobotsIndexingListener.php | 43 +++++++++++++++++ .../DisallowRobotsIndexingListenerTest.php | 47 +++++++++++++++++++ 8 files changed, 134 insertions(+) create mode 100644 src/Symfony/Component/HttpKernel/EventListener/DisallowRobotsIndexingListener.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 0d2aa264ad..421f669c95 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -108,6 +108,7 @@ class Configuration implements ConfigurationInterface $this->addWebLinkSection($rootNode); $this->addLockSection($rootNode); $this->addMessengerSection($rootNode); + $this->addRobotsIndexSection($rootNode); return $treeBuilder; } @@ -1156,4 +1157,17 @@ class Configuration implements ConfigurationInterface ->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 714d92379d..874aeaa977 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -394,6 +394,10 @@ class FrameworkExtension extends Extension // 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 41c682c211..07aa84c9cc 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 2ddf9175f3..589ddf50a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -330,6 +330,7 @@ class ConfigurationTest extends TestCase '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 6d3563d078..586a3811e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1327,6 +1327,27 @@ abstract class FrameworkExtensionTest extends TestCase $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 8a923812b5..0fe512b30a 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 0000000000..280bd90d06 --- /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 0000000000..b50ace673b --- /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']), + ]; + } +}