summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorDavid Rothstein <drothstein@gmail.com>2015-08-19 17:22:37 -0400
committerDavid Rothstein <drothstein@gmail.com>2015-08-19 17:22:37 -0400
commit731dfacab8bf39918c135bf4939e56a76dc6ab34 (patch)
tree7febc850df6d6f4ebc82073376f4041c300abfb7 /includes
parent11cc2b0251d705b5bed981368fee038e5dddb0d1 (diff)
parentbe00a1ced4104d84df2f34b149b35fb0adf91093 (diff)
downloadbrdo-731dfacab8bf39918c135bf4939e56a76dc6ab34.tar.gz
brdo-731dfacab8bf39918c135bf4939e56a76dc6ab34.tar.bz2
Merge tag '7.39' into 7.x
7.39 release Conflicts: CHANGELOG.txt includes/bootstrap.inc
Diffstat (limited to 'includes')
-rw-r--r--includes/ajax.inc37
-rw-r--r--includes/bootstrap.inc2
-rw-r--r--includes/database/database.inc2
-rw-r--r--includes/form.inc101
-rw-r--r--includes/menu.inc2
5 files changed, 127 insertions, 17 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index 6e8e277b8..50e8e28a5 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -230,6 +230,10 @@
* functions.
*/
function ajax_render($commands = array()) {
+ // Although ajax_deliver() does this, some contributed and custom modules
+ // render Ajax responses without using that delivery callback.
+ ajax_set_verification_header();
+
// Ajax responses aren't rendered with html.tpl.php, so we have to call
// drupal_get_css() and drupal_get_js() here, in order to have new files added
// during this request to be loaded by the page. We only want to send back
@@ -487,6 +491,9 @@ function ajax_deliver($page_callback_result) {
}
}
+ // Let ajax.js know that this response is safe to process.
+ ajax_set_verification_header();
+
// Print the response.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
@@ -577,6 +584,29 @@ function ajax_prepare_response($page_callback_result) {
}
/**
+ * Sets a response header for ajax.js to trust the response body.
+ *
+ * It is not safe to invoke Ajax commands within user-uploaded files, so this
+ * header protects against those being invoked.
+ *
+ * @see Drupal.ajax.options.success()
+ */
+function ajax_set_verification_header() {
+ $added = &drupal_static(__FUNCTION__);
+
+ // User-uploaded files cannot set any response headers, so a custom header is
+ // used to indicate to ajax.js that this response is safe. Note that most
+ // Ajax requests bound using the Form API will be protected by having the URL
+ // flagged as trusted in Drupal.settings, so this header is used only for
+ // things like custom markup that gets Ajax behaviors attached.
+ if (empty($added)) {
+ drupal_add_http_header('X-Drupal-Ajax-Token', '1');
+ // Avoid sending the header twice.
+ $added = TRUE;
+ }
+}
+
+/**
* Performs end-of-Ajax-request tasks.
*
* This function is the equivalent of drupal_page_footer(), but for Ajax
@@ -764,7 +794,12 @@ function ajax_pre_render_element($element) {
$element['#attached']['js'][] = array(
'type' => 'setting',
- 'data' => array('ajax' => array($element['#id'] => $settings)),
+ 'data' => array(
+ 'ajax' => array($element['#id'] => $settings),
+ 'urlIsAjaxTrusted' => array(
+ $settings['url'] => TRUE,
+ ),
+ ),
);
// Indicate that Ajax processing was successful.
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index e2e133443..efddf006a 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.39-dev');
+define('VERSION', '7.40-dev');
/**
* Core API compatibility.
diff --git a/includes/database/database.inc b/includes/database/database.inc
index 01b638584..3d776b573 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -626,7 +626,7 @@ abstract class DatabaseConnection extends PDO {
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
- return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
+ return strtr($comment, array('*' => ' * '));
}
/**
diff --git a/includes/form.inc b/includes/form.inc
index 88ef30415..f1691adf3 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) . ' />';
diff --git a/includes/menu.inc b/includes/menu.inc
index 8e26b6dee..0e9c977c6 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -1487,7 +1487,7 @@ function menu_tree_collect_node_links(&$tree, &$node_links) {
* menu_tree_collect_node_links().
*/
function menu_tree_check_access(&$tree, $node_links = array()) {
- if ($node_links) {
+ if ($node_links && (user_access('access content') || user_access('bypass node access'))) {
$nids = array_keys($node_links);
$select = db_select('node', 'n');
$select->addField('n', 'nid');