summaryrefslogtreecommitdiff
path: root/includes/form.inc
diff options
context:
space:
mode:
Diffstat (limited to 'includes/form.inc')
-rw-r--r--includes/form.inc101
1 files changed, 88 insertions, 13 deletions
diff --git a/includes/form.inc b/includes/form.inc
index 306747bae..f7671bed6 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -1128,6 +1128,17 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
drupal_alter($hooks, $form, $form_state, $form_id);
}
+/**
+ * Helper function to call form_set_error() if there is a token error.
+ */
+function _drupal_invalid_token_set_form_error() {
+ $path = current_path();
+ $query = drupal_get_query_parameters();
+ $url = url($path, array('query' => $query));
+
+ // Setting this error will cause the form to fail validation.
+ form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+}
/**
* Validates user-submitted form data in the $form_state array.
@@ -1162,16 +1173,11 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
}
// If the session token was set by drupal_prepare_form(), ensure that it
- // matches the current user's session.
+ // matches the current user's session. This is duplicate to code in
+ // form_builder() but left to protect any custom form handling code.
if (isset($form['#token'])) {
- if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
- $path = current_path();
- $query = drupal_get_query_parameters();
- $url = url($path, array('query' => $query));
-
- // Setting this error will cause the form to fail validation.
- form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
-
+ if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) {
+ _drupal_invalid_token_set_form_error();
// Stop here and don't run any further validation handlers, because they
// could invoke non-safe operations which opens the door for CSRF
// vulnerabilities.
@@ -1827,6 +1833,20 @@ function form_builder($form_id, &$element, &$form_state) {
// from the POST data is set and matches the current form_id.
if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
$form_state['process_input'] = TRUE;
+ // If the session token was set by drupal_prepare_form(), ensure that it
+ // matches the current user's session.
+ $form_state['invalid_token'] = FALSE;
+ if (isset($element['#token'])) {
+ if (empty($form_state['input']['form_token']) || !drupal_valid_token($form_state['input']['form_token'], $element['#token'])) {
+ // Set an early form error to block certain input processing since that
+ // opens the door for CSRF vulnerabilities.
+ _drupal_invalid_token_set_form_error();
+ // This value is checked in _form_builder_handle_input_element().
+ $form_state['invalid_token'] = TRUE;
+ // Make sure file uploads do not get processed.
+ $_FILES = array();
+ }
+ }
}
else {
$form_state['process_input'] = FALSE;
@@ -1930,6 +1950,18 @@ function form_builder($form_id, &$element, &$form_state) {
$element['#attributes']['enctype'] = 'multipart/form-data';
}
+ // Allow Ajax submissions to the form action to bypass verification. This is
+ // especially useful for multipart forms, which cannot be verified via a
+ // response header.
+ $element['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'urlIsAjaxTrusted' => array(
+ $element['#action'] => TRUE,
+ ),
+ ),
+ );
+
// If a form contains a single textfield, and the ENTER key is pressed
// within it, Internet Explorer submits the form with no POST data
// identifying any submit button. Other browsers submit POST data as though
@@ -1978,6 +2010,19 @@ function form_builder($form_id, &$element, &$form_state) {
* Adds the #name and #value properties of an input element before rendering.
*/
function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
+ static $safe_core_value_callbacks = array(
+ 'form_type_token_value',
+ 'form_type_textarea_value',
+ 'form_type_textfield_value',
+ 'form_type_checkbox_value',
+ 'form_type_checkboxes_value',
+ 'form_type_radios_value',
+ 'form_type_password_confirm_value',
+ 'form_type_select_value',
+ 'form_type_tableselect_value',
+ 'list_boolean_allowed_values_callback',
+ );
+
if (!isset($element['#name'])) {
$name = array_shift($element['#parents']);
$element['#name'] = $name;
@@ -2056,7 +2101,14 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
// property, optionally filtered through $value_callback.
if ($input_exists) {
if (function_exists($value_callback)) {
- $element['#value'] = $value_callback($element, $input, $form_state);
+ // Skip all value callbacks except safe ones like text if the CSRF
+ // token was invalid.
+ if (empty($form_state['invalid_token']) || in_array($value_callback, $safe_core_value_callbacks)) {
+ $element['#value'] = $value_callback($element, $input, $form_state);
+ }
+ else {
+ $input = NULL;
+ }
}
if (!isset($element['#value']) && isset($input)) {
$element['#value'] = $input;
@@ -3911,6 +3963,29 @@ function theme_hidden($variables) {
}
/**
+ * Process function to prepare autocomplete data.
+ *
+ * @param $element
+ * A textfield or other element with a #autocomplete_path.
+ *
+ * @return array
+ * The processed form element.
+ */
+function form_process_autocomplete($element) {
+ $element['#autocomplete_input'] = array();
+ if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
+ $element['#autocomplete_input']['#id'] = $element['#id'] .'-autocomplete';
+ // Force autocomplete to use non-clean URLs since this protects against the
+ // browser interpreting the path plus search string as an actual file.
+ $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL;
+ $GLOBALS['conf']['clean_url'] = 0;
+ $element['#autocomplete_input']['#url_value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
+ $GLOBALS['conf']['clean_url'] = $current_clean_url;
+ }
+ return $element;
+}
+
+/**
* Returns HTML for a textfield form element.
*
* @param $variables
@@ -3928,14 +4003,14 @@ function theme_textfield($variables) {
_form_set_class($element, array('form-text'));
$extra = '';
- if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
+ if ($element['#autocomplete_path'] && !empty($element['#autocomplete_input'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
- $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
- $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE));
+ $attributes['id'] = $element['#autocomplete_input']['#id'];
+ $attributes['value'] = $element['#autocomplete_input']['#url_value'];
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';