Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 6593a3e

Browse files
mheveryIgorMinar
authored andcommitted
fix($location): fix URL interception in hash-bang mode
Closes #1051
1 parent 0f44964 commit 6593a3e

File tree

3 files changed

+126
-34
lines changed

3 files changed

+126
-34
lines changed

src/ng/location.js

+65-31
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ function encodePath(path) {
2323
return segments.join('/');
2424
}
2525

26+
function stripHash(url) {
27+
return url.split('#')[0];
28+
}
29+
2630

2731
function matchUrl(url, obj) {
2832
var match = URL_MATCH.exec(url);
@@ -102,19 +106,19 @@ function convertToHashbangUrl(url, basePath, hashPrefix) {
102106
* @param {string} url HTML5 url
103107
* @param {string} pathPrefix
104108
*/
105-
function LocationUrl(url, pathPrefix) {
109+
function LocationUrl(url, pathPrefix, appBaseUrl) {
106110
pathPrefix = pathPrefix || '';
107111

108112
/**
109113
* Parse given html5 (regular) url string into properties
110-
* @param {string} url HTML5 url
114+
* @param {string} newAbsoluteUrl HTML5 url
111115
* @private
112116
*/
113-
this.$$parse = function(url) {
114-
var match = matchUrl(url, this);
117+
this.$$parse = function(newAbsoluteUrl) {
118+
var match = matchUrl(newAbsoluteUrl, this);
115119

116120
if (match.path.indexOf(pathPrefix) !== 0) {
117-
throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
121+
throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
118122
}
119123

120124
this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
@@ -137,6 +141,14 @@ function LocationUrl(url, pathPrefix) {
137141
pathPrefix + this.$$url;
138142
};
139143

144+
145+
this.$$rewriteAppUrl = function(absoluteLinkUrl) {
146+
if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
147+
return absoluteLinkUrl;
148+
}
149+
}
150+
151+
140152
this.$$parse(url);
141153
}
142154

@@ -149,7 +161,7 @@ function LocationUrl(url, pathPrefix) {
149161
* @param {string} url Legacy url
150162
* @param {string} hashPrefix Prefix for hash part (containing path and search)
151163
*/
152-
function LocationHashbangUrl(url, hashPrefix) {
164+
function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
153165
var basePath;
154166

155167
/**
@@ -192,6 +204,13 @@ function LocationHashbangUrl(url, hashPrefix) {
192204
basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
193205
};
194206

207+
this.$$rewriteAppUrl = function(absoluteLinkUrl) {
208+
if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
209+
return absoluteLinkUrl;
210+
}
211+
}
212+
213+
195214
this.$$parse(url);
196215
}
197216

@@ -380,6 +399,19 @@ LocationUrl.prototype = {
380399

381400
LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
382401

402+
function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
403+
LocationHashbangUrl.apply(this, arguments);
404+
405+
406+
this.$$rewriteAppUrl = function(absoluteLinkUrl) {
407+
if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
408+
return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
409+
}
410+
}
411+
}
412+
413+
LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
414+
383415
function locationGetter(property) {
384416
return function() {
385417
return this[property];
@@ -479,26 +511,33 @@ function $LocationProvider(){
479511
basePath,
480512
pathPrefix,
481513
initUrl = $browser.url(),
482-
absUrlPrefix;
514+
initUrlParts = matchUrl(initUrl),
515+
appBaseUrl;
483516

484517
if (html5Mode) {
485518
basePath = $browser.baseHref() || '/';
486519
pathPrefix = pathPrefixFromBase(basePath);
520+
appBaseUrl =
521+
composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
522+
pathPrefix + '/';
523+
487524
if ($sniffer.history) {
488525
$location = new LocationUrl(
489526
convertToHtml5Url(initUrl, basePath, hashPrefix),
490-
pathPrefix);
527+
pathPrefix, appBaseUrl);
491528
} else {
492-
$location = new LocationHashbangUrl(
529+
$location = new LocationHashbangInHtml5Url(
493530
convertToHashbangUrl(initUrl, basePath, hashPrefix),
494-
hashPrefix);
531+
hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
495532
}
496-
// link rewriting
497-
absUrlPrefix = composeProtocolHostPort(
498-
$location.protocol(), $location.host(), $location.port()) + pathPrefix;
499533
} else {
500-
$location = new LocationHashbangUrl(initUrl, hashPrefix);
501-
absUrlPrefix = $location.absUrl().split('#')[0];
534+
appBaseUrl =
535+
composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
536+
(initUrlParts.path || '') +
537+
(initUrlParts.search ? ('?' + initUrlParts.search) : '') +
538+
'#' + hashPrefix + '/';
539+
540+
$location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
502541
}
503542

504543
$rootElement.bind('click', function(event) {
@@ -510,27 +549,22 @@ function $LocationProvider(){
510549
var elm = jqLite(event.target);
511550

512551
// traverse the DOM up to find first A tag
513-
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
552+
while (lowercase(elm[0].nodeName) !== 'a') {
553+
if (elm[0] === $rootElement[0]) return;
514554
elm = elm.parent();
515555
}
516556

517557
var absHref = elm.prop('href'),
518-
href;
519-
520-
if (!absHref ||
521-
elm.attr('target') ||
522-
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
523-
return;
558+
rewrittenUrl = $location.$$rewriteAppUrl(absHref);
559+
560+
if (absHref && !elm.attr('target') && rewrittenUrl) {
561+
// update location manually
562+
$location.$$parse(rewrittenUrl);
563+
$rootScope.$apply();
564+
event.preventDefault();
565+
// hack to work around FF6 bug 684208 when scenario runner clicks on links
566+
window.angular['ff-684208-preventDefault'] = true;
524567
}
525-
526-
// update location with href without the prefix
527-
href = absHref.substr(absUrlPrefix.length);
528-
if (href.indexOf('#' + hashPrefix) == 0) href = href.substr(hashPrefix.length + 1);
529-
$location.url(href);
530-
$rootScope.$apply();
531-
event.preventDefault();
532-
// hack to work around FF6 bug 684208 when scenario runner clicks on links
533-
window.angular['ff-684208-preventDefault'] = true;
534568
});
535569

536570

src/ngMock/angular-mocks.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ angular.mock.$Browser = function() {
3939
var self = this;
4040

4141
this.isMock = true;
42-
self.$$url = "http://server";
42+
self.$$url = "http://server/";
4343
self.$$lastUrl = self.$$url; // used by url polling fn
4444
self.pollFns = [];
4545

test/ng/locationSpec.js

+60-2
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,64 @@ describe('$location', function() {
10291029
expect($browser.url()).toEqual(base + '#!/view2');
10301030
});
10311031
});
1032+
1033+
1034+
it('should not intercept link clicks outside the app base url space', function() {
1035+
var base, clickHandler;
1036+
module(function($provide) {
1037+
$provide.value('$rootElement', {
1038+
bind: function(event, handler) {
1039+
expect(event).toEqual('click');
1040+
clickHandler = handler;
1041+
}
1042+
});
1043+
return function($browser) {
1044+
$browser.url(base = 'http://server/');
1045+
}
1046+
});
1047+
inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) {
1048+
// make IE happy
1049+
jqLite(window.document.body).html('<a href="http://server/test.html">link</a>');
1050+
1051+
var event = {
1052+
target: jqLite(window.document.body).find('a')[0],
1053+
preventDefault: jasmine.createSpy('preventDefault')
1054+
};
1055+
1056+
1057+
clickHandler(event);
1058+
expect(event.preventDefault).not.toHaveBeenCalled();
1059+
});
1060+
});
1061+
1062+
1063+
it('should not intercept hash link clicks outside the app base url space', function() {
1064+
var base, clickHandler;
1065+
module(function($provide) {
1066+
$provide.value('$rootElement', {
1067+
bind: function(event, handler) {
1068+
expect(event).toEqual('click');
1069+
clickHandler = handler;
1070+
}
1071+
});
1072+
return function($browser) {
1073+
$browser.url(base = 'http://server/');
1074+
}
1075+
});
1076+
inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) {
1077+
// make IE happy
1078+
jqLite(window.document.body).html('<a href="http://server/index.html#test">link</a>');
1079+
1080+
var event = {
1081+
target: jqLite(window.document.body).find('a')[0],
1082+
preventDefault: jasmine.createSpy('preventDefault')
1083+
};
1084+
1085+
1086+
clickHandler(event);
1087+
expect(event.preventDefault).not.toHaveBeenCalled();
1088+
});
1089+
});
10321090
});
10331091

10341092

@@ -1111,7 +1169,7 @@ describe('$location', function() {
11111169
);
11121170

11131171

1114-
it('should listen on click events on href and prevent browser default in hasbang mode', function() {
1172+
it('should listen on click events on href and prevent browser default in hashbang mode', function() {
11151173
module(function() {
11161174
return function($rootElement, $compile, $rootScope) {
11171175
$rootElement.html('<a href="http://server/#/somePath">link</a>');
@@ -1162,7 +1220,7 @@ describe('$location', function() {
11621220
log += '$locationChangeStart';
11631221
});
11641222
$rootScope.$on('$locationChangeSuccess', function() {
1165-
throw new Error('after cancalation in html5 mode');
1223+
throw new Error('after cancelation in html5 mode');
11661224
});
11671225

11681226
browserTrigger(link, 'click');

0 commit comments

Comments
 (0)