2015-12-05 05:21:38 -05:00
|
|
|
import {Injectable, Inject} from 'angular2/src/core/di';
|
2016-04-12 12:40:37 -04:00
|
|
|
import {
|
|
|
|
StringWrapper,
|
|
|
|
isPresent,
|
|
|
|
isBlank,
|
|
|
|
RegExpWrapper,
|
|
|
|
normalizeBlank
|
|
|
|
} from 'angular2/src/facade/lang';
|
2015-11-06 20:34:07 -05:00
|
|
|
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
|
|
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
2015-12-05 05:21:38 -05:00
|
|
|
import {PACKAGE_ROOT_URL} from 'angular2/src/core/application_tokens';
|
|
|
|
import {Provider} from 'angular2/src/core/di';
|
2015-02-20 12:00:09 -05:00
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Create a {@link UrlResolver} with no package prefix.
|
|
|
|
*/
|
2015-10-15 19:03:23 -04:00
|
|
|
export function createWithoutPackagePrefix(): UrlResolver {
|
|
|
|
return new UrlResolver();
|
|
|
|
}
|
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
|
|
|
|
*/
|
2016-04-12 12:40:37 -04:00
|
|
|
export var DEFAULT_PACKAGE_URL_PROVIDER = new Provider(PACKAGE_ROOT_URL, {useValue: "/"});
|
2015-10-15 19:03:23 -04:00
|
|
|
|
2015-07-07 02:15:58 -04:00
|
|
|
/**
|
|
|
|
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
|
|
|
|
*
|
2015-12-03 18:49:09 -05:00
|
|
|
* This class can be overridden by the application developer to create custom behavior.
|
2015-07-07 02:15:58 -04:00
|
|
|
*
|
|
|
|
* See {@link Compiler}
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example compiler/ts/url_resolver/url_resolver.ts region='url_resolver'}
|
2015-07-07 02:15:58 -04:00
|
|
|
*/
|
2015-03-16 17:44:14 -04:00
|
|
|
@Injectable()
|
2015-02-20 12:00:09 -05:00
|
|
|
export class UrlResolver {
|
2015-12-05 05:21:38 -05:00
|
|
|
private _packagePrefix: string;
|
|
|
|
|
|
|
|
constructor(@Inject(PACKAGE_ROOT_URL) packagePrefix: string = null) {
|
|
|
|
if (isPresent(packagePrefix)) {
|
2016-04-12 12:40:37 -04:00
|
|
|
this._packagePrefix = StringWrapper.stripRight(packagePrefix, "/") + "/";
|
2015-12-05 05:21:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-12-05 05:21:38 -05:00
|
|
|
resolve(baseUrl: string, url: string): string {
|
|
|
|
var resolvedUrl = url;
|
|
|
|
if (isPresent(baseUrl) && baseUrl.length > 0) {
|
|
|
|
resolvedUrl = _resolveUrl(baseUrl, resolvedUrl);
|
|
|
|
}
|
2016-04-12 12:40:37 -04:00
|
|
|
if (isPresent(this._packagePrefix) && getUrlScheme(resolvedUrl) == "package") {
|
|
|
|
resolvedUrl = resolvedUrl.replace("package:", this._packagePrefix);
|
2015-12-05 05:21:38 -05:00
|
|
|
}
|
|
|
|
return resolvedUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Extract the scheme of a URL.
|
|
|
|
*/
|
2015-12-05 05:21:38 -05:00
|
|
|
export function getUrlScheme(url: string): string {
|
|
|
|
var match = _split(url);
|
2016-04-12 12:40:37 -04:00
|
|
|
return (match && match[_ComponentIndex.Scheme]) || "";
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
*/
|
2016-04-12 12:40:37 -04:00
|
|
|
function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,
|
|
|
|
opt_port?: string, opt_path?: string, opt_queryData?: string,
|
|
|
|
opt_fragment?: string): string {
|
2015-06-23 06:05:09 -04:00
|
|
|
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}
|
2015-10-02 16:32:48 -04:00
|
|
|
* @internal
|
2015-06-23 06:05:09 -04:00
|
|
|
*/
|
2016-04-12 12:40:37 -04:00
|
|
|
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
|
|
|
|
'$');
|
2015-06-23 06:05:09 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The index of each URI component in the return value of goog.uri.utils.split.
|
|
|
|
* @enum {number}
|
|
|
|
*/
|
|
|
|
enum _ComponentIndex {
|
2015-08-26 16:40:12 -04:00
|
|
|
Scheme = 1,
|
|
|
|
UserInfo,
|
|
|
|
Domain,
|
|
|
|
Port,
|
|
|
|
Path,
|
|
|
|
QueryData,
|
|
|
|
Fragment
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2016-04-12 12:40:37 -04:00
|
|
|
function _split(uri: string): Array<string | any> {
|
2015-06-23 06:05:09 -04:00
|
|
|
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('/');
|
|
|
|
|
2015-10-07 12:09:43 -04:00
|
|
|
var out: string[] = [];
|
2015-06-23 06:05:09 -04:00
|
|
|
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) {
|
2015-10-07 12:09:43 -04:00
|
|
|
out.pop();
|
2015-06-23 06:05:09 -04:00
|
|
|
} else {
|
|
|
|
up++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
out.push(segment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leadingSlash == '') {
|
|
|
|
while (up-- > 0) {
|
2015-10-07 12:09:43 -04:00
|
|
|
out.unshift('..');
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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}
|
|
|
|
*/
|
2015-08-28 14:29:19 -04:00
|
|
|
function _joinAndCanonicalizePath(parts: any[]): string {
|
2015-08-26 16:40:12 -04:00
|
|
|
var path = parts[_ComponentIndex.Path];
|
2015-06-23 06:05:09 -04:00
|
|
|
path = isBlank(path) ? '' : _removeDotSegments(path);
|
2015-08-26 16:40:12 -04:00
|
|
|
parts[_ComponentIndex.Path] = path;
|
2015-06-23 06:05:09 -04:00
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
return _buildFromEncodedParts(parts[_ComponentIndex.Scheme], parts[_ComponentIndex.UserInfo],
|
|
|
|
parts[_ComponentIndex.Domain], parts[_ComponentIndex.Port], path,
|
|
|
|
parts[_ComponentIndex.QueryData], parts[_ComponentIndex.Fragment]);
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 {
|
2015-08-08 03:25:24 -04:00
|
|
|
var parts = _split(encodeURI(url));
|
2015-06-23 06:05:09 -04:00
|
|
|
var baseParts = _split(base);
|
|
|
|
|
2015-08-26 16:40:12 -04:00
|
|
|
if (isPresent(parts[_ComponentIndex.Scheme])) {
|
2015-06-23 06:05:09 -04:00
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
} else {
|
2015-08-26 16:40:12 -04:00
|
|
|
parts[_ComponentIndex.Scheme] = baseParts[_ComponentIndex.Scheme];
|
2015-06-23 06:05:09 -04:00
|
|
|
}
|
|
|
|
|
2015-08-26 16:40:12 -04:00
|
|
|
for (var i = _ComponentIndex.Scheme; i <= _ComponentIndex.Port; i++) {
|
2015-06-23 06:05:09 -04:00
|
|
|
if (isBlank(parts[i])) {
|
|
|
|
parts[i] = baseParts[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-26 16:40:12 -04:00
|
|
|
if (parts[_ComponentIndex.Path][0] == '/') {
|
2015-06-23 06:05:09 -04:00
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
}
|
|
|
|
|
2015-08-26 16:40:12 -04:00
|
|
|
var path = baseParts[_ComponentIndex.Path];
|
2015-06-23 06:05:09 -04:00
|
|
|
if (isBlank(path)) path = '/';
|
|
|
|
var index = path.lastIndexOf('/');
|
2015-08-26 16:40:12 -04:00
|
|
|
path = path.substring(0, index + 1) + parts[_ComponentIndex.Path];
|
|
|
|
parts[_ComponentIndex.Path] = path;
|
2015-06-23 06:05:09 -04:00
|
|
|
return _joinAndCanonicalizePath(parts);
|
|
|
|
}
|