From 289531f0d0f57ed6f82b5665df7d861b21271477 Mon Sep 17 00:00:00 2001 From: James Halsall Date: Sun, 17 Jul 2016 15:16:09 +0100 Subject: [PATCH] [Form] Skip CSRF validation on form when POST max size is exceeded --- .../Resources/config/form_csrf.xml | 1 + .../EventListener/CsrfValidationListener.php | 12 ++++++++-- .../Csrf/Type/FormTypeCsrfExtension.php | 12 ++++++++-- .../HttpFoundationRequestHandler.php | 5 +---- .../Component/Form/NativeRequestHandler.php | 5 +---- .../CsrfValidationListenerTest.php | 22 +++++++++++++++++++ .../Component/Form/Util/ServerParams.php | 13 +++++++++++ 7 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index f20552ed18..5da44c464e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -16,6 +16,7 @@ %form.type_extension.csrf.field_name% %validator.translation_domain% + diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 64378336e9..034f30612d 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; @@ -68,6 +69,11 @@ class CsrfValidationListener implements EventSubscriberInterface */ private $translationDomain; + /** + * @var ServerParams + */ + private $serverParams; + public static function getSubscribedEvents() { return array( @@ -75,7 +81,7 @@ class CsrfValidationListener implements EventSubscriberInterface ); } - public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null) { if ($tokenManager instanceof CsrfProviderInterface) { $tokenManager = new CsrfProviderAdapter($tokenManager); @@ -89,13 +95,15 @@ class CsrfValidationListener implements EventSubscriberInterface $this->errorMessage = $errorMessage; $this->translator = $translator; $this->translationDomain = $translationDomain; + $this->serverParams = $serverParams ?: new ServerParams(); } public function preSubmit(FormEvent $event) { $form = $event->getForm(); + $postRequestSizeExceeded = $form->getConfig()->getMethod() === 'POST' && $this->serverParams->hasPostMaxSizeBeenExceeded(); - if ($form->isRoot() && $form->getConfig()->getOption('compound')) { + if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) { $data = $event->getData(); if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) { diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 35d8648215..2a34ef0e9e 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -20,6 +20,7 @@ use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -55,7 +56,12 @@ class FormTypeCsrfExtension extends AbstractTypeExtension */ private $translationDomain; - public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) + /** + * @var ServerParams + */ + private $serverParams; + + public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null) { if ($defaultTokenManager instanceof CsrfProviderInterface) { $defaultTokenManager = new CsrfProviderAdapter($defaultTokenManager); @@ -68,6 +74,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension $this->defaultFieldName = $defaultFieldName; $this->translator = $translator; $this->translationDomain = $translationDomain; + $this->serverParams = $serverParams; } /** @@ -89,7 +96,8 @@ class FormTypeCsrfExtension extends AbstractTypeExtension $options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())), $options['csrf_message'], $this->translator, - $this->translationDomain + $this->translationDomain, + $this->serverParams )) ; } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 98bbd4b9ce..d1e5eece7b 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -73,10 +73,7 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface // Mark the form with an error if the uploaded size was too large // This is done here and not in FormValidator because $_POST is // empty when that error occurs. Hence the form is never submitted. - $contentLength = $this->serverParams->getContentLength(); - $maxContentLength = $this->serverParams->getPostMaxSize(); - - if (!empty($maxContentLength) && $contentLength > $maxContentLength) { + if ($this->serverParams->hasPostMaxSizeBeenExceeded()) { // Submit the form, but don't clear the default values $form->submit(null, false); diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 36a7d7cff2..5541e96ad5 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -81,10 +81,7 @@ class NativeRequestHandler implements RequestHandlerInterface // Mark the form with an error if the uploaded size was too large // This is done here and not in FormValidator because $_POST is // empty when that error occurs. Hence the form is never submitted. - $contentLength = $this->serverParams->getContentLength(); - $maxContentLength = $this->serverParams->getPostMaxSize(); - - if (!empty($maxContentLength) && $contentLength > $maxContentLength) { + if ($this->serverParams->hasPostMaxSizeBeenExceeded()) { // Submit the form, but don't clear the default values $form->submit(null, false); diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php index 7206ceede7..4904b679dd 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener; +use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; @@ -72,4 +73,25 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase // Validate accordingly $this->assertSame($data, $event->getData()); } + + public function testMaxPostSizeExceeded() + { + $serverParams = $this + ->getMockBuilder('\Symfony\Component\Form\Util\ServerParams') + ->disableOriginalConstructor() + ->getMock() + ; + + $serverParams + ->expects($this->once()) + ->method('hasPostMaxSizeBeenExceeded') + ->willReturn(true) + ; + + $event = new FormEvent($this->form, array('csrf' => 'token')); + $validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Error message', null, null, $serverParams); + + $validation->preSubmit($event); + $this->assertEmpty($this->form->getErrors()); + } } diff --git a/src/Symfony/Component/Form/Util/ServerParams.php b/src/Symfony/Component/Form/Util/ServerParams.php index c4da49db84..b9f5aaff55 100644 --- a/src/Symfony/Component/Form/Util/ServerParams.php +++ b/src/Symfony/Component/Form/Util/ServerParams.php @@ -25,6 +25,19 @@ class ServerParams $this->requestStack = $requestStack; } + /** + * Returns true if the POST max size has been exceeded in the request. + * + * @return bool + */ + public function hasPostMaxSizeBeenExceeded() + { + $contentLength = $this->getContentLength(); + $maxContentLength = $this->getPostMaxSize(); + + return $maxContentLength && $contentLength > $maxContentLength; + } + /** * Returns maximum post size in bytes. *