aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng
diff options
context:
space:
mode:
authorMisko Hevery2012-06-13 15:37:52 -0700
committerMisko Hevery2013-04-15 12:05:27 -0700
commit58ef32308f45141c8f7f7cc32a6156cd328ba692 (patch)
treeeb0e5178e1e492aec2db6593594fb9cba8435976 /src/ng
parent59bfe8e5a9edf7ba000d258b9ac1ef8355c9aca7 (diff)
downloadangular.js-58ef32308f45141c8f7f7cc32a6156cd328ba692.tar.bz2
fix($location): fix URL interception in hash-bang mode
Closes #1051
Diffstat (limited to 'src/ng')
-rw-r--r--src/ng/http.js2
-rw-r--r--src/ng/httpBackend.js2
-rw-r--r--src/ng/location.js272
3 files changed, 122 insertions, 154 deletions
diff --git a/src/ng/http.js b/src/ng/http.js
index b0f8de62..dd949a6c 100644
--- a/src/ng/http.js
+++ b/src/ng/http.js
@@ -52,7 +52,7 @@ function isSameDomain(requestUrl, locationUrl) {
relativeProtocol: match[2] === undefined || match[2] === ''
};
- match = URL_MATCH.exec(locationUrl);
+ match = SERVER_MATCH.exec(locationUrl);
var domain2 = {
protocol: match[1],
host: match[3],
diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js
index 91ebe03d..a3f6cdc0 100644
--- a/src/ng/httpBackend.js
+++ b/src/ng/httpBackend.js
@@ -117,7 +117,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
function completeRequest(callback, status, response, headersString) {
// URL_MATCH is defined in src/service/location.js
- var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
+ var protocol = (url.match(SERVER_MATCH) || ['', locationProtocol])[1];
// fix status code for file protocol (it's always 0)
status = (protocol == 'file') ? (response ? 200 : 404) : status;
diff --git a/src/ng/location.js b/src/ng/location.js
index 3196b1d5..ce11c2fb 100644
--- a/src/ng/location.js
+++ b/src/ng/location.js
@@ -1,8 +1,7 @@
'use strict';
-var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
- PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
- HASH_MATCH = PATH_MATCH,
+var SERVER_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
+ PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
@@ -23,30 +22,23 @@ function encodePath(path) {
return segments.join('/');
}
-function stripHash(url) {
- return url.split('#')[0];
+function matchUrl(url, obj) {
+ var match = SERVER_MATCH.exec(url);
+
+ obj.$$protocol = match[1];
+ obj.$$host = match[3];
+ obj.$$port = int(match[5]) || DEFAULT_PORTS[match[1]] || null;
}
+function matchAppUrl(url, obj) {
+ var match = PATH_MATCH.exec(url);
-function matchUrl(url, obj) {
- var match = URL_MATCH.exec(url);
-
- match = {
- protocol: match[1],
- host: match[3],
- port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
- path: match[6] || '/',
- search: match[8],
- hash: match[10]
- };
-
- if (obj) {
- obj.$$protocol = match.protocol;
- obj.$$host = match.host;
- obj.$$port = match.port;
- }
+ obj.$$path = decodeURIComponent(match[1]);
+ obj.$$search = parseKeyValue(match[3]);
+ obj.$$hash = decodeURIComponent(match[5] || '');
- return match;
+ // make sure path starts with '/';
+ if (obj.$$path && obj.$$path.charAt(0) != '/') obj.$$path = '/' + obj.$$path;
}
@@ -54,77 +46,62 @@ function composeProtocolHostPort(protocol, host, port) {
return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
}
-
-function pathPrefixFromBase(basePath) {
- return basePath.substr(0, basePath.lastIndexOf('/'));
+/**
+ *
+ * @param {string} begin
+ * @param {string} whole
+ * @param {string} otherwise
+ * @returns {string} returns text from whole after begin or otherwise if it does not begin with expected string.
+ */
+function beginsWith(begin, whole, otherwise) {
+ return whole.indexOf(begin) == 0 ? whole.substr(begin.length) : otherwise;
}
-function convertToHtml5Url(url, basePath, hashPrefix) {
- var match = matchUrl(url);
-
- // already html5 url
- if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
- match.hash.indexOf(hashPrefix) !== 0) {
- return url;
- // convert hashbang url -> html5 url
- } else {
- return composeProtocolHostPort(match.protocol, match.host, match.port) +
- pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
- }
+function stripHash(url) {
+ var index = url.indexOf('#');
+ return index == -1 ? url : url.substr(0, index);
}
-function convertToHashbangUrl(url, basePath, hashPrefix) {
- var match = matchUrl(url);
-
- // already hashbang url
- if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) &&
- match.hash.indexOf(hashPrefix) === 0) {
- return url;
- // convert html5 url -> hashbang url
- } else {
- var search = match.search && '?' + match.search || '',
- hash = match.hash && '#' + match.hash || '',
- pathPrefix = pathPrefixFromBase(basePath),
- path = match.path.substr(pathPrefix.length);
-
- if (match.path.indexOf(pathPrefix) !== 0) {
- throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
- }
+function stripFile(url) {
+ return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
+}
- return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
- '#' + hashPrefix + path + search + hash;
- }
+/* return the server only */
+function serverBase(url) {
+ return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
}
/**
- * LocationUrl represents an url
+ * LocationHtml5Url represents an url
* This object is exposed as $location service when HTML5 mode is enabled and supported
*
* @constructor
- * @param {string} url HTML5 url
- * @param {string} pathPrefix
+ * @param {string} appBase application base URL
+ * @param {string} basePrefix url path prefix
*/
-function LocationUrl(url, pathPrefix, appBaseUrl) {
- pathPrefix = pathPrefix || '';
-
+function LocationHtml5Url(appBase, basePrefix) {
+ basePrefix = basePrefix || '';
+ var appBaseNoFile = stripFile(appBase);
/**
* Parse given html5 (regular) url string into properties
* @param {string} newAbsoluteUrl HTML5 url
* @private
*/
- this.$$parse = function(newAbsoluteUrl) {
- var match = matchUrl(newAbsoluteUrl, this);
-
- if (match.path.indexOf(pathPrefix) !== 0) {
- throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
+ this.$$parse = function(url) {
+ var parsed = {}
+ matchUrl(url, parsed);
+ var pathUrl = beginsWith(appBaseNoFile, url);
+ if (!isString(pathUrl)) {
+ throw Error('Invalid url "' + url + '", missing path prefix "' + appBaseNoFile + '".');
+ }
+ matchAppUrl(pathUrl, parsed);
+ extend(this, parsed);
+ if (!this.$$path) {
+ this.$$path = '/';
}
-
- this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
- this.$$search = parseKeyValue(match.search);
- this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
this.$$compose();
};
@@ -138,19 +115,24 @@ function LocationUrl(url, pathPrefix, appBaseUrl) {
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
- pathPrefix + this.$$url;
+ this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
};
+ this.$$rewrite = function(url) {
+ var appUrl;
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return absoluteLinkUrl;
+ if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
+ if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
+ return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
+ } else {
+ return appBase;
+ }
+ } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
+ return appBaseNoFile + appUrl;
+ } else if (appBaseNoFile == url + '/') {
+ return appBaseNoFile;
}
}
-
-
- this.$$parse(url);
}
@@ -159,11 +141,11 @@ function LocationUrl(url, pathPrefix, appBaseUrl) {
* This object is exposed as $location service when html5 history api is disabled or not supported
*
* @constructor
- * @param {string} url Legacy url
- * @param {string} hashPrefix Prefix for hash part (containing path and search)
+ * @param {string} appBase application base URL
+ * @param {string} hashPrefix hashbang prefix
*/
-function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
- var basePath;
+function LocationHashbangUrl(appBase, hashPrefix) {
+ var appBaseNoFile = stripFile(appBase);
/**
* Parse given hashbang url into properties
@@ -171,24 +153,16 @@ function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
* @private
*/
this.$$parse = function(url) {
- var match = matchUrl(url, this);
-
-
- if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
- throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
+ matchUrl(url, this);
+ var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
+ if (!isString(withoutBaseUrl)) {
+ throw new Error('Invalid url "' + url + '", does not start with "' + appBase + '".');
}
-
- basePath = match.path + (match.search ? '?' + match.search : '');
- match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
- if (match[1]) {
- this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
- } else {
- this.$$path = '';
+ var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' ? beginsWith(hashPrefix, withoutBaseUrl) : withoutBaseUrl;
+ if (!isString(withoutHashUrl)) {
+ throw new Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '".');
}
-
- this.$$search = parseKeyValue(match[3]);
- this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
-
+ matchAppUrl(withoutHashUrl, this);
this.$$compose();
};
@@ -201,22 +175,48 @@ function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
- basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
+ this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
};
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return absoluteLinkUrl;
+ this.$$rewrite = function(url) {
+ if(stripHash(appBase) == stripHash(url)) {
+ return url;
}
}
+}
+
+
+/**
+ * LocationHashbangUrl represents url
+ * This object is exposed as $location service when html5 history api is enabled but the browser
+ * does not support it.
+ *
+ * @constructor
+ * @param {string} appBase application base URL
+ * @param {string} hashPrefix hashbang prefix
+ */
+function LocationHashbangInHtml5Url(appBase, hashPrefix) {
+ LocationHashbangUrl.apply(this, arguments);
+
+ var appBaseNoFile = stripFile(appBase);
+ this.$$rewrite = function(url) {
+ var appUrl;
- this.$$parse(url);
+ if ( appBase == stripHash(url) ) {
+ return url;
+ } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
+ return appBase + hashPrefix + appUrl;
+ } else if ( appBaseNoFile === url + '/') {
+ return appBaseNoFile;
+ }
+ }
}
-LocationUrl.prototype = {
+LocationHashbangInHtml5Url.prototype =
+ LocationHashbangUrl.prototype =
+ LocationHtml5Url.prototype = {
/**
* Has any change been replacing ?
@@ -398,21 +398,6 @@ LocationUrl.prototype = {
}
};
-LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
-
-function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
- LocationHashbangUrl.apply(this, arguments);
-
-
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
- }
- }
-}
-
-LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
-
function locationGetter(property) {
return function() {
return this[property];
@@ -509,37 +494,20 @@ function $LocationProvider(){
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
function( $rootScope, $browser, $sniffer, $rootElement) {
var $location,
- basePath,
- pathPrefix,
- initUrl = $browser.url(),
- initUrlParts = matchUrl(initUrl),
- appBaseUrl;
+ LocationMode,
+ baseHref = $browser.baseHref(),
+ initialUrl = $browser.url(),
+ appBase;
if (html5Mode) {
- basePath = $browser.baseHref() || '/';
- pathPrefix = pathPrefixFromBase(basePath);
- appBaseUrl =
- composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
- pathPrefix + '/';
-
- if ($sniffer.history) {
- $location = new LocationUrl(
- convertToHtml5Url(initUrl, basePath, hashPrefix),
- pathPrefix, appBaseUrl);
- } else {
- $location = new LocationHashbangInHtml5Url(
- convertToHashbangUrl(initUrl, basePath, hashPrefix),
- hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
- }
+ appBase = baseHref ? serverBase(initialUrl) + baseHref : initialUrl;
+ LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
} else {
- appBaseUrl =
- composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
- (initUrlParts.path || '') +
- (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
- '#' + hashPrefix + '/';
-
- $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
+ appBase = stripHash(initialUrl);
+ LocationMode = LocationHashbangUrl;
}
+ $location = new LocationMode(appBase, '#' + hashPrefix);
+ $location.$$parse($location.$$rewrite(initialUrl));
$rootElement.bind('click', function(event) {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
@@ -556,7 +524,7 @@ function $LocationProvider(){
}
var absHref = elm.prop('href'),
- rewrittenUrl = $location.$$rewriteAppUrl(absHref);
+ rewrittenUrl = $location.$$rewrite(absHref);
if (absHref && !elm.attr('target') && rewrittenUrl) {
// update location manually
@@ -570,7 +538,7 @@ function $LocationProvider(){
// rewrite hashbang url <> html5 url
- if ($location.absUrl() != initUrl) {
+ if ($location.absUrl() != initialUrl) {
$browser.url($location.absUrl(), true);
}