diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index bf63332ced..7980131433 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -151,8 +151,11 @@ + + + diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 6da2034731..b417afdbcb 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -56,8 +56,7 @@ class FormType extends BaseType ->setDataLocked($isDataOptionSet) ->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null) ->setMethod($options['method']) - ->setAction($options['action']) - ; + ->setAction($options['action']); if ($options['trim']) { $builder->addEventSubscriber(new TrimListener()); @@ -170,25 +169,26 @@ class FormType extends BaseType )); $resolver->setDefaults(array( - 'data_class' => $dataClass, - 'empty_data' => $emptyData, - 'trim' => true, - 'required' => true, - 'read_only' => false, - 'max_length' => null, - 'pattern' => null, - 'property_path' => null, - 'mapped' => true, - 'by_reference' => true, - 'error_bubbling' => $errorBubbling, - 'label_attr' => array(), - 'virtual' => null, - 'inherit_data' => $inheritData, - 'compound' => true, - 'method' => 'POST', + 'data_class' => $dataClass, + 'empty_data' => $emptyData, + 'trim' => true, + 'required' => true, + 'read_only' => false, + 'max_length' => null, + 'pattern' => null, + 'property_path' => null, + 'mapped' => true, + 'by_reference' => true, + 'error_bubbling' => $errorBubbling, + 'label_attr' => array(), + 'virtual' => null, + 'inherit_data' => $inheritData, + 'compound' => true, + 'method' => 'POST', // According to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt) // section 4.2., empty URIs are considered same-document references - 'action' => '', + 'action' => '', + 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', )); $resolver->setAllowedTypes(array( diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php index 08bd89c9e4..33e9c1c4d4 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Extension\HttpFoundation; use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\Util\ServerParams; /** * Integrates the HttpFoundation component with the Form library. @@ -20,10 +21,20 @@ use Symfony\Component\Form\AbstractExtension; */ class HttpFoundationExtension extends AbstractExtension { + /** + * @var ServerParams + */ + private $serverParams; + + public function __construct(ServerParams $serverParams = null) + { + $this->serverParams = $serverParams; + } + protected function loadTypeExtensions() { return array( - new Type\FormTypeHttpFoundationExtension(), + new Type\FormTypeHttpFoundationExtension($this->serverParams), ); } } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 2094699481..03805fef52 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -12,8 +12,10 @@ namespace Symfony\Component\Form\Extension\HttpFoundation; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\RequestHandlerInterface; +use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\HttpFoundation\Request; /** @@ -24,6 +26,19 @@ use Symfony\Component\HttpFoundation\Request; */ class HttpFoundationRequestHandler implements RequestHandlerInterface { + /** + * @var ServerParams + */ + private $serverParams; + + /** + * {@inheritdoc} + */ + public function __construct(ServerParams $serverParams = null) + { + $this->serverParams = $serverParams ?: new ServerParams(); + } + /** * {@inheritdoc} */ @@ -53,6 +68,25 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface $data = $request->query->get($name); } } else { + // 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) { + // Submit the form, but don't clear the default values + $form->submit(null, false); + + $form->addError(new FormError( + $form->getConfig()->getOption('post_max_size_message'), + null, + array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()) + )); + + return; + } + if ('' === $name) { $params = $request->request->all(); $files = $request->files->all(); diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php index 9b09b05c39..4596f06b98 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -15,6 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestListener; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Util\ServerParams; /** * @author Bernhard Schussek @@ -31,10 +32,10 @@ class FormTypeHttpFoundationExtension extends AbstractTypeExtension */ private $requestHandler; - public function __construct() + public function __construct(ServerParams $serverParams = null) { $this->listener = new BindRequestListener(); - $this->requestHandler = new HttpFoundationRequestHandler(); + $this->requestHandler = new HttpFoundationRequestHandler($serverParams); } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 154e865923..2851016f40 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Form\Extension\Validator\Constraints; use Symfony\Component\Form\FormInterface; -use Symfony\Component\Form\Extension\Validator\Util\ServerParams; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -21,22 +20,6 @@ use Symfony\Component\Validator\ConstraintValidator; */ class FormValidator extends ConstraintValidator { - /** - * @var ServerParams - */ - private $serverParams; - - /** - * Creates a validator with the given server parameters. - * - * @param ServerParams $params The server parameters. Default - * parameters are created if null. - */ - public function __construct(ServerParams $params = null) - { - $this->serverParams = $params ?: new ServerParams(); - } - /** * {@inheritdoc} */ @@ -113,21 +96,6 @@ class FormValidator extends ConstraintValidator $form->getExtraData() ); } - - // Mark the form with an error if the uploaded size was too large - $length = $this->serverParams->getContentLength(); - - if ($form->isRoot() && null !== $length) { - $max = $this->serverParams->getPostMaxSize(); - - if (!empty($max) && $length > $max) { - $this->context->addViolation( - $config->getOption('post_max_size_message'), - array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()), - $length - ); - } - } } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 344bddadc1..ae39af66fc 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -66,7 +66,6 @@ class FormTypeValidatorExtension extends BaseValidatorExtension 'invalid_message' => 'This value is not valid.', 'invalid_message_parameters' => array(), 'extra_fields_message' => 'This form should not contain extra fields.', - 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', )); $resolver->setNormalizers(array( diff --git a/src/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php b/src/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php index 58fdc25e22..c058d60cae 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php +++ b/src/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php @@ -14,59 +14,6 @@ namespace Symfony\Component\Form\Extension\Validator\Util; /** * @author Bernhard Schussek */ -class ServerParams +class ServerParams extends \Symfony\Component\Form\Util\ServerParams { - /** - * Returns maximum post size in bytes. - * - * @return null|int The maximum post size in bytes - */ - public function getPostMaxSize() - { - $iniMax = strtolower($this->getNormalizedIniPostMaxSize()); - - if ('' === $iniMax) { - return; - } - - $max = ltrim($iniMax, '+'); - if (0 === strpos($max, '0x')) { - $max = intval($max, 16); - } elseif (0 === strpos($max, '0')) { - $max = intval($max, 8); - } else { - $max = intval($max); - } - - switch (substr($iniMax, -1)) { - case 't': $max *= 1024; - case 'g': $max *= 1024; - case 'm': $max *= 1024; - case 'k': $max *= 1024; - } - - return $max; - } - - /** - * Returns the normalized "post_max_size" ini setting. - * - * @return string - */ - public function getNormalizedIniPostMaxSize() - { - return strtoupper(trim(ini_get('post_max_size'))); - } - - /** - * Returns the content length of the request. - * - * @return mixed The request content length. - */ - public function getContentLength() - { - return isset($_SERVER['CONTENT_LENGTH']) - ? (int) $_SERVER['CONTENT_LENGTH'] - : null; - } } diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index fefe546af8..9df9066886 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Util\ServerParams; /** * A request handler using PHP's super globals $_GET, $_POST and $_SERVER. @@ -20,6 +21,19 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException; */ class NativeRequestHandler implements RequestHandlerInterface { + /** + * @var ServerParams + */ + private $serverParams; + + /** + * {@inheritdoc} + */ + public function __construct(ServerParams $params = null) + { + $this->serverParams = $params ?: new ServerParams(); + } + /** * The allowed keys of the $_FILES array. * @@ -62,6 +76,25 @@ class NativeRequestHandler implements RequestHandlerInterface $data = $_GET[$name]; } } else { + // 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) { + // Submit the form, but don't clear the default values + $form->submit(null, false); + + $form->addError(new FormError( + $form->getConfig()->getOption('post_max_size_message'), + null, + array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()) + )); + + return; + } + $fixedFiles = array(); foreach ($_FILES as $name => $file) { $fixedFiles[$name] = self::stripEmptyFiles(self::fixPhpFilesArray($file)); diff --git a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php index fbba16ba17..b017db90d0 100644 --- a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php @@ -11,21 +11,38 @@ namespace Symfony\Component\Form\Tests; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\Forms; +use Symfony\Component\Form\RequestHandlerInterface; + /** * @author Bernhard Schussek */ abstract class AbstractRequestHandlerTest extends \PHPUnit_Framework_TestCase { /** - * @var \Symfony\Component\Form\RequestHandlerInterface + * @var RequestHandlerInterface */ protected $requestHandler; + /** + * @var FormFactory + */ + protected $factory; + protected $request; + protected $serverParams; + protected function setUp() { + $this->serverParams = $this->getMock( + 'Symfony\Component\Form\Util\ServerParams', + array('getNormalizedIniPostMaxSize', 'getContentLength') + ); $this->requestHandler = $this->getRequestHandler(); + $this->factory = Forms::createFormFactoryBuilder()->getFormFactory(); $this->request = null; } @@ -249,6 +266,50 @@ abstract class AbstractRequestHandlerTest extends \PHPUnit_Framework_TestCase $this->requestHandler->handleRequest($form, $this->request); } + /** + * @dataProvider getPostMaxSizeFixtures + */ + public function testAddFormErrorIfPostMaxSizeExceeded($contentLength, $iniMax, $shouldFail, array $errorParams = array()) + { + $this->serverParams->expects($this->once()) + ->method('getContentLength') + ->will($this->returnValue($contentLength)); + $this->serverParams->expects($this->any()) + ->method('getNormalizedIniPostMaxSize') + ->will($this->returnValue($iniMax)); + + $options = array('post_max_size_message' => 'Max {{ max }}!'); + $form = $this->factory->createNamed('name', 'text', null, $options); + $this->setRequestData('POST', array(), array()); + + $this->requestHandler->handleRequest($form, $this->request); + + if ($shouldFail) { + $errors = array(new FormError($options['post_max_size_message'], null, $errorParams)); + + $this->assertEquals($errors, $form->getErrors()); + $this->assertTrue($form->isSubmitted()); + } else { + $this->assertCount(0, $form->getErrors()); + $this->assertFalse($form->isSubmitted()); + } + } + + public function getPostMaxSizeFixtures() + { + return array( + array(pow(1024, 3) + 1, '1G', true, array('{{ max }}' => '1G')), + array(pow(1024, 3), '1G', false), + array(pow(1024, 2) + 1, '1M', true, array('{{ max }}' => '1M')), + array(pow(1024, 2), '1M', false), + array(1024 + 1, '1K', true, array('{{ max }}' => '1K')), + array(1024, '1K', false), + array(null, '1K', false), + array(1024, '', false), + array(1024, 0, false), + ); + } + abstract protected function setRequestData($method, $data, $files = array()); abstract protected function getRequestHandler(); diff --git a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php index cf5d63d90e..dcd26891c1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php @@ -43,7 +43,7 @@ class HttpFoundationRequestHandlerTest extends AbstractRequestHandlerTest protected function getRequestHandler() { - return new HttpFoundationRequestHandler(); + return new HttpFoundationRequestHandler($this->serverParams); } protected function getMockFile() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index 19d5cec9f4..8032d6273d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -542,69 +542,6 @@ class FormValidatorTest extends AbstractConstraintValidatorTest ), 'property.path', array('foo' => 'bar')); } - /** - * @dataProvider getPostMaxSizeFixtures - */ - public function testPostMaxSizeViolation($contentLength, $iniMax, $nbViolation, array $params = array()) - { - $this->serverParams->expects($this->once()) - ->method('getContentLength') - ->will($this->returnValue($contentLength)); - $this->serverParams->expects($this->any()) - ->method('getNormalizedIniPostMaxSize') - ->will($this->returnValue($iniMax)); - - $options = array('post_max_size_message' => 'Max {{ max }}!'); - $form = $this->getBuilder('name', null, $options)->getForm(); - - $this->validator->validate($form, new Form()); - - $violations = array(); - - for ($i = 0; $i < $nbViolation; ++$i) { - $violations[] = $this->createViolation($options['post_max_size_message'], $params, 'property.path', $contentLength); - } - - $this->assertViolations($violations); - } - - public function getPostMaxSizeFixtures() - { - return array( - array(pow(1024, 3) + 1, '1G', 1, array('{{ max }}' => '1G')), - array(pow(1024, 3), '1G', 0), - array(pow(1024, 2) + 1, '1M', 1, array('{{ max }}' => '1M')), - array(pow(1024, 2), '1M', 0), - array(1024 + 1, '1K', 1, array('{{ max }}' => '1K')), - array(1024, '1K', 0), - array(null, '1K', 0), - array(1024, '', 0), - array(1024, 0, 0), - ); - } - - public function testNoViolationIfNotRoot() - { - $this->serverParams->expects($this->once()) - ->method('getContentLength') - ->will($this->returnValue(1025)); - $this->serverParams->expects($this->never()) - ->method('getNormalizedIniPostMaxSize'); - - $parent = $this->getBuilder() - ->setCompound(true) - ->setDataMapper($this->getDataMapper()) - ->getForm(); - $form = $this->getForm(); - $parent->add($form); - - $this->expectNoValidate(); - - $this->validator->validate($form, new Form()); - - $this->assertNoViolation(); - } - /** * Access has to be public, as this method is called via callback array * in {@link testValidateFormDataCanHandleCallbackValidationGroups()} diff --git a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php index 02b0a4ed74..eac767f8c8 100644 --- a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php @@ -203,7 +203,7 @@ class NativeRequestHandlerTest extends AbstractRequestHandlerTest protected function getRequestHandler() { - return new NativeRequestHandler(); + return new NativeRequestHandler($this->serverParams); } protected function getMockFile() diff --git a/src/Symfony/Component/Form/Util/ServerParams.php b/src/Symfony/Component/Form/Util/ServerParams.php new file mode 100644 index 0000000000..3b1f835182 --- /dev/null +++ b/src/Symfony/Component/Form/Util/ServerParams.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Util; + +/** + * @author Bernhard Schussek + */ +class ServerParams +{ + /** + * Returns maximum post size in bytes. + * + * @return null|int The maximum post size in bytes + */ + public function getPostMaxSize() + { + $iniMax = strtolower($this->getNormalizedIniPostMaxSize()); + + if ('' === $iniMax) { + return; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = intval($max); + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns the normalized "post_max_size" ini setting. + * + * @return string + */ + public function getNormalizedIniPostMaxSize() + { + return strtoupper(trim(ini_get('post_max_size'))); + } + + /** + * Returns the content length of the request. + * + * @return mixed The request content length. + */ + public function getContentLength() + { + return isset($_SERVER['CONTENT_LENGTH']) + ? (int) $_SERVER['CONTENT_LENGTH'] + : null; + } +}