diff options
author | David Rothstein <drothstein@gmail.com> | 2015-08-19 17:20:31 -0400 |
---|---|---|
committer | David Rothstein <drothstein@gmail.com> | 2015-08-19 17:20:31 -0400 |
commit | be00a1ced4104d84df2f34b149b35fb0adf91093 (patch) | |
tree | 57eb4bdd551ef892671c5d7d653a78fdd3f3d454 /misc | |
parent | 5cb79b4b217e9aa315d61284398cce132c28bea4 (diff) | |
download | brdo-be00a1ced4104d84df2f34b149b35fb0adf91093.tar.gz brdo-be00a1ced4104d84df2f34b149b35fb0adf91093.tar.bz2 |
Drupal 7.39
Diffstat (limited to 'misc')
-rw-r--r-- | misc/ajax.js | 40 | ||||
-rw-r--r-- | misc/autocomplete.js | 7 | ||||
-rw-r--r-- | misc/drupal.js | 73 |
3 files changed, 110 insertions, 10 deletions
diff --git a/misc/ajax.js b/misc/ajax.js index 01b894d75..bb4a6e14f 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -14,6 +14,8 @@ Drupal.ajax = Drupal.ajax || {}; +Drupal.settings.urlIsAjaxTrusted = Drupal.settings.urlIsAjaxTrusted || {}; + /** * Attaches the Ajax behavior to each Ajax form element. */ @@ -130,6 +132,11 @@ Drupal.ajax = function (base, element, element_settings) { // 5. /nojs# - Followed by a fragment. // E.g.: path/nojs#myfragment this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); + // If the 'nojs' version of the URL is trusted, also trust the 'ajax' version. + if (Drupal.settings.urlIsAjaxTrusted[element_settings.url]) { + Drupal.settings.urlIsAjaxTrusted[this.url] = true; + } + this.wrapper = '#' + element_settings.wrapper; // If there isn't a form, jQuery.ajax() will be used instead, allowing us to @@ -155,18 +162,36 @@ Drupal.ajax = function (base, element, element_settings) { ajax.ajaxing = true; return ajax.beforeSend(xmlhttprequest, options); }, - success: function (response, status) { + success: function (response, status, xmlhttprequest) { // Sanity check for browser support (object expected). // When using iFrame uploads, responses must be returned as a string. if (typeof response == 'string') { response = $.parseJSON(response); } + + // Prior to invoking the response's commands, verify that they can be + // trusted by checking for a response header. See + // ajax_set_verification_header() for details. + // - Empty responses are harmless so can bypass verification. This avoids + // an alert message for server-generated no-op responses that skip Ajax + // rendering. + // - Ajax objects with trusted URLs (e.g., ones defined server-side via + // #ajax) can bypass header verification. This is especially useful for + // Ajax with multipart forms. Because IFRAME transport is used, the + // response headers cannot be accessed for verification. + if (response !== null && !Drupal.settings.urlIsAjaxTrusted[ajax.url]) { + if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') { + var customMessage = Drupal.t("The response failed verification so will not be processed."); + return ajax.error(xmlhttprequest, ajax.url, customMessage); + } + } + return ajax.success(response, status); }, - complete: function (response, status) { + complete: function (xmlhttprequest, status) { ajax.ajaxing = false; if (status == 'error' || status == 'parsererror') { - return ajax.error(response, ajax.url); + return ajax.error(xmlhttprequest, ajax.url); } }, dataType: 'json', @@ -175,6 +200,9 @@ Drupal.ajax = function (base, element, element_settings) { // 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)) { + throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url})); + } return ajax.eventResponse(this, event); }); @@ -447,8 +475,8 @@ Drupal.ajax.prototype.getEffect = function (response) { /** * Handler for the form redirection error. */ -Drupal.ajax.prototype.error = function (response, uri) { - alert(Drupal.ajaxError(response, uri)); +Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { + alert(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); @@ -462,7 +490,7 @@ Drupal.ajax.prototype.error = function (response, uri) { $(this.element).removeClass('progress-disabled').removeAttr('disabled'); // Reattach behaviors, if they were detached in beforeSerialize(). if (this.form) { - var settings = response.settings || this.settings || Drupal.settings; + var settings = this.settings || Drupal.settings; Drupal.attachBehaviors(this.form, settings); } }; diff --git a/misc/autocomplete.js b/misc/autocomplete.js index 567908170..d71441b6c 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -271,8 +271,11 @@ Drupal.ACDB.prototype.search = function (searchString) { var db = this; this.searchString = searchString; - // See if this string needs to be searched for anyway. - searchString = searchString.replace(/^\s+|\s+$/, ''); + // See if this string needs to be searched for anyway. The pattern ../ is + // stripped since it may be misinterpreted by the browser. + searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, ''); + // Skip empty search strings, or search strings ending with a comma, since + // that is the separator between search terms. if (searchString.length <= 0 || searchString.charAt(searchString.length - 1) == ',') { return; diff --git a/misc/drupal.js b/misc/drupal.js index 643baa1bf..427c4a1e2 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -270,6 +270,72 @@ Drupal.formatPlural = function (count, singular, plural, args, options) { }; /** + * 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 @@ -350,7 +416,7 @@ Drupal.getSelection = function (element) { /** * Build an error message from an Ajax response. */ -Drupal.ajaxError = function (xmlhttp, uri) { +Drupal.ajaxError = function (xmlhttp, uri, customMessage) { var statusCode, statusText, pathText, responseText, readyStateText, message; if (xmlhttp.status) { statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") + "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status}); @@ -383,7 +449,10 @@ Drupal.ajaxError = function (xmlhttp, uri) { // We don't need readyState except for status == 0. readyStateText = xmlhttp.status == 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : ""; - message = statusCode + pathText + statusText + responseText + readyStateText; + // Additional message beyond what the xmlhttp object provides. + customMessage = customMessage ? ("\n" + Drupal.t("CustomMessage: !customMessage", {'!customMessage': customMessage})) : ""; + + message = statusCode + pathText + statusText + customMessage + responseText + readyStateText; return message; }; |