diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 9b2f3cb3a4373..c1f73e561038a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -46,5 +46,9 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index bcfce7b707007..9f62cb42e7de7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,8 +22,8 @@ "symfony/config": "~2.4", "symfony/event-dispatcher": "~2.5", "symfony/finder": "~2.0,>=2.0.5", - "symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4", - "symfony/http-kernel": "~2.7", + "symfony/http-foundation": "~2.7", + "symfony/http-kernel": "~2.7.15|~2.8.8", "symfony/filesystem": "~2.3", "symfony/routing": "~2.6,>2.6.4", "symfony/security-core": "~2.6.13|~2.7.9|~2.8", diff --git a/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php new file mode 100644 index 0000000000000..00096ccf9e4f2 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.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\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates that the headers and other information indicating the + * client IP address of a request are consistent. + * + * @author Magnus Nordlander + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + // This will throw an exception if the headers are inconsistent. + $request->getClientIps(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array( + array('onKernelRequest', 256), + ), + ); + } +} diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 2b8146127e9d5..4e628a1409beb 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -63,6 +63,9 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ try { return $this->handleRaw($request, $type); } catch (\Exception $e) { + if ($e instanceof ConflictingHeadersException) { + $e = new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e); + } if (false === $catch) { $this->finishRequest($request, $type); @@ -115,13 +118,6 @@ public function terminateWithException(\Exception $exception) */ private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { - if (self::MASTER_REQUEST === $type && $request::getTrustedProxies()) { - try { - $request->getClientIps(); - } catch (ConflictingHeadersException $e) { - throw new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e); - } - } $this->requestStack->push($request); // request diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.php new file mode 100644 index 0000000000000..842a3869cba79 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ValidateRequestListenerTest.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\HttpKernel\Tests\EventListener; + +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +class ValidateRequestListenerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + */ + public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1')); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', '2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest')); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $dispatcher->dispatch(KernelEvents::REQUEST, $event); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 59c1d3342551d..372c2a3c1b1ae 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -276,26 +276,20 @@ public function testVerifyRequestStackPushPopDuringHandle() */ public function testInconsistentClientIpsOnMasterRequests() { - $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver()); - $request = new Request(); - $request->setTrustedProxies(array('1.1.1.1')); - $request->server->set('REMOTE_ADDR', '1.1.1.1'); - $request->headers->set('FORWARDED', '2.2.2.2'); - $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->getRequest()->getClientIp(); + }); - $kernel->handle($request, $kernel::MASTER_REQUEST, false); - } + $kernel = new HttpKernel($dispatcher, $this->getResolver()); - public function testInconsistentClientIpsOnSubRequests() - { - $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver()); $request = new Request(); $request->setTrustedProxies(array('1.1.1.1')); $request->server->set('REMOTE_ADDR', '1.1.1.1'); $request->headers->set('FORWARDED', '2.2.2.2'); $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); - $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $kernel->handle($request, $kernel::SUB_REQUEST, false)); + $kernel->handle($request, $kernel::MASTER_REQUEST, false); } protected function getResolver($controller = null)