Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit e68697e

Browse files
jbedardNarretz
authored andcommitted
fix($location): fix infinite recursion/digest on URLs with special characters
Some characters are treated differently by `$location` compared to `$browser` and the native browser. When comparing URLs across these two services this must be taken into account. Fixes #16592 Closes #16611
1 parent a02ed88 commit e68697e

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

src/ng/location.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,13 @@ function $LocationProvider() {
865865

866866
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
867867

868+
// Determine if two URLs are equal despite potentially having different encoding/normalizing
869+
// such as $location.absUrl() vs $browser.url()
870+
// See https://github.com/angular/angular.js/issues/16592
871+
function urlsEqual(a, b) {
872+
return a === b || urlResolve(a).href === urlResolve(b).href;
873+
}
874+
868875
function setBrowserUrlWithFallback(url, replace, state) {
869876
var oldUrl = $location.url();
870877
var oldState = $location.$$state;
@@ -981,7 +988,7 @@ function $LocationProvider() {
981988
var newUrl = $location.absUrl();
982989
var oldState = $browser.state();
983990
var currentReplace = $location.$$replace;
984-
var urlOrStateChanged = oldUrl !== newUrl ||
991+
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
985992
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
986993

987994
if (initializing || urlOrStateChanged) {

test/ng/locationSpec.js

+108-1
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,58 @@ describe('$location', function() {
748748
});
749749

750750

751+
//https://github.com/angular/angular.js/issues/16592
752+
it('should not infinitely digest when initial params contain a quote', function() {
753+
initService({html5Mode:true,supportHistory:true});
754+
mockUpBrowser({initialUrl:'http://localhost:9876/?q=\'', baseHref:'/'});
755+
inject(function($location, $browser, $rootScope) {
756+
expect(function() {
757+
$rootScope.$digest();
758+
}).not.toThrow();
759+
});
760+
});
761+
762+
763+
//https://github.com/angular/angular.js/issues/16592
764+
it('should not infinitely digest when initial params contain an escaped quote', function() {
765+
initService({html5Mode:true,supportHistory:true});
766+
mockUpBrowser({initialUrl:'http://localhost:9876/?q=%27', baseHref:'/'});
767+
inject(function($location, $browser, $rootScope) {
768+
expect(function() {
769+
$rootScope.$digest();
770+
}).not.toThrow();
771+
});
772+
});
773+
774+
775+
//https://github.com/angular/angular.js/issues/16592
776+
it('should not infinitely digest when updating params containing a quote (via $browser.url)', function() {
777+
initService({html5Mode:true,supportHistory:true});
778+
mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'});
779+
inject(function($location, $browser, $rootScope) {
780+
$rootScope.$digest();
781+
$browser.url('http://localhost:9876/?q=\'');
782+
expect(function() {
783+
$rootScope.$digest();
784+
}).not.toThrow();
785+
});
786+
});
787+
788+
789+
//https://github.com/angular/angular.js/issues/16592
790+
it('should not infinitely digest when updating params containing a quote (via window.location + popstate)', function() {
791+
initService({html5Mode:true,supportHistory:true});
792+
mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'});
793+
inject(function($window, $location, $browser, $rootScope) {
794+
$rootScope.$digest();
795+
$window.location.href = 'http://localhost:9876/?q=\'';
796+
expect(function() {
797+
jqLite($window).triggerHandler('popstate');
798+
}).not.toThrow();
799+
});
800+
});
801+
802+
751803
describe('when changing the browser URL/history directly during a `$digest`', function() {
752804

753805
beforeEach(function() {
@@ -805,10 +857,13 @@ describe('$location', function() {
805857
});
806858

807859

808-
function updatePathOnLocationChangeSuccessTo(newPath) {
860+
function updatePathOnLocationChangeSuccessTo(newPath, newParams) {
809861
inject(function($rootScope, $location) {
810862
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
811863
$location.path(newPath);
864+
if (newParams) {
865+
$location.search(newParams);
866+
}
812867
});
813868
});
814869
}
@@ -951,6 +1006,24 @@ describe('$location', function() {
9511006
expect($browserUrl).not.toHaveBeenCalled();
9521007
});
9531008
});
1009+
1010+
//https://github.com/angular/angular.js/issues/16592
1011+
it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes query params to contain quote', function() {
1012+
initService({html5Mode: true, supportHistory: true});
1013+
mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'});
1014+
inject(function($rootScope, $injector, $browser) {
1015+
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough();
1016+
1017+
var $location = $injector.get('$location');
1018+
updatePathOnLocationChangeSuccessTo('/', {q: '\''});
1019+
1020+
$rootScope.$digest();
1021+
1022+
expect($location.path()).toEqual('/');
1023+
expect($location.search()).toEqual({q: '\''});
1024+
expect($browserUrl).toHaveBeenCalledTimes(1);
1025+
});
1026+
});
9541027
});
9551028

9561029
});
@@ -1141,6 +1214,40 @@ describe('$location', function() {
11411214
});
11421215
});
11431216

1217+
//https://github.com/angular/angular.js/issues/16592
1218+
it('should not infinite $digest on pushState() with quote in param', function() {
1219+
initService({html5Mode: true, supportHistory: true});
1220+
mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'});
1221+
inject(function($rootScope, $injector, $window) {
1222+
var $location = $injector.get('$location');
1223+
$rootScope.$digest(); //allow $location initialization to finish
1224+
1225+
$window.history.pushState({}, null, 'http://server/app/Home?q=\'');
1226+
$rootScope.$digest();
1227+
1228+
expect($location.absUrl()).toEqual('http://server/app/Home?q=\'');
1229+
expect($location.path()).toEqual('/Home');
1230+
expect($location.search()).toEqual({q: '\''});
1231+
});
1232+
});
1233+
1234+
//https://github.com/angular/angular.js/issues/16592
1235+
it('should not infinite $digest on popstate event with quote in param', function() {
1236+
initService({html5Mode: true, supportHistory: true});
1237+
mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'});
1238+
inject(function($rootScope, $injector, $window) {
1239+
var $location = $injector.get('$location');
1240+
$rootScope.$digest(); //allow $location initialization to finish
1241+
1242+
$window.location.href = 'http://server/app/Home?q=\'';
1243+
jqLite($window).triggerHandler('popstate');
1244+
1245+
expect($location.absUrl()).toEqual('http://server/app/Home?q=\'');
1246+
expect($location.path()).toEqual('/Home');
1247+
expect($location.search()).toEqual({q: '\''});
1248+
});
1249+
});
1250+
11441251
it('should replace browser url & state when replace() was called at least once', function() {
11451252
initService({html5Mode:true, supportHistory: true});
11461253
mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});

0 commit comments

Comments
 (0)