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

Commit aee7d53

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 #16592 Closes #16611
1 parent bfdc917 commit aee7d53

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

+108-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,24 @@ 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($location.path()).toEqual('/');
1022+
expect($location.search()).toEqual({q: '\''});
1023+
expect($browserUrl).toHaveBeenCalledTimes(1);
1024+
});
1025+
});
9531026
});
9541027

9551028
});
@@ -1140,6 +1213,40 @@ describe('$location', function() {
11401213
});
11411214
});
11421215

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

0 commit comments

Comments
 (0)