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

Commit fcd457f

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 0e26197 commit fcd457f

File tree

2 files changed

+119
-2
lines changed

2 files changed

+119
-2
lines changed

src/ng/location.js

Lines changed: 8 additions & 1 deletion
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

Lines changed: 111 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy