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 #5019
This commit is contained in:
Jeff Cross
2014-07-28 09:57:11 -07:00
parent ca0f59e677
commit 3625803349
3 changed files with 48 additions and 5 deletions
+1
View File
@@ -1176,6 +1176,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%3B/gi, ';').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
+5 -5
View File
@@ -668,16 +668,16 @@ describe('angular', function() {
toEqual('asdf1234asdf');
//don't encode unreserved'
expect(encodeUriSegment("-_.!~*'() -_.!~*'()")).
toEqual("-_.!~*'()%20-_.!~*'()");
expect(encodeUriSegment("-_.!~*'(); -_.!~*'();")).
toEqual("-_.!~*'();%20-_.!~*'();");
//don't encode the rest of pchar'
expect(encodeUriSegment(':@&=+$, :@&=+$,')).
toEqual(':@&=+$,%20:@&=+$,');
//encode '/', ';' and ' ''
//encode '/' and ' ''
expect(encodeUriSegment('/; /;')).
toEqual('%2F%3B%20%2F%3B');
toEqual('%2F;%20%2F;');
});
});
@@ -699,7 +699,7 @@ describe('angular', function() {
//encode '&', ';', '=', '+', and '#'
expect(encodeUriQuery('&;=+# &;=+#')).
toEqual('%26%3B%3D%2B%23+%26%3B%3D%2B%23');
toEqual('%26;%3D%2B%23+%26;%3D%2B%23');
//encode ' ' as '+'
expect(encodeUriQuery(' ')).
+42
View File
@@ -62,6 +62,48 @@ describe('$location', function() {
});
it('should not infinitely digest when using a semicolon in initial path', function() {
module(function($windowProvider, $locationProvider, $browserProvider) {
$locationProvider.html5Mode(true);
$windowProvider.$get = function() {
var win = {};
angular.extend(win, window);
win.addEventListener = angular.noop;
win.removeEventListener = angular.noop;
win.history = {
replaceState: angular.noop,
pushState: angular.noop
};
win.location = {
href: 'http://localhost:9876/;jsessionid=foo',
replace: function(val) {
win.location.href = val;
}
};
return win;
};
$browserProvider.$get = function($document, $window) {
var sniffer = {history: true, hashchange: false};
var logs = {log:[], warn:[], info:[], error:[]};
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
warn: function() { logs.warn.push(slice.call(arguments)); },
info: function() { logs.info.push(slice.call(arguments)); },
error: function() { logs.error.push(slice.call(arguments)); }};
/* global Browser: false */
var b = new Browser($window, $document, fakeLog, sniffer);
b.pollFns = [];
return b;
};
});
var self = this;
inject(function($location, $browser, $rootScope) {
expect(function() {
$rootScope.$digest();
}).not.toThrow();
});
});
describe('NewUrl', function() {
beforeEach(function() {
url = new LocationHtml5Url('http://www.domain.com:9877/');