Skip to content

Commit ae3ef06

Browse files
committed
fix($location): add semicolon to whitelist of delimiters to unencode
Some servers require characters within path segments to contain semicolons, such as `/;jsessionid=foo` in order to work correctly. RFC-3986 includes semicolons as acceptable sub-delimiters inside of path and query, but $location currently encodes semicolons. This can cause an infinite digest to occur since $location is comparing the internal semicolon-encoded url with the semicolon-unencoded url returned from window.location.href, causing Angular to believe the url is changing with each digest loop. This fix adds ";" to the list of characters to unencode after encoding queries or path segments. Closes angular#5019
1 parent afe93ea commit ae3ef06

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/Angular.js

+1
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
11911191
replace(/%3A/gi, ':').
11921192
replace(/%24/g, '$').
11931193
replace(/%2C/gi, ',').
1194+
replace(/%3B/gi, ';').
11941195
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
11951196
}
11961197

test/AngularSpec.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -659,16 +659,16 @@ describe('angular', function() {
659659
toEqual('asdf1234asdf');
660660

661661
//don't encode unreserved'
662-
expect(encodeUriSegment("-_.!~*'() -_.!~*'()")).
663-
toEqual("-_.!~*'()%20-_.!~*'()");
662+
expect(encodeUriSegment("-_.!~*'(); -_.!~*'();")).
663+
toEqual("-_.!~*'();%20-_.!~*'();");
664664

665665
//don't encode the rest of pchar'
666666
expect(encodeUriSegment(':@&=+$, :@&=+$,')).
667667
toEqual(':@&=+$,%20:@&=+$,');
668668

669-
//encode '/', ';' and ' ''
669+
//encode '/' and ' ''
670670
expect(encodeUriSegment('/; /;')).
671-
toEqual('%2F%3B%20%2F%3B');
671+
toEqual('%2F;%20%2F;');
672672
});
673673
});
674674

@@ -690,7 +690,7 @@ describe('angular', function() {
690690

691691
//encode '&', ';', '=', '+', and '#'
692692
expect(encodeUriQuery('&;=+# &;=+#')).
693-
toEqual('%26%3B%3D%2B%23+%26%3B%3D%2B%23');
693+
toEqual('%26;%3D%2B%23+%26;%3D%2B%23');
694694

695695
//encode ' ' as '+'
696696
expect(encodeUriQuery(' ')).

test/ng/locationSpec.js

+42
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,48 @@ describe('$location', function() {
6262
});
6363

6464

65+
it('should not infinitely digest when using a semicolon in initial path', function() {
66+
module(function($windowProvider, $locationProvider, $browserProvider) {
67+
$locationProvider.html5Mode(true);
68+
$windowProvider.$get = function() {
69+
var win = {};
70+
angular.extend(win, window);
71+
win.addEventListener = angular.noop;
72+
win.removeEventListener = angular.noop;
73+
win.history = {
74+
replaceState: angular.noop,
75+
pushState: angular.noop
76+
};
77+
win.location = {
78+
href: 'http://localhost:9876/;jsessionid=foo',
79+
replace: function(val) {
80+
win.location.href = val;
81+
}
82+
};
83+
return win;
84+
};
85+
$browserProvider.$get = function($document, $window) {
86+
var sniffer = {history: true, hashchange: false};
87+
var logs = {log:[], warn:[], info:[], error:[]};
88+
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
89+
warn: function() { logs.warn.push(slice.call(arguments)); },
90+
info: function() { logs.info.push(slice.call(arguments)); },
91+
error: function() { logs.error.push(slice.call(arguments)); }};
92+
93+
/* global Browser: false */
94+
var b = new Browser($window, $document, fakeLog, sniffer);
95+
b.pollFns = [];
96+
return b;
97+
};
98+
});
99+
var self = this;
100+
inject(function($location, $browser, $rootScope) {
101+
expect(function() {
102+
$rootScope.$digest();
103+
}).not.toThrow();
104+
});
105+
});
106+
65107
describe('NewUrl', function() {
66108
beforeEach(function() {
67109
url = new LocationHtml5Url('http://www.domain.com:9877/');

0 commit comments

Comments
 (0)