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

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

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