From ae3ef064f7ef4e58c43e97380fd5b4cc722063ec Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 28 Jul 2014 09:57:11 -0700 Subject: [PATCH] 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 --- src/Angular.js | 1 + test/AngularSpec.js | 10 +++++----- test/ng/locationSpec.js | 42 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 9d047ae04643..5d5eaa4a95b4 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1191,6 +1191,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). + replace(/%3B/gi, ';'). replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 148f80f29a80..5373ac4691c8 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -659,16 +659,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;'); }); }); @@ -690,7 +690,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(' ')). diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index 7e842e16a749..e586ad744b6a 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -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/');