diff --git a/includes/common.inc b/includes/common.inc index 9a28c06..a5c362d 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1499,7 +1499,7 @@ function url($path = NULL, $options = array()) { ); if (!isset($options['external'])) { - $options['external'] = menu_path_is_external($path); + $options['external'] = $_GET['q'] === $path ? FALSE : menu_path_is_external($path); } // May need language dependent rewriting if language.inc is present. diff --git a/misc/drupal.js b/misc/drupal.js index a85b8f8..fd68051 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -20,6 +20,42 @@ return jquery_init.call(this, selector, context, rootjQuery); }; jQuery.fn.init.prototype = jquery_init.prototype; + + /** + * Pre-filter Ajax requests to guard against XSS attacks. + * + * See https://github.com/jquery/jquery/issues/2432 + */ + if ($.ajaxPrefilter) { + // For newer versions of jQuery, use an Ajax prefilter to prevent + // auto-executing script tags from untrusted domains. This is similar to the + // fix that is built in to jQuery 3.0 and higher. + $.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); + } + else if ($.httpData) { + // For the version of jQuery that ships with Drupal core, override + // jQuery.httpData to prevent auto-detecting "script" data types from + // untrusted domains. + var jquery_httpData = $.httpData; + $.httpData = function (xhr, type, s) { + // @todo Consider backporting code from newer jQuery versions to check for + // a cross-domain request here, rather than using Drupal.urlIsLocal() to + // block scripts from all URLs that are not on the same site. + if (!type && (!s || !Drupal.urlIsLocal(s.url))) { + var content_type = xhr.getResponseHeader('content-type') || ''; + if (content_type.indexOf('javascript') >= 0) { + // Default to a safe data type. + type = 'text'; + } + } + return jquery_httpData.call(this, xhr, type, s); + }; + $.httpData.prototype = jquery_httpData.prototype; + } })(); var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} }; @@ -71,7 +107,7 @@ Drupal.attachBehaviors = function(context) { */ Drupal.checkPlain = function(str) { str = String(str); - var replace = { '&': '&', '"': '"', '<': '<', '>': '>' }; + var replace = { '&': '&', "'": ''', '"': '"', '<': '<', '>': '>' }; for (var character in replace) { var regex = new RegExp(character, 'g'); str = str.replace(regex, replace[character]); @@ -177,6 +213,72 @@ Drupal.formatPlural = function(count, singular, plural, args) { }; /** + * Returns the passed in URL as an absolute URL. + * + * @param url + * The URL string to be normalized to an absolute URL. + * + * @return + * The normalized, absolute URL. + * + * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js + * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript + * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53 + */ +Drupal.absoluteUrl = function (url) { + var urlParsingNode = document.createElement('a'); + + // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8 + // strings may throw an exception. + try { + url = decodeURIComponent(url); + } catch (e) {} + + urlParsingNode.setAttribute('href', url); + + // IE <= 7 normalizes the URL when assigned to the anchor node similar to + // the other browsers. + return urlParsingNode.cloneNode(false).href; +}; + +/** + * Returns true if the URL is within Drupal's base path. + * + * @param url + * The URL string to be tested. + * + * @return + * Boolean true if local. + * + * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58 + */ +Drupal.urlIsLocal = function (url) { + // Always use browser-derived absolute URLs in the comparison, to avoid + // attempts to break out of the base path using directory traversal. + var absoluteUrl = Drupal.absoluteUrl(url); + var protocol = location.protocol; + + // Consider URLs that match this site's base URL but use HTTPS instead of HTTP + // as local as well. + if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) { + protocol = 'https:'; + } + var baseUrl = protocol + '//' + location.host + Drupal.settings.basePath.slice(0, -1); + + // Decoding non-UTF-8 strings may throw an exception. + try { + absoluteUrl = decodeURIComponent(absoluteUrl); + } catch (e) {} + try { + baseUrl = decodeURIComponent(baseUrl); + } catch (e) {} + + // The given URL matches the site's base URL, or has a path under the site's + // base URL. + return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0; +}; + +/** * Generate the themed representation of a Drupal object. * * All requests for themed output must go through this function. It examines