diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 40c598f..a767301 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -22,6 +22,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * Provides form building and processing. @@ -188,6 +189,11 @@ public function buildForm($form_id, FormStateInterface &$form_state) { $request = $this->requestStack->getCurrentRequest(); $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all(); $form_state->setUserInput($input); + + // There is a potential for a broken POST request, for example caused + // by an upload limit set the same as the max post size, which triggers + // PHP to truncate the incoming variables. + $this->doTruncationCheck($form_state); } if (isset($_SESSION['batch_form_state'])) { @@ -605,6 +611,14 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { '#parents' => array('form_build_id'), ); + $form['form_truncation_check'] = [ + '#type' => 'hidden', + '#value' => Crypt::randomBytesBase64(), + // Ensure that the truncation check is added to the end, so its appears at + // the end of the POST request. + '#weight' => 1e6, + ]; + // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually // requested previously by the user and protects against cross site request @@ -1151,4 +1165,33 @@ protected function currentUser() { return $this->currentUser; } + /** + * Ensures that the POST request is not truncated. + * + * This could be triggered by the following cases: + * + * - More POST variables than max_upload_vars + * - A max_upload_size near post_max_size. This allows people to upload files + * which results in having no space available for the other form fields. + * - An internal temp file used by PHP could not be created or written. + * + * @todo On the longrun we could try to use the CONTENT-LENGTH HTTP header + * directly. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state with input set already. + * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * Thrown when the truncation check failed. + */ + protected function doTruncationCheck($form_state) { + if ($form_state->isMethodType('POST')) { + $user_input = $form_state->getUserInput(); + $form_truncation_check = isset($user_input['form_truncation_check']) ? $user_input['form_truncation_check'] : FALSE; + if (!$form_truncation_check) { + throw new BadRequestHttpException('POST truncation check failed.'); + } + } + } + } diff --git a/core/modules/system/templates/form.html.twig b/core/modules/system/templates/form.html.twig index 2cd1e95..6d8a49f 100644 --- a/core/modules/system/templates/form.html.twig +++ b/core/modules/system/templates/form.html.twig @@ -7,6 +7,9 @@ * - attributes: A list of HTML attributes for the wrapper element. * - children: The child elements of the form. * + * In case this template is overridden ensure that + * children.form_truncation_check is rendered at the end. + * * @see template_preprocess_form() * * @ingroup themeable diff --git a/core/themes/classy/templates/form/form.html.twig b/core/themes/classy/templates/form/form.html.twig index 97b4b7a..981130a 100644 --- a/core/themes/classy/templates/form/form.html.twig +++ b/core/themes/classy/templates/form/form.html.twig @@ -7,6 +7,9 @@ * - attributes: A list of HTML attributes for the wrapper element. * - children: The child elements of the form. * + * In case this template is overridden ensure that + * children.form_truncation_check is rendered at the end. + * * @see template_preprocess_form() */ #}