diff options
| author | Misko Hevery | 2012-06-13 15:37:52 -0700 |
|---|---|---|
| committer | Misko Hevery | 2013-04-15 12:05:27 -0700 |
| commit | 58ef32308f45141c8f7f7cc32a6156cd328ba692 (patch) | |
| tree | eb0e5178e1e492aec2db6593594fb9cba8435976 /src | |
| parent | 59bfe8e5a9edf7ba000d258b9ac1ef8355c9aca7 (diff) | |
| download | angular.js-58ef32308f45141c8f7f7cc32a6156cd328ba692.tar.bz2 | |
fix($location): fix URL interception in hash-bang mode
Closes #1051
Diffstat (limited to 'src')
| -rw-r--r-- | src/ng/http.js | 2 | ||||
| -rw-r--r-- | src/ng/httpBackend.js | 2 | ||||
| -rw-r--r-- | src/ng/location.js | 272 |
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); } |
