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/ng | |
| parent | 59bfe8e5a9edf7ba000d258b9ac1ef8355c9aca7 (diff) | |
| download | angular.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.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);      } | 
