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

fix($location): links should respect absolute paths and base href when in html5mode #9126

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 69 additions & 17 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {

if ( appBase == stripHash(url) ) {
return url;
} else if ( (appUrl = beginsWith(appBaseNoFile + hashPrefix + '/', url)) ) {
return appBase + hashPrefix + appUrl;
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
return appBase + hashPrefix + appUrl;
} else if ( appBaseNoFile === url + '/') {
Expand Down Expand Up @@ -636,7 +638,7 @@ function $LocationProvider(){
LocationMode = LocationHashbangUrl;
}
$location = new LocationMode(appBase, '#' + hashPrefix);
$location.$$parse($location.$$rewrite(initialUrl));
$location.$$parse($location.$$rewrite(initialUrl) || appBase);

var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;

Expand Down Expand Up @@ -675,26 +677,22 @@ function $LocationProvider(){

if (href && href.indexOf('://') < 0) { // Ignore absolute URLs
var prefix = '#' + hashPrefix;
if (href[0] == '/') {
// absolute path - replace old path
absHref = appBase + prefix + href;
var appBaseNoFile = stripFile(appBase);
var html5Url = appBase + $location.url().substr(1);
var baseUri = baseHref ? urlResolveShim(baseHref, appBase) : html5Url;
var appUrl;
if (appUrl = beginsWith(prefix + '/', href)) {
// hashbang beginning with / refers to appbase
absHref = urlResolveShim(appBaseNoFile + appUrl);
} else if (appUrl = beginsWith(prefix, href)) {
// hashbang relative path
absHref = urlResolveShim(appUrl, baseUri);
} else if (href[0] == '#') {
// local anchor
absHref = appBase + prefix + ($location.path() || '/') + href;
} else {
// relative path - join with current path
var stack = $location.path().split("/"),
parts = href.split("/");
if (stack.length === 2 && !stack[1]) stack.length = 1;
for (var i=0; i<parts.length; i++) {
if (parts[i] == ".")
continue;
else if (parts[i] == "..")
stack.pop();
else if (parts[i].length)
stack.push(parts[i]);
}
absHref = appBase + prefix + stack.join('/');
// relative path
absHref = urlResolveShim(href, baseUri);
}
}
}
Expand Down Expand Up @@ -766,5 +764,59 @@ function $LocationProvider(){
function afterLocationChange(oldUrl) {
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
}

// Similar to URL(URL, base) of URLUtils
function urlResolveShim(url, base) {
var absHref;
if (url.indexOf('//') >= 0) {
absHref = url;
} else if (url.charAt(0) === '/') {
absHref = serverBase(base) + url;
} else {
absHref = stripFile(base) + url;
}

var resolvedUrl = urlResolve(absHref);
var hash = resolvedUrl.hash ? '#' + resolvedUrl.hash : '';
var search = resolvedUrl.search;
var path = resolvedUrl.pathname;
var normalizedPath = normalizePath(path); // only for IE7 compatibility
return serverBase(resolvedUrl.href) + normalizedPath + (search ? '?' + search : '') + hash;
}

function normalizePath(path) {
path = path || '';
var inputSegments = path.split('/');
var outputSegments = [];
var inputSegment;
for (var i = 0; i < inputSegments.length; i++) {
inputSegment = inputSegments[i];

if ((inputSegment.length === 0)
|| (inputSegment == '.')) {
// Do nothing
continue;
} else if (inputSegment == '..') {
if (outputSegments.length) {
outputSegments.pop();
}
} else {
outputSegments.push(inputSegment);
}
}

var outputSegment, output = '';
for (i = 0; i < outputSegments.length; i++) {
outputSegment = outputSegments[i];
output += '/' + outputSegment;
}

if (path.lastIndexOf('/') == path.length - 1) {
// path.endsWith("/") || path.equals("")
output += '/';
}

return output;
}
}];
}
135 changes: 128 additions & 7 deletions test/ng/locationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -875,10 +875,15 @@ describe('$location', function() {
});
}

function initBrowser() {
function initBrowser(atRoot, noBase) {
return function($browser){
$browser.url('http://host.com/base');
$browser.$$baseHref = '/base/index.html';
if (atRoot) {
$browser.url('http://host.com/');
$browser.$$baseHref = noBase ? '' : '/index.html';
} else {
$browser.url('http://host.com/base');
$browser.$$baseHref = noBase ? '' : '/base/index.html';
}
};
}

Expand Down Expand Up @@ -1192,22 +1197,83 @@ describe('$location', function() {
});


it('should rewrite relative links relative to current path when history disabled', function() {
it('should rewrite relative links relative to current path when no base and history enabled on old browser', function() {
configureService('link', true, false, true);
inject(
initBrowser(false, true),
initLocation(),
function($browser, $location) {
$location.path('/some/');
expect($browser.url(), 'http://host.com/#!/some/');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/#!/some/link');

$location.path('/some');
expect($browser.url(), 'http://host.com/#!/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/#!/link');
}
);
});


it('should rewrite relative links relative to base href when history enabled on old browser', function() {
configureService('link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/some/link');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should replace current path when link begins with "/" and history disabled', function() {
it('should replace current path when link begins with "/" and app is on root and history enabled on old browser', function() {
configureService('/link', true, false, true);
inject(
initBrowser(true),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/index.html#!/link');
}
);
});


it('should replace current path when relative link begins with "/base/" and history enabled on old browser', function() {
configureService('/base/link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should replace current path when relative link leads to base and history enabled on old browser', function() {
configureService('../base/link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should replace current path when relative link begins with "/base/" and history enabled on old browser', function() {
configureService('/base/#!/link', true, false, true);
inject(
initBrowser(),
initLocation(),
Expand All @@ -1220,7 +1286,62 @@ describe('$location', function() {
});


it('should replace current hash fragment when link begins with "#" history disabled', function() {
it('should rewrite relative hashbang links with respect to base when history enabled on old browser', function() {
configureService('#!link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some/');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should replace current path when link begins with "#!/" and history enabled on old browser', function() {
configureService('#!/link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should rewrite when relative link begins with "/" and app is on the root and there is no base tag and history enabled on old browser', function() {
configureService('/link', true, false, true);
inject(
initBrowser(true, true),
initLocation(),
function($browser, $location) {
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/#!/link');
}
);
});


it('should not rewrite when relative link begins with "/" and history enabled on old browser', function() {
configureService('/other_base/link', true, false, true);
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectNoRewrite($browser);
}
);
});


it('should replace current hash fragment when link begins with "#" and history enabled on old browser', function() {
configureService('#link', true, false, true);
inject(
initBrowser(),
Expand Down