From 91e0ff102ec302a8e96fb71b21401f2880f42135 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Mon, 9 Nov 2020 10:18:07 +0100 Subject: [PATCH] updated core to 7.73 --- CHANGELOG.txt | 20 +- includes/batch.inc | 15 +- includes/bootstrap.inc | 2 +- includes/common.inc | 11 +- includes/filetransfer/filetransfer.inc | 2 +- includes/form.inc | 14 +- includes/menu.inc | 3 + includes/pager.inc | 30 ++ includes/path.inc | 14 +- includes/request-sanitizer.inc | 2 +- misc/ajax.js | 22 +- misc/autocomplete.js | 3 +- misc/drupal.js | 17 + misc/jquery-html-prefilter-3.5.0-backport.js | 251 +++++++++++++ misc/typo3/phar-stream-wrapper/README.md | 5 +- misc/typo3/phar-stream-wrapper/composer.json | 4 +- misc/typo3/phar-stream-wrapper/src/Helper.php | 4 +- .../phar-stream-wrapper/src/Phar/Reader.php | 40 +- .../src/PharStreamWrapper.php | 2 +- .../src/Resolver/PharInvocationResolver.php | 18 +- modules/aggregator/aggregator.info | 6 +- modules/aggregator/tests/aggregator_test.info | 6 +- modules/block/block.info | 6 +- modules/block/block.module | 3 +- modules/block/tests/block_test.info | 6 +- .../block_test_theme/block_test_theme.info | 6 +- modules/blog/blog.info | 6 +- modules/book/book.info | 6 +- modules/color/color.info | 6 +- modules/color/color.module | 5 +- modules/comment/comment.info | 6 +- modules/comment/comment.install | 3 - modules/comment/comment.test | 55 +++ modules/contact/contact.info | 6 +- modules/contextual/contextual.info | 6 +- modules/dashboard/dashboard.info | 6 +- modules/dblog/dblog.info | 6 +- modules/field/field.info | 6 +- .../field_sql_storage/field_sql_storage.info | 6 +- modules/field/modules/list/list.info | 6 +- .../field/modules/list/tests/list_test.info | 6 +- modules/field/modules/number/number.info | 6 +- modules/field/modules/number/number.test | 2 +- modules/field/modules/options/options.info | 6 +- modules/field/modules/text/text.info | 6 +- modules/field/tests/field_test.info | 6 +- modules/field/tests/field_test.storage.inc | 6 +- modules/field_ui/field_ui.admin.inc | 4 + modules/field_ui/field_ui.info | 6 +- modules/field_ui/field_ui.module | 7 +- modules/file/file.info | 6 +- modules/file/tests/file.test | 2 +- modules/file/tests/file_module_test.info | 6 +- modules/filter/filter.api.php | 4 +- modules/filter/filter.info | 6 +- modules/forum/forum.info | 6 +- modules/forum/forum.module | 3 +- modules/help/help.info | 6 +- modules/image/image.info | 6 +- modules/image/tests/image_module_test.info | 6 +- modules/locale/locale.info | 6 +- modules/locale/tests/locale_test.info | 6 +- modules/menu/menu.info | 6 +- modules/node/node.info | 6 +- modules/node/tests/node_access_test.info | 6 +- modules/node/tests/node_test.info | 6 +- modules/node/tests/node_test_exception.info | 6 +- modules/openid/openid.info | 6 +- modules/openid/tests/openid_test.info | 6 +- modules/overlay/overlay.info | 6 +- modules/path/path.info | 6 +- modules/php/php.info | 6 +- modules/poll/poll.info | 6 +- modules/profile/profile.info | 6 +- modules/rdf/rdf.info | 6 +- modules/rdf/tests/rdf_test.info | 6 +- modules/search/search.extender.inc | 2 +- modules/search/search.info | 6 +- modules/search/search.module | 2 +- .../search/tests/search_embedded_form.info | 6 +- modules/search/tests/search_extra_type.info | 6 +- modules/search/tests/search_node_tags.info | 6 +- modules/shortcut/shortcut.info | 6 +- modules/simpletest/simpletest.info | 7 +- .../simpletest/tests/actions_loop_test.info | 6 +- modules/simpletest/tests/ajax_forms_test.info | 6 +- modules/simpletest/tests/ajax_test.info | 6 +- modules/simpletest/tests/batch_test.info | 6 +- modules/simpletest/tests/boot_test_1.info | 6 +- modules/simpletest/tests/boot_test_2.info | 6 +- modules/simpletest/tests/common_test.info | 6 +- .../tests/common_test_cron_helper.info | 6 +- modules/simpletest/tests/database_test.info | 6 +- .../drupal_autoload_test.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- .../tests/entity_crud_hook_test.info | 6 +- .../tests/entity_query_access_test.info | 6 +- modules/simpletest/tests/error_test.info | 6 +- modules/simpletest/tests/file_test.info | 6 +- modules/simpletest/tests/filter_test.info | 6 +- modules/simpletest/tests/form.test | 5 +- modules/simpletest/tests/form_test.info | 6 +- modules/simpletest/tests/image_test.info | 6 +- modules/simpletest/tests/menu_test.info | 6 +- modules/simpletest/tests/module_test.info | 6 +- modules/simpletest/tests/path_test.info | 6 +- .../tests/psr_0_test/psr_0_test.info | 6 +- .../tests/psr_4_test/psr_4_test.info | 6 +- .../simpletest/tests/request_sanitizer.test | 354 ++++++++++++++++++ .../simpletest/tests/requirements1_test.info | 6 +- .../simpletest/tests/requirements2_test.info | 6 +- modules/simpletest/tests/session_test.info | 6 +- .../tests/system_dependencies_test.info | 6 +- ...atible_core_version_dependencies_test.info | 6 +- ...system_incompatible_core_version_test.info | 6 +- ...ible_module_version_dependencies_test.info | 6 +- ...stem_incompatible_module_version_test.info | 6 +- .../tests/system_project_namespace_test.info | 6 +- modules/simpletest/tests/system_test.info | 6 +- modules/simpletest/tests/taxonomy_test.info | 6 +- modules/simpletest/tests/theme_test.info | 6 +- .../themes/test_basetheme/test_basetheme.info | 6 +- .../themes/test_subtheme/test_subtheme.info | 6 +- .../tests/themes/test_theme/test_theme.info | 6 +- .../test_theme_nyan_cat.info | 6 +- .../simpletest/tests/update_script_test.info | 6 +- modules/simpletest/tests/update_test_1.info | 6 +- modules/simpletest/tests/update_test_2.info | 6 +- modules/simpletest/tests/update_test_3.info | 6 +- modules/simpletest/tests/url_alter_test.info | 6 +- modules/simpletest/tests/xmlrpc_test.info | 6 +- modules/statistics/statistics.info | 6 +- modules/syslog/syslog.info | 6 +- modules/system/system.info | 6 +- modules/system/system.install | 14 + modules/system/system.module | 3 +- modules/system/system.test | 11 +- modules/system/tests/cron_queue_test.info | 6 +- modules/system/tests/system_cron_test.info | 6 +- modules/taxonomy/taxonomy.info | 6 +- modules/taxonomy/taxonomy.install | 2 +- modules/toolbar/toolbar.info | 6 +- modules/tracker/tracker.info | 6 +- .../translation/tests/translation_test.info | 6 +- modules/translation/translation.info | 6 +- modules/trigger/tests/trigger_test.info | 6 +- modules/trigger/trigger.info | 6 +- modules/update/tests/aaa_update_test.info | 6 +- modules/update/tests/bbb_update_test.info | 6 +- modules/update/tests/ccc_update_test.info | 6 +- .../update_test_admintheme.info | 6 +- .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- modules/update/tests/update_test.info | 6 +- modules/update/update.info | 6 +- modules/user/tests/user_form_test.info | 6 +- modules/user/tests/user_session_test.info | 6 +- modules/user/user.info | 6 +- modules/user/user.module | 38 +- modules/user/user.test | 53 --- profiles/minimal/minimal.info | 6 +- profiles/standard/standard.info | 6 +- ...drupal_system_listing_compatible_test.info | 6 +- ...upal_system_listing_incompatible_test.info | 6 +- profiles/testing/testing.info | 6 +- scripts/run-tests.sh | 2 +- themes/bartik/bartik.info | 6 +- themes/garland/garland.info | 6 +- themes/seven/seven.info | 6 +- themes/stark/stark.info | 6 +- 173 files changed, 1304 insertions(+), 542 deletions(-) create mode 100644 misc/jquery-html-prefilter-3.5.0-backport.js create mode 100644 modules/simpletest/tests/request_sanitizer.test diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 39c48f1b..7f5f570a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,23 @@ -Drupal 7.xx, xxxx-xx-xx (development version) +Drupal 7.73, 2020-09-16 ----------------------- +- Fixed security issues: + - SA-CORE-2020-007 + +Drupal 7.72, 2020-06-17 +----------------------- +- Fixed security issues: + - SA-CORE-2020-004 + +Drupal 7.71, 2020-06-03 +----------------------- +- Fix for jQuery Form bug in Chromium-based browsers +- Full support for PHP 7.4 + +Drupal 7.70, 2020-05-19 +----------------------- +- Fixed security issues: + - SA-CORE-2020-002 + - SA-CORE-2020-003 Drupal 7.69, 2019-12-18 ----------------------- diff --git a/includes/batch.inc b/includes/batch.inc index e89ab8de..4d4e504d 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -478,18 +478,17 @@ function _batch_finished() { $queue->deleteQueue(); } } + // Clean-up the session. Not needed for CLI updates. + if (isset($_SESSION)) { + unset($_SESSION['batches'][$batch['id']]); + if (empty($_SESSION['batches'])) { + unset($_SESSION['batches']); + } + } } $_batch = $batch; $batch = NULL; - // Clean-up the session. Not needed for CLI updates. - if (isset($_SESSION)) { - unset($_SESSION['batches'][$batch['id']]); - if (empty($_SESSION['batches'])) { - unset($_SESSION['batches']); - } - } - // Redirect if needed. if ($_batch['progressive']) { // Revert the 'destination' that was saved in batch_process(). diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 10e2a420..099c348d 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.69'); +define('VERSION', '7.73'); /** * Core API compatibility. diff --git a/includes/common.inc b/includes/common.inc index d370bb8f..07373ac4 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -391,7 +391,7 @@ function drupal_add_feed($url = NULL, $title = '') { */ function drupal_get_feeds($delimiter = "\n") { $feeds = drupal_add_feed(); - return implode($feeds, $delimiter); + return implode($delimiter, $feeds); } /** @@ -684,7 +684,10 @@ function drupal_goto($path = '', array $options = array(), $http_response_code = // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector. if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { $destination = drupal_parse_url($_GET['destination']); - $path = $destination['path']; + // Double check the path derived by drupal_parse_url() is not external. + if (!url_is_external($destination['path'])) { + $path = $destination['path']; + } $options['query'] = $destination['query']; $options['fragment'] = $destination['fragment']; } @@ -3740,7 +3743,7 @@ function _drupal_build_css_path($matches, $base = NULL) { } // Prefix with base and remove '../' segments where possible. - $path = $_base . $matches[1]; + $path = $_base . (isset($matches[1]) ? $matches[1] : ''); $last = ''; while ($path != $last) { $last = $path; @@ -6653,7 +6656,7 @@ function element_children(&$elements, $sort = FALSE) { $children = array(); $sortable = FALSE; foreach ($elements as $key => $value) { - if ($key === '' || $key[0] !== '#') { + if (is_int($key) || $key === '' || $key[0] !== '#') { $children[$key] = $value; if (is_array($value) && isset($value['#weight'])) { $sortable = TRUE; diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc index 6c55b2f4..cd420bd0 100644 --- a/includes/filetransfer/filetransfer.inc +++ b/includes/filetransfer/filetransfer.inc @@ -301,7 +301,7 @@ abstract class FileTransfer { $parts = explode('/', $path); $chroot = ''; while (count($parts)) { - $check = implode($parts, '/'); + $check = implode('/', $parts); if ($this->isFile($check . '/' . drupal_basename(__FILE__))) { // Remove the trailing slash. return substr($chroot, 0, -1); diff --git a/includes/form.inc b/includes/form.inc index 6c33de7f..1158fd03 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1135,12 +1135,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { * 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 reload this page.', array('@link' => $url))); + form_set_error('form_token', t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.')); } /** @@ -1181,6 +1177,11 @@ function drupal_validate_form($form_id, &$form, &$form_state) { if (!empty($form['#token'])) { if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) { _drupal_invalid_token_set_form_error(); + // Ignore all submitted values. + $form_state['input'] = array(); + $_POST = array(); + // Make sure file uploads do not get processed. + $_FILES = array(); // Stop here and don't run any further validation handlers, because they // could invoke non-safe operations which opens the door for CSRF // vulnerabilities. @@ -1848,6 +1849,9 @@ function form_builder($form_id, &$element, &$form_state) { _drupal_invalid_token_set_form_error(); // This value is checked in _form_builder_handle_input_element(). $form_state['invalid_token'] = TRUE; + // Ignore all submitted values. + $form_state['input'] = array(); + $_POST = array(); // Make sure file uploads do not get processed. $_FILES = array(); } diff --git a/includes/menu.inc b/includes/menu.inc index ca37ba50..2b489d88 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -2483,6 +2483,9 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { // untranslated paths). Afterwards, the most relevant path is picked from // the menus, ordered by menu preference. $item = menu_get_item($path); + if ($item === FALSE) { + return FALSE; + } $path_candidates = array(); // 1. The current item href. $path_candidates[$item['href']] = $item['href']; diff --git a/includes/pager.inc b/includes/pager.inc index 17c042d6..316e17d5 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -324,6 +324,16 @@ function theme_pager($variables) { $quantity = empty($variables['quantity']) ? 0 : $variables['quantity']; global $pager_page_array, $pager_total; + // Nothing to do if there is no pager. + if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) { + return; + } + + // Nothing to do if there is only one page. + if ($pager_total[$element] <= 1) { + return; + } + // Calculate various markers within this pager piece: // Middle is used to "center" pages around the current page. $pager_middle = ceil($quantity / 2); @@ -455,6 +465,11 @@ function theme_pager_first($variables) { global $pager_page_array; $output = ''; + // Nothing to do if there is no pager. + if (!isset($pager_page_array[$element])) { + return; + } + // If we are anywhere but the first page if ($pager_page_array[$element] > 0) { $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters)); @@ -485,6 +500,11 @@ function theme_pager_previous($variables) { global $pager_page_array; $output = ''; + // Nothing to do if there is no pager. + if (!isset($pager_page_array[$element])) { + return; + } + // If we are anywhere but the first page if ($pager_page_array[$element] > 0) { $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array); @@ -524,6 +544,11 @@ function theme_pager_next($variables) { global $pager_page_array, $pager_total; $output = ''; + // Nothing to do if there is no pager. + if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) { + return; + } + // If we are anywhere but the last page if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array); @@ -560,6 +585,11 @@ function theme_pager_last($variables) { global $pager_page_array, $pager_total; $output = ''; + // Nothing to do if there is no pager. + if (!isset($pager_page_array[$element]) || !isset($pager_total[$element])) { + return; + } + // If we are anywhere but the last page if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters)); diff --git a/includes/path.inc b/includes/path.inc index 6bd48d30..28d1146a 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -466,13 +466,15 @@ function path_delete($criteria) { $criteria = array('pid' => $criteria); } $path = path_load($criteria); - $query = db_delete('url_alias'); - foreach ($criteria as $field => $value) { - $query->condition($field, $value); + if (isset($path['source'])) { + $query = db_delete('url_alias'); + foreach ($criteria as $field => $value) { + $query->condition($field, $value); + } + $query->execute(); + module_invoke_all('path_delete', $path); + drupal_clear_path_cache($path['source']); } - $query->execute(); - module_invoke_all('path_delete', $path); - drupal_clear_path_cache($path['source']); } /** diff --git a/includes/request-sanitizer.inc b/includes/request-sanitizer.inc index 7214436b..6cd7fd3f 100644 --- a/includes/request-sanitizer.inc +++ b/includes/request-sanitizer.inc @@ -99,7 +99,7 @@ class DrupalRequestSanitizer { protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) { if (is_array($input)) { foreach ($input as $key => $value) { - if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) { + if ($key !== '' && is_string($key) && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) { unset($input[$key]); $sanitized_keys[] = $key; } diff --git a/misc/ajax.js b/misc/ajax.js index c944ebbf..79a4e9eb 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -149,7 +149,7 @@ Drupal.ajax = function (base, element, element_settings) { // The 'this' variable will not persist inside of the options object. var ajax = this; ajax.options = { - url: ajax.url, + url: Drupal.sanitizeAjaxUrl(ajax.url), data: ajax.submit, beforeSerialize: function (element_settings, options) { return ajax.beforeSerialize(element_settings, options); @@ -195,9 +195,29 @@ Drupal.ajax = function (base, element, element_settings) { } }, dataType: 'json', + jsonp: false, type: 'POST' }; + // For multipart forms (e.g., file uploads), jQuery Form targets the form + // submission to an iframe instead of using an XHR object. The initial "src" + // of the iframe, prior to the form submission, is set to options.iframeSrc. + // "about:blank" is the semantically correct, standards-compliant, way to + // initialize a blank iframe; however, some old IE versions (possibly only 6) + // incorrectly report a mixed content warning when iframes with an + // "about:blank" src are added to a parent document with an https:// origin. + // jQuery Form works around this by defaulting to "javascript:false" instead, + // but that breaks on Chrome 83, so here we force the semantically correct + // behavior for all browsers except old IE. + // @see https://www.drupal.org/project/drupal/issues/3143016 + // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68 + // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874 + // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts + // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy + if (navigator.userAgent.indexOf("MSIE") === -1) { + ajax.options.iframeSrc = 'about:blank'; + } + // Bind the ajaxSubmit function to the element event. $(ajax.element).bind(element_settings.event, function (event) { if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) { diff --git a/misc/autocomplete.js b/misc/autocomplete.js index af090713..09ceeec0 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -297,8 +297,9 @@ Drupal.ACDB.prototype.search = function (searchString) { // encodeURIComponent to allow autocomplete search terms to contain slashes. $.ajax({ type: 'GET', - url: db.uri + '/' + Drupal.encodePath(searchString), + url: Drupal.sanitizeAjaxUrl(db.uri + '/' + Drupal.encodePath(searchString)), dataType: 'json', + jsonp: false, success: function (matches) { if (typeof matches.status == 'undefined' || matches.status != 0) { db.cache[searchString] = matches; diff --git a/misc/drupal.js b/misc/drupal.js index 19fbc712..7a3f5f59 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -424,6 +424,23 @@ Drupal.urlIsLocal = function (url) { return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0; }; +/** + * Sanitizes a URL for use with jQuery.ajax(). + * + * @param url + * The URL string to be sanitized. + * + * @return + * The sanitized URL. + */ +Drupal.sanitizeAjaxUrl = function (url) { + var regex = /\=\?(&|$)/; + while (url.match(regex)) { + url = url.replace(regex, ''); + } + return url; +} + /** * Generate the themed representation of a Drupal object. * diff --git a/misc/jquery-html-prefilter-3.5.0-backport.js b/misc/jquery-html-prefilter-3.5.0-backport.js new file mode 100644 index 00000000..93771502 --- /dev/null +++ b/misc/jquery-html-prefilter-3.5.0-backport.js @@ -0,0 +1,251 @@ +/** + * For jQuery versions less than 3.5.0, this replaces the jQuery.htmlPrefilter() + * function with one that fixes these security vulnerabilities while also + * retaining the pre-3.5.0 behavior where it's safe to do so. + * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022 + * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023 + * + * Additionally, for jQuery versions that do not have a jQuery.htmlPrefilter() + * function (1.x prior to 1.12 and 2.x prior to 2.2), this adds it, and + * extends the functions that need to call it to do so. + * + * Drupal core's jQuery version is 1.4.4, but jQuery Update can provide a + * different version, so this covers all versions between 1.4.4 and 3.4.1. + * The GitHub links in the code comments below link to jQuery 1.5 code, because + * 1.4.4 isn't on GitHub, but the referenced code didn't change from 1.4.4 to + * 1.5. + */ + +(function (jQuery) { + + // Parts of this backport differ by jQuery version. + var versionParts = jQuery.fn.jquery.split('.'); + var majorVersion = parseInt(versionParts[0]); + var minorVersion = parseInt(versionParts[1]); + + // No backport is needed if we're already on jQuery 3.5 or higher. + if ( (majorVersion > 3) || (majorVersion === 3 && minorVersion >= 5) ) { + return; + } + + // Prior to jQuery 3.5, jQuery converted XHTML-style self-closing tags to + // their XML equivalent: e.g., "
" to "
". This is + // problematic for several reasons, including that it's vulnerable to XSS + // attacks. However, since this was jQuery's behavior for many years, many + // Drupal modules and jQuery plugins may be relying on it. Therefore, we + // preserve that behavior, but for a limited set of tags only, that we believe + // to not be vulnerable. This is the set of HTML tags that satisfy all of the + // following conditions: + // - In DOMPurify's list of HTML tags. If an HTML tag isn't safe enough to + // appear in that list, then we don't want to mess with it here either. + // @see https://github.com/cure53/DOMPurify/blob/2.0.11/dist/purify.js#L128 + // - A normal element (not a void, template, text, or foreign element). + // @see https://html.spec.whatwg.org/multipage/syntax.html#elements-2 + // - An element that is still defined by the current HTML specification + // (not a deprecated element), because we do not want to rely on how + // browsers parse deprecated elements. + // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element + // - Not 'html', 'head', or 'body', because this pseudo-XHTML expansion is + // designed for fragments, not entire documents. + // - Not 'colgroup', because due to an idiosyncrasy of jQuery's original + // regular expression, it didn't match on colgroup, and we don't want to + // introduce a behavior change for that. + var selfClosingTagsToReplace = [ + 'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', + 'blockquote', 'button', 'canvas', 'caption', 'cite', 'code', 'data', + 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', + 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', + 'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', 'label', 'legend', + 'li', 'main', 'map', 'mark', 'menu', 'meter', 'nav', 'ol', 'optgroup', + 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', + 'ruby', 's', 'samp', 'section', 'select', 'small', 'source', 'span', + 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'time', 'tr', 'u', 'ul', 'var', 'video' + ]; + + // Define regular expressions for and . Doing this as + // two expressions makes it easier to target without also targeting + // every tag that starts with "a". + var xhtmlRegExpGroup = '(' + selfClosingTagsToReplace.join('|') + ')'; + var whitespace = '[\\x20\\t\\r\\n\\f]'; + var rxhtmlTagWithoutSpaceOrAttributes = new RegExp('<' + xhtmlRegExpGroup + '\\/>', 'gi'); + var rxhtmlTagWithSpaceAndMaybeAttributes = new RegExp('<' + xhtmlRegExpGroup + '(' + whitespace + '[^>]*)\\/>', 'gi'); + + // jQuery 3.5 also fixed a vulnerability for when appears within + // an , but it did that in local code that we can't + // backport directly. Instead, we filter such cases out. To do so, we need to + // determine when jQuery would otherwise invoke the vulnerable code, which it + // uses this regular expression to determine. The regular expression changed + // for version 3.0.0 and changed again for 3.4.0. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4958 + // @see https://github.com/jquery/jquery/blob/3.0.0/dist/jquery.js#L4584 + // @see https://github.com/jquery/jquery/blob/3.4.0/dist/jquery.js#L4712 + var rtagName; + if (majorVersion < 3) { + rtagName = /<([\w:]+)/; + } + else if (minorVersion < 4) { + rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]+)/i; + } + else { + rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i; + } + + // The regular expression that jQuery uses to determine which self-closing + // tags to expand to open and close tags. This is vulnerable, because it + // matches all tag names except the few excluded ones. We only use this + // expression for determining vulnerability. The expression changed for + // version 3, but we only need to check for vulnerability in versions 1 and 2, + // so we use the expression from those versions. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4957 + var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + + jQuery.extend({ + htmlPrefilter: function (html) { + // This is how jQuery determines the first tag in the HTML. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5521 + var tag = ( rtagName.exec( html ) || [ "", "" ] )[ 1 ].toLowerCase(); + + // It is not valid HTML for to have