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.
This commit is contained in:
Ingo Schommer 2013-01-15 13:27:11 +01:00
parent dadd9e1b98
commit 64d3a3dafc
2 changed files with 85 additions and 54 deletions

View File

@ -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 <base> 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);

View File

@ -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);