2015-05-18 14:57:20 -04:00
|
|
|
import {Injectable} from 'angular2/di';
|
2015-06-23 06:05:09 -04:00
|
|
|
import {
|
|
|
|
isPresent,
|
|
|
|
isBlank,
|
|
|
|
RegExpWrapper,
|
|
|
|
BaseException,
|
|
|
|
normalizeBlank
|
|
|
|
} from 'angular2/src/facade/lang';
|
|
|
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
2015-02-20 12:00:09 -05:00
|
|
|
|
2015-07-07 02:15:58 -04:00
|
|
|
/**
|
|
|
|
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
|
|
|
|
*
|
|
|
|
* This interface can be overridden by the application developer to create custom behavior.
|
|
|
|
*
|
|
|
|
* See {@link Compiler}
|
|
|
|
*/
|
2015-03-16 17:44:14 -04:00
|
|
|
@Injectable()
|
2015-02-20 12:00:09 -05:00
|
|
|
export class UrlResolver {
|
2015-05-13 03:28:42 -04:00
|
|
|
/**
|
2015-06-23 05:28:06 -04:00
|
|
|
* Resolves the `url` given the `baseUrl`:
|
2015-05-13 03:28:42 -04:00
|
|
|
* - when the `url` is null, the `baseUrl` is returned,
|
2015-05-18 14:57:20 -04:00
|
|
|
* - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
|
|
|
|
* `baseUrl` and `url`,
|
2015-06-23 06:05:09 -04:00
|
|
|
* - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
|
|
|
|
* returned as is (ignoring the `baseUrl`)
|
2015-05-13 03:28:42 -04:00
|
|
|
*
|
|
|
|
* @param {string} baseUrl
|
|
|
|
* @param {string} url
|
|
|
|
* @returns {string} the resolved URL
|
|
|
|
*/
|
2015-06-23 06:05:09 -04:00
|
|
|
resolve(baseUrl: string, url: string): string { return _resolveUrl(baseUrl, url); }
|
|
|
|
}
|
|
|
|
|
|
|
|
// The code below is adapted from Traceur:
|
|
|
|
// https://github.com/google/traceur-compiler/blob/9511c1dafa972bf0de1202a8a863bad02f0f95a8/src/runtime/url.js
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a URI string from already-encoded parts.
|
|
|
|
*
|
|
|
|
* No encoding is performed. Any component may be omitted as either null or
|
|
|
|
* undefined.
|
|
|
|
*
|
|
|
|
* @param {?string=} opt_scheme The scheme such as 'http'.
|
|
|
|
* @param {?string=} opt_userInfo The user name before the '@'.
|
|
|
|
* @param {?string=} opt_domain The domain such as 'www.google.com', already
|
|
|
|
* URI-encoded.
|
|
|
|
* @param {(string|null)=} opt_port The port number.
|
|
|
|
* @param {?string=} opt_path The path, already URI-encoded. If it is not
|
|
|
|
* empty, it must begin with a slash.
|
|
|
|
* @param {?string=} opt_queryData The URI-encoded query data.
|
|
|
|
* @param {?string=} opt_fragment The URI-encoded fragment identifier.
|
|
|
|
* @return {string} The fully combined URI.
|
|
|
|
*/
|
|
|
|
function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,
|
|
|
|
opt_port?: string, opt_path?: string, opt_queryData?: string,
|
|
|
|
opt_fragment?: string): string {
|
|
|
|
var out = [];
|
|
|
|
|
|
|
|
if (isPresent(opt_scheme)) {
|
|
|
|
out.push(opt_scheme + ':');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPresent(opt_domain)) {
|
|
|
|
out.push('//');
|
|
|
|
|
|
|
|
if (isPresent(opt_userInfo)) {
|
|
|
|
out.push(opt_userInfo + '@');
|
2015-02-20 12:00:09 -05:00
|
|
|
}
|
|
|
|
|
2015-06-23 06:05:09 -04:00
|
|
|
out.push(opt_domain);
|
2015-02-20 12:00:09 -05:00
|
|
|
|
2015-06-23 06:05:09 -04:00
|
|
|
if (isPresent(opt_port)) {
|
|
|
|
out.push(':' + opt_port);
|
2015-02-20 12:00:09 -05:00
|
|
|
}
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
2015-02-20 12:00:09 -05:00
|
|
|
|
2015-06-23 06:05:09 -04:00
|
|
|
if (isPresent(opt_path)) {
|
|
|
|
out.push(opt_path);
|
2015-02-20 12:00:09 -05:00
|
|
|
}
|
2015-06-23 06:05:09 -04:00
|
|
|
|
|
|
|
if (isPresent(opt_queryData)) {
|
|
|
|
out.push('?' + opt_queryData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPresent(opt_fragment)) {
|
|
|
|
out.push('#' + opt_fragment);
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.join('');
|
2015-02-20 12:00:09 -05:00
|
|
|
}
|
|
|
|
|
2015-06-23 06:05:09 -04:00
|
|
|
/**
|
|
|
|
* A regular expression for breaking a URI into its component parts.
|
|
|
|
*
|
|
|
|
* {@link http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234} says
|
|
|
|
* As the "first-match-wins" algorithm is identical to the "greedy"
|
|
|
|
* disambiguation method used by POSIX regular expressions, it is natural and
|
|
|
|
* commonplace to use a regular expression for parsing the potential five
|
|
|
|
* components of a URI reference.
|
|
|
|
*
|
|
|
|
* The following line is the regular expression for breaking-down a
|
|
|
|
* well-formed URI reference into its components.
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
|
|
|
|
* 12 3 4 5 6 7 8 9
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* The numbers in the second line above are only to assist readability; they
|
|
|
|
* indicate the reference points for each subexpression (i.e., each paired
|
|
|
|
* parenthesis). We refer to the value matched for subexpression <n> as $<n>.
|
|
|
|
* For example, matching the above expression to
|
|
|
|
* <pre>
|
|
|
|
* http://www.ics.uci.edu/pub/ietf/uri/#Related
|
|
|
|
* </pre>
|
|
|
|
* results in the following subexpression matches:
|
|
|
|
* <pre>
|
|
|
|
* $1 = http:
|
|
|
|
* $2 = http
|
|
|
|
* $3 = //www.ics.uci.edu
|
|
|
|
* $4 = www.ics.uci.edu
|
|
|
|
* $5 = /pub/ietf/uri/
|
|
|
|
* $6 = <undefined>
|
|
|
|
* $7 = <undefined>
|
|
|
|
* $8 = #Related
|
|
|
|
* $9 = Related
|
|
|
|
* </pre>
|
|
|
|
* where <undefined> indicates that the component is not present, as is the
|
|
|
|
* case for the query component in the above example. Therefore, we can
|
|
|
|
* determine the value of the five components as
|
|
|
|
* <pre>
|
|
|
|
* scheme = $2
|
|
|
|
* authority = $4
|
|
|
|
* path = $5
|
|
|
|
* query = $7
|
|
|
|
* fragment = $9
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* The regular expression has been modified slightly to expose the
|
|
|
|
* userInfo, domain, and port separately from the authority.
|
|
|
|
* The modified version yields
|
|
|
|
* <pre>
|
|
|
|
* $1 = http scheme
|
|
|
|
* $2 = <undefined> userInfo -\
|
|
|
|
* $3 = www.ics.uci.edu domain | authority
|
|
|
|
* $4 = <undefined> port -/
|
|
|
|
* $5 = /pub/ietf/uri/ path
|
|
|
|
* $6 = <undefined> query without ?
|
|
|
|
* $7 = Related fragment without #
|
|
|
|
* </pre>
|
|
|
|
* @type {!RegExp}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var _splitRe =
|
|
|
|
RegExpWrapper.create('^' +
|
|
|
|
'(?:' +
|
|
|
|
'([^:/?#.]+)' + // scheme - ignore special characters
|
|
|
|
// used by other URL parts such as :,
|
|
|
|
// ?, /, #, and .
|
|
|
|
':)?' +
|
|
|
|
'(?://' +
|
|
|
|
'(?:([^/?#]*)@)?' + // userInfo
|
|
|
|
'([\\w\\d\\-\\u0100-\\uffff.%]*)' + // domain - restrict to letters,
|
|
|
|
// digits, dashes, dots, percent
|
|
|
|
// escapes, and unicode characters.
|
|
|
|
'(?::([0-9]+))?' + // port
|
|
|
|
')?' +
|
|
|
|
'([^?#]+)?' + // path
|
|
|
|
'(?:\\?([^#]*))?' + // query
|
|
|
|
'(?:#(.*))?' + // fragment
|
|
|
|
'$');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The index of each URI component in the return value of goog.uri.utils.split.
|
|
|
|
* @enum {number}
|
|
|
|
*/
|
|
|
|
enum _ComponentIndex {
|
|
|
|
SCHEME = 1,
|
|
|
|
USER_INFO,
|
|
|
|
DOMAIN,
|
|
|
|
PORT,
|
|
|
|
PATH,
|
|
|
|
QUERY_DATA,
|
|
|
|
FRAGMENT
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Splits a URI into its component parts.
|
|
|
|
*
|
|
|
|
* Each component can be accessed via the component indices; for example:
|
|
|
|
* <pre>
|
|
|
|
* goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* @param {string} uri The URI string to examine.
|
|
|
|
* @return {!Array.<string|undefined>} Each component still URI-encoded.
|
|
|
|
* Each component that is present will contain the encoded value, whereas
|
|
|
|
* components that are not present will be undefined or empty, depending
|
|
|
|
* on the browser's regular expression implementation. Never null, since
|
|
|
|
* arbitrary strings may still look like path names.
|
|
|
|
*/
|
|
|
|
function _split(uri: string): List<string | any> {
|
|
|
|
return RegExpWrapper.firstMatch(_splitRe, uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes dot segments in given path component, as described in
|
|
|
|
* RFC 3986, section 5.2.4.
|
|
|
|
*
|
|
|
|
* @param {string} path A non-empty path component.
|
|
|
|
* @return {string} Path component with removed dot segments.
|
|
|
|
*/
|
|
|
|
function _removeDotSegments(path: string): string {
|
|
|
|
if (path == '/') return '/';
|
|
|
|
|
|
|
|
var leadingSlash = path[0] == '/' ? '/' : '';
|
|
|
|
var trailingSlash = path[path.length - 1] === '/' ? '/' : '';
|
|
|
|
var segments = path.split('/');
|
|
|
|
|
|
|
|
var out = [];
|
|
|
|
var up = 0;
|
|
|
|
for (var pos = 0; pos < segments.length; pos++) {
|
|
|
|
var segment = segments[pos];
|
|
|
|
switch (segment) {
|
|
|
|
case '':
|
|
|
|
case '.':
|
|
|
|
break;
|
|
|
|
case '..':
|
|
|
|
if (out.length > 0) {
|
|
|
|
ListWrapper.removeAt(out, out.length - 1);
|
|
|
|
} else {
|
|
|
|
up++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
out.push(segment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leadingSlash == '') {
|
|
|
|
while (up-- > 0) {
|
|
|
|
ListWrapper.insert(out, 0, '..');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out.length === 0) out.push('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
return leadingSlash + out.join('/') + trailingSlash;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes an array of the parts from split and canonicalizes the path part
|
|
|
|
* and then joins all the parts.
|
|
|
|
* @param {Array.<string?>} parts
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
function _joinAndCanonicalizePath(parts: List<any>): string {
|
|
|
|
var path = parts[_ComponentIndex.PATH];
|
|
|
|
path = isBlank(path) ? '' : _removeDotSegments(path);
|
|
|
|
parts[_ComponentIndex.PATH] = path;
|
|
|
|
|
|
|
|
return _buildFromEncodedParts(parts[_ComponentIndex.SCHEME], parts[_ComponentIndex.USER_INFO],
|
|
|
|
parts[_ComponentIndex.DOMAIN], parts[_ComponentIndex.PORT], path,
|
|
|
|
parts[_ComponentIndex.QUERY_DATA], parts[_ComponentIndex.FRAGMENT]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves a URL.
|
|
|
|
* @param {string} base The URL acting as the base URL.
|
|
|
|
* @param {string} to The URL to resolve.
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
function _resolveUrl(base: string, url: string): string {
|
|
|
|
var parts = _split(url);
|
|
|
|
var baseParts = _split(base);
|
|
|
|
|
|
|
|
if (isPresent(parts[_ComponentIndex.SCHEME])) {
|
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
} else {
|
|
|
|
parts[_ComponentIndex.SCHEME] = baseParts[_ComponentIndex.SCHEME];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = _ComponentIndex.SCHEME; i <= _ComponentIndex.PORT; i++) {
|
|
|
|
if (isBlank(parts[i])) {
|
|
|
|
parts[i] = baseParts[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parts[_ComponentIndex.PATH][0] == '/') {
|
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
}
|
|
|
|
|
|
|
|
var path = baseParts[_ComponentIndex.PATH];
|
|
|
|
if (isBlank(path)) path = '/';
|
|
|
|
var index = path.lastIndexOf('/');
|
|
|
|
path = path.substring(0, index + 1) + parts[_ComponentIndex.PATH];
|
|
|
|
parts[_ComponentIndex.PATH] = path;
|
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
}
|