Skip to content

Commit c9adf16

Browse files
committed
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 angular#16592 Closes angular#16611
1 parent 0e26197 commit c9adf16

File tree

2 files changed

+119
-2
lines changed

2 files changed

+119
-2
lines changed

src/ng/location.js

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

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

882+
// Determine if two URLs are equal despite potentially having different encoding/normalizing
883+
// such as $location.absUrl() vs $browser.url()
884+
// See https://github.com/angular/angular.js/issues/16592
885+
function urlsEqual(a, b) {
886+
return a === b || urlResolve(a).href === urlResolve(b).href;
887+
}
888+
882889
function setBrowserUrlWithFallback(url, replace, state) {
883890
var oldUrl = $location.url();
884891
var oldState = $location.$$state;
@@ -996,7 +1003,7 @@ function $LocationProvider() {
9961003
var newUrl = trimEmptyHash($location.absUrl());
9971004
var oldState = $browser.state();
9981005
var currentReplace = $location.$$replace;
999-
var urlOrStateChanged = oldUrl !== newUrl ||
1006+
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||
10001007
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
10011008

10021009
if (initializing || urlOrStateChanged) {

test/ng/locationSpec.js

+111-1
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,58 @@ describe('$location', function() {
747747
});
748748

749749

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

752804
beforeEach(function() {
@@ -804,10 +856,13 @@ describe('$location', function() {
804856
});
805857

806858

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

9551029
});
@@ -1140,6 +1214,42 @@ describe('$location', function() {
11401214
});
11411215
});
11421216

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, $browser, $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($browser.url()).toEqual('http://server/app/Home?q=%27');
1229+
expect($location.absUrl()).toEqual('http://server/app/Home?q=\'');
1230+
expect($location.path()).toEqual('/Home');
1231+
expect($location.search()).toEqual({q: '\''});
1232+
});
1233+
});
1234+
1235+
//https://github.com/angular/angular.js/issues/16592
1236+
it('should not infinite $digest on popstate event with quote in param', function() {
1237+
initService({html5Mode: true, supportHistory: true});
1238+
mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'});
1239+
inject(function($rootScope, $injector, $browser, $window) {
1240+
var $location = $injector.get('$location');
1241+
$rootScope.$digest(); //allow $location initialization to finish
1242+
1243+
$window.location.href = 'http://server/app/Home?q=\'';
1244+
jqLite($window).triggerHandler('popstate');
1245+
1246+
expect($browser.url()).toEqual('http://server/app/Home?q=%27');
1247+
expect($location.absUrl()).toEqual('http://server/app/Home?q=\'');
1248+
expect($location.path()).toEqual('/Home');
1249+
expect($location.search()).toEqual({q: '\''});
1250+
});
1251+
});
1252+
11431253
it('should replace browser url & state when replace() was called at least once', function() {
11441254
initService({html5Mode:true, supportHistory: true});
11451255
mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});

0 commit comments

Comments
 (0)