352 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {Inject, InjectionToken, PACKAGE_ROOT_URL} from '@angular/core';
 | |
| 
 | |
| import {CompilerInjectable} from './injectable';
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Create a {@link UrlResolver} with no package prefix.
 | |
|  */
 | |
| export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
 | |
|   return new UrlResolver();
 | |
| }
 | |
| 
 | |
| export function createOfflineCompileUrlResolver(): UrlResolver {
 | |
|   return new UrlResolver('.');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
 | |
|  */
 | |
| export const DEFAULT_PACKAGE_URL_PROVIDER = {
 | |
|   provide: PACKAGE_ROOT_URL,
 | |
|   useValue: '/'
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Used by the {@link Compiler} when resolving HTML and CSS template URLs.
 | |
|  *
 | |
|  * This class can be overridden by the application developer to create custom behavior.
 | |
|  *
 | |
|  * See {@link Compiler}
 | |
|  *
 | |
|  * ## Example
 | |
|  *
 | |
|  * {@example compiler/ts/url_resolver/url_resolver.ts region='url_resolver'}
 | |
|  *
 | |
|  * @security  When compiling templates at runtime, you must
 | |
|  * ensure that the entire template comes from a trusted source.
 | |
|  * Attacker-controlled data introduced by a template could expose your
 | |
|  * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
 | |
|  */
 | |
| @CompilerInjectable()
 | |
| export class UrlResolver {
 | |
|   constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string = null) {}
 | |
| 
 | |
|   /**
 | |
|    * Resolves the `url` given the `baseUrl`:
 | |
|    * - when the `url` is null, the `baseUrl` is returned,
 | |
|    * - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
 | |
|    * `baseUrl` and `url`,
 | |
|    * - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
 | |
|    * returned as is (ignoring the `baseUrl`)
 | |
|    */
 | |
|   resolve(baseUrl: string, url: string): string {
 | |
|     let resolvedUrl = url;
 | |
|     if (baseUrl != null && baseUrl.length > 0) {
 | |
|       resolvedUrl = _resolveUrl(baseUrl, resolvedUrl);
 | |
|     }
 | |
|     const resolvedParts = _split(resolvedUrl);
 | |
|     let prefix = this._packagePrefix;
 | |
|     if (prefix != null && resolvedParts != null &&
 | |
|         resolvedParts[_ComponentIndex.Scheme] == 'package') {
 | |
|       let path = resolvedParts[_ComponentIndex.Path];
 | |
|       prefix = prefix.replace(/\/+$/, '');
 | |
|       path = path.replace(/^\/+/, '');
 | |
|       return `${prefix}/${path}`;
 | |
|     }
 | |
|     return resolvedUrl;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Extract the scheme of a URL.
 | |
|  */
 | |
| export function getUrlScheme(url: string): string {
 | |
|   const match = _split(url);
 | |
|   return (match && match[_ComponentIndex.Scheme]) || '';
 | |
| }
 | |
| 
 | |
| // 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 opt_scheme The scheme such as 'http'.
 | |
|  * @param opt_userInfo The user name before the '@'.
 | |
|  * @param opt_domain The domain such as 'www.google.com', already
 | |
|  *     URI-encoded.
 | |
|  * @param opt_port The port number.
 | |
|  * @param opt_path The path, already URI-encoded.  If it is not
 | |
|  *     empty, it must begin with a slash.
 | |
|  * @param opt_queryData The URI-encoded query data.
 | |
|  * @param opt_fragment The URI-encoded fragment identifier.
 | |
|  * @return 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 {
 | |
|   const out: string[] = [];
 | |
| 
 | |
|   if (opt_scheme != null) {
 | |
|     out.push(opt_scheme + ':');
 | |
|   }
 | |
| 
 | |
|   if (opt_domain != null) {
 | |
|     out.push('//');
 | |
| 
 | |
|     if (opt_userInfo != null) {
 | |
|       out.push(opt_userInfo + '@');
 | |
|     }
 | |
| 
 | |
|     out.push(opt_domain);
 | |
| 
 | |
|     if (opt_port != null) {
 | |
|       out.push(':' + opt_port);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (opt_path != null) {
 | |
|     out.push(opt_path);
 | |
|   }
 | |
| 
 | |
|   if (opt_queryData != null) {
 | |
|     out.push('?' + opt_queryData);
 | |
|   }
 | |
| 
 | |
|   if (opt_fragment != null) {
 | |
|     out.push('#' + opt_fragment);
 | |
|   }
 | |
| 
 | |
|   return out.join('');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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}
 | |
|  * @internal
 | |
|  */
 | |
| const _splitRe = new RegExp(
 | |
|     '^' +
 | |
|     '(?:' +
 | |
|     '([^:/?#.]+)' +  // 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,
 | |
|   UserInfo,
 | |
|   Domain,
 | |
|   Port,
 | |
|   Path,
 | |
|   QueryData,
 | |
|   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 uri The URI string to examine.
 | |
|  * @return 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): Array<string|any> {
 | |
|   return uri.match(_splitRe);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   * Removes dot segments in given path component, as described in
 | |
|   * RFC 3986, section 5.2.4.
 | |
|   *
 | |
|   * @param path A non-empty path component.
 | |
|   * @return Path component with removed dot segments.
 | |
|   */
 | |
| function _removeDotSegments(path: string): string {
 | |
|   if (path == '/') return '/';
 | |
| 
 | |
|   const leadingSlash = path[0] == '/' ? '/' : '';
 | |
|   const trailingSlash = path[path.length - 1] === '/' ? '/' : '';
 | |
|   const segments = path.split('/');
 | |
| 
 | |
|   const out: string[] = [];
 | |
|   let up = 0;
 | |
|   for (let pos = 0; pos < segments.length; pos++) {
 | |
|     const segment = segments[pos];
 | |
|     switch (segment) {
 | |
|       case '':
 | |
|       case '.':
 | |
|         break;
 | |
|       case '..':
 | |
|         if (out.length > 0) {
 | |
|           out.pop();
 | |
|         } else {
 | |
|           up++;
 | |
|         }
 | |
|         break;
 | |
|       default:
 | |
|         out.push(segment);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (leadingSlash == '') {
 | |
|     while (up-- > 0) {
 | |
|       out.unshift('..');
 | |
|     }
 | |
| 
 | |
|     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.
 | |
|  */
 | |
| function _joinAndCanonicalizePath(parts: any[]): string {
 | |
|   let path = parts[_ComponentIndex.Path];
 | |
|   path = path == null ? '' : _removeDotSegments(path);
 | |
|   parts[_ComponentIndex.Path] = path;
 | |
| 
 | |
|   return _buildFromEncodedParts(
 | |
|       parts[_ComponentIndex.Scheme], parts[_ComponentIndex.UserInfo], parts[_ComponentIndex.Domain],
 | |
|       parts[_ComponentIndex.Port], path, parts[_ComponentIndex.QueryData],
 | |
|       parts[_ComponentIndex.Fragment]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Resolves a URL.
 | |
|  * @param base The URL acting as the base URL.
 | |
|  * @param to The URL to resolve.
 | |
|  */
 | |
| function _resolveUrl(base: string, url: string): string {
 | |
|   const parts = _split(encodeURI(url));
 | |
|   const baseParts = _split(base);
 | |
| 
 | |
|   if (parts[_ComponentIndex.Scheme] != null) {
 | |
|     return _joinAndCanonicalizePath(parts);
 | |
|   } else {
 | |
|     parts[_ComponentIndex.Scheme] = baseParts[_ComponentIndex.Scheme];
 | |
|   }
 | |
| 
 | |
|   for (let i = _ComponentIndex.Scheme; i <= _ComponentIndex.Port; i++) {
 | |
|     if (parts[i] == null) {
 | |
|       parts[i] = baseParts[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (parts[_ComponentIndex.Path][0] == '/') {
 | |
|     return _joinAndCanonicalizePath(parts);
 | |
|   }
 | |
| 
 | |
|   let path = baseParts[_ComponentIndex.Path];
 | |
|   if (path == null) path = '/';
 | |
|   const index = path.lastIndexOf('/');
 | |
|   path = path.substring(0, index + 1) + parts[_ComponentIndex.Path];
 | |
|   parts[_ComponentIndex.Path] = path;
 | |
|   return _joinAndCanonicalizePath(parts);
 | |
| }
 |