From 64d3a3dafc8d7e3c3b218e9e20d9c0cdacd58043 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 15 Jan 2013 13:27:11 +0100 Subject: [PATCH] BUG Don't double unescape URLs in history.js (fixes #8170) Merged in pull request https://github.com/balupton/history.js/pull/108. This has been a serious problem with the library for more than a year, see https://github.com/balupton/history.js/issues/228. --- .../scripts/uncompressed/history.html4.js | 41 ++++++-- .../scripts/uncompressed/history.js | 98 ++++++++++--------- 2 files changed, 85 insertions(+), 54 deletions(-) diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js b/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js index 709d9b887ed..b64ddf23a86 100644 --- a/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.html4.js @@ -78,6 +78,19 @@ return isLast; }; + /** + * History.isHashEqual(newHash, oldHash) + * Checks to see if two hashes are functionally equal + * @param {string} newHash + * @param {string} oldHash + * @return {boolean} true + */ + History.isHashEqual = function(newHash, oldHash){ + newHash = encodeURIComponent(newHash).replace(/%25/g, "%"); + oldHash = encodeURIComponent(oldHash).replace(/%25/g, "%"); + return newHash === oldHash; + }; + /** * History.saveHash(newHash) * Push a Hash @@ -300,8 +313,9 @@ checkerRunning = true; // Fetch - var documentHash = History.getHash()||'', - iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||''; + var + documentHash = History.getHash(), + iframeHash = History.getHash(iframe.contentWindow.document.location); // The Document Hash has changed (application caused) if ( documentHash !== lastDocumentHash ) { @@ -398,7 +412,7 @@ //History.debug('History.onHashChange', arguments); // Prepare - var currentUrl = ((event && event.newURL) || document.location.href), + var currentUrl = ((event && event.newURL) || History.getLocationHref()), currentHash = History.getHashByUrl(currentUrl), currentState = null, currentStateHash = null, @@ -429,9 +443,10 @@ } // Create State + // MODIFIED ischommer: URL normalization needs to respect our tag, // otherwise will go into infinite loops - currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,true),true); + currentState = History.extractState(History.getFullUrl(currentHash||History.getLocationHref(),true),true); // END MODIFIED // Check if we are the same state @@ -463,7 +478,7 @@ // Push the new HTML5 State //History.debug('History.onHashChange: success hashchange'); - History.pushState(currentState.data,currentState.title,currentState.url,false); + History.pushState(currentState.data,currentState.title,encodeURI(currentState.url),false); // End onHashChange closure return true; @@ -482,6 +497,11 @@ History.pushState = function(data,title,url,queue){ //History.debug('History.pushState: called', arguments); + // We assume that the URL passed in is URI-encoded, but this makes + // sure that it's fully URI encoded; any '%'s that are encoded are + // converted back into '%'s + url = encodeURI(url).replace(/%25/g, "%"); + // Check the State if ( History.getHashByUrl(url) ) { throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); @@ -528,7 +548,7 @@ } // Update HTML4 Hash - if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) { + if ( !History.isHashEqual(newStateHash, html4Hash) && !History.isHashEqual(newStateHash, History.getShortUrl(History.getLocationHref())) ) { //History.debug('History.pushState: update hash', newStateHash, html4Hash); History.setHash(newStateHash,false); return false; @@ -558,9 +578,14 @@ History.replaceState = function(data,title,url,queue){ //History.debug('History.replaceState: called', arguments); + // We assume that the URL passed in is URI-encoded, but this makes + // sure that it's fully URI encoded; any '%'s that are encoded are + // converted back into '%'s + url = encodeURI(url).replace(/%25/g, "%"); + // Check the State if ( History.getHashByUrl(url) ) { - throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).'); } // Handle Queueing @@ -621,4 +646,4 @@ History.init(); } -})(window); +})(window); \ No newline at end of file diff --git a/admin/thirdparty/history-js/scripts/uncompressed/history.js b/admin/thirdparty/history-js/scripts/uncompressed/history.js index 0e7cb1ec6b7..e3a92d71db4 100644 --- a/admin/thirdparty/history-js/scripts/uncompressed/history.js +++ b/admin/thirdparty/history-js/scripts/uncompressed/history.js @@ -416,7 +416,7 @@ // Fetch var State = History.getState(false,false), - stateUrl = (State||{}).url||document.location.href, + stateUrl = (State||{}).url||History.getLocationHref(), pageUrl; // Create @@ -435,7 +435,7 @@ */ History.getBasePageUrl = function(){ // Create - var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ + var basePageUrl = (History.getLocationHref()).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ return (/[^\/]$/).test(part) ? '' : part; }).replace(/\/+$/,'')+'/'; @@ -522,6 +522,36 @@ return shortUrl; }; + /** + * History.getLocationHref(document) + * Returns a normalized version of document.location.href + * accounting for browser inconsistencies, etc. + * + * This URL will be URI-encoded and will include the hash + * + * @param {object} document + * @return {string} url + */ + History.getLocationHref = function(doc) { + doc = doc || document; + + // most of the time, this will be true + if (doc.URL === doc.location.href) + return doc.location.href; + + // some versions of webkit URI-decode document.location.href + // but they leave document.URL in an encoded state + if (doc.location.href === decodeURIComponent(doc.URL)) + return doc.URL; + + // FF 3.6 only updates document.URL when a page is reloaded + // document.location.href is updated correctly + if (doc.location.hash && decodeURIComponent(doc.location.href.replace(/^[^#]+/, "")) === doc.location.hash) + return doc.location.href; + + return doc.URL || doc.location.href; + }; + // ==================================================================== // State Storage @@ -673,7 +703,7 @@ newState = {}; newState.normalized = true; newState.title = oldState.title||''; - newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href)); + newState.url = History.getFullUrl(oldState.url?decodeURIComponent(oldState.url):(History.getLocationHref())); newState.hash = History.getShortUrl(newState.url); newState.data = History.cloneObject(oldState.data); @@ -728,7 +758,7 @@ var State = { 'data': data, 'title': title, - 'url': url + 'url': encodeURIComponent(url||"") }; // Expand the State @@ -1035,36 +1065,15 @@ /** * History.getHash() + * @param {Location=} location * Gets the current document hash + * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers * @return {string} */ - History.getHash = function(){ - var hash = History.unescapeHash(document.location.hash); - return hash; - }; - - /** - * History.unescapeString() - * Unescape a string - * @param {String} str - * @return {string} - */ - History.unescapeString = function(str){ - // Prepare - var result = str, - tmp; - - // Unescape hash - while ( true ) { - tmp = window.unescape(result); - if ( tmp === result ) { - break; - } - result = tmp; - } - - // Return result - return result; + History.getHash = function(location){ + if ( !location ) location = document.location; + var href = location.href.replace( /^[^#]*/, "" ); + return href.substr(1); }; /** @@ -1078,7 +1087,7 @@ var result = History.normalizeHash(hash); // Unescape hash - result = History.unescapeString(result); + result = decodeURIComponent(result); // Return result return result; @@ -1105,7 +1114,7 @@ */ History.setHash = function(hash,queue){ // Prepare - var adjustedHash, State, pageUrl; + var State, pageUrl; // Handle Queueing if ( queue !== false && History.busy() ) { @@ -1123,9 +1132,6 @@ // Log //History.debug('History.setHash: called',hash); - // Prepare - adjustedHash = History.escapeHash(hash); - // Make Busy + Continue History.busy(true); @@ -1138,7 +1144,7 @@ // PushState History.pushState(State.data,State.title,State.url,false); } - else if ( document.location.hash !== adjustedHash ) { + else if ( History.getHash() !== hash ) { // Hash is a proper hash, so apply it // Handle browser bugs @@ -1149,11 +1155,11 @@ pageUrl = History.getPageUrl(); // Safari hash apply - History.pushState(null,null,pageUrl+'#'+adjustedHash,false); + History.pushState(null,null,pageUrl+'#'+hash,false); } else { // Normal hash apply - document.location.hash = adjustedHash; + document.location.hash = hash; } } @@ -1171,7 +1177,7 @@ var result = History.normalizeHash(hash); // Escape hash - result = window.escape(result); + result = window.encodeURIComponent(result); // IE6 Escape Bug if ( !History.bugs.hashEscape ) { @@ -1446,7 +1452,7 @@ // Get the Last State which has the new URL var - urlState = History.extractState(document.location.href), + urlState = History.extractState(History.getLocationHref()), newState; // Check for a difference @@ -1617,7 +1623,7 @@ currentHash = History.getHash(); if ( currentHash ) { // Expand Hash - currentState = History.extractState(currentHash||document.location.href,true); + currentState = History.extractState(currentHash||History.getLocationHref(),true); if ( currentState ) { // We were able to parse it, it must be a State! // Let's forward to replaceState @@ -1650,13 +1656,13 @@ } else { // Initial State - newState = History.extractState(document.location.href); + newState = History.extractState(History.getLocationHref()); } // The State did not exist in our store if ( !newState ) { // Regenerate the State - newState = History.createStateObject(null,null,document.location.href); + newState = History.createStateObject(null,null,History.getLocationHref()); } // Clean @@ -1836,7 +1842,7 @@ /** * Create the initial State */ - History.saveState(History.storeState(History.extractState(document.location.href,true))); + History.saveState(History.storeState(History.extractState(History.getLocationHref(),true))); /** * Bind for Saving Store @@ -1940,4 +1946,4 @@ // Try and Initialise History History.init(); -})(window); +})(window); \ No newline at end of file