From 2699101722a69e069a8db0c50f32c7acfa920a2a 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 | 2 ++ test/AngularSpec.js | 10 +++++----- test/ng/locationSpec.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 3e9982a54aab..fee3bfa57906 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1154,6 +1154,7 @@ function toKeyValue(obj) { function encodeUriSegment(val) { return encodeUriQuery(val, true). replace(/%26/gi, '&'). + replace(/%3B/gi, ';'). replace(/%3D/gi, '='). replace(/%2B/gi, '+'); } @@ -1176,6 +1177,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 540098353cf6..01d6f5eb7086 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -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(' ')). diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index 7e842e16a749..41c4b3b83f30 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -62,6 +62,39 @@ 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.location = { + href: 'http://localhost:9876/;jsessionid=foo' + }; + return win; + }; + $browserProvider.$get = function($document, $window) { + var sniffer = {history: false, hashchange: true}; + 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/');