| 
1 | 1 | import {Injectable} from 'angular2/di';  | 
2 |  | -import {isPresent, isBlank, RegExpWrapper, BaseException} from 'angular2/src/facade/lang';  | 
3 |  | -import {DOM} from 'angular2/src/dom/dom_adapter';  | 
 | 2 | +import {  | 
 | 3 | + isPresent,  | 
 | 4 | + isBlank,  | 
 | 5 | + RegExpWrapper,  | 
 | 6 | + BaseException,  | 
 | 7 | + normalizeBlank  | 
 | 8 | +} from 'angular2/src/facade/lang';  | 
 | 9 | +import {ListWrapper} from 'angular2/src/facade/collection';  | 
4 | 10 | 
 
  | 
5 | 11 | @Injectable()  | 
6 | 12 | export class UrlResolver {  | 
7 |  | - static a;  | 
8 |  | - | 
9 |  | - constructor() {  | 
10 |  | - if (isBlank(UrlResolver.a)) {  | 
11 |  | - UrlResolver.a = DOM.createElement('a');  | 
12 |  | - }  | 
13 |  | - }  | 
14 |  | - | 
15 | 13 |  /**  | 
16 | 14 |  * Resolves the `url` given the `baseUrl`:  | 
17 | 15 |  * - when the `url` is null, the `baseUrl` is returned,  | 
18 |  | - * - due to a limitation in the process used to resolve urls (a HTMLLinkElement), `url` must not  | 
19 |  | - * start with a `/`,  | 
20 | 16 |  * - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of  | 
21 | 17 |  * `baseUrl` and `url`,  | 
22 |  | - * - if `url` is absolute (it has a scheme: 'http://', 'https://'), the `url` is returned  | 
23 |  | - * (ignoring the `baseUrl`)  | 
 | 18 | + * - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is  | 
 | 19 | + * returned as is (ignoring the `baseUrl`)  | 
24 | 20 |  *  | 
25 | 21 |  * @param {string} baseUrl  | 
26 | 22 |  * @param {string} url  | 
27 | 23 |  * @returns {string} the resolved URL  | 
28 | 24 |  */  | 
29 |  | - resolve(baseUrl: string, url: string): string {  | 
30 |  | - if (isBlank(url) || url == '') return baseUrl;  | 
31 |  | - | 
32 |  | - if (url[0] == '/') {  | 
33 |  | - // The `HTMLLinkElement` does not allow resolving this case (the `url` would be interpreted as  | 
34 |  | - // relative):  | 
35 |  | - // - `baseUrl` = 'http://www.foo.com/base'  | 
36 |  | - // - `url` = '/absolute/path/to/here'  | 
37 |  | - // - the result would be 'http://www.foo.com/base/absolute/path/to/here' while  | 
38 |  | - // 'http://www.foo.com/absolute/path/to/here'  | 
39 |  | - // is expected (without the 'base' segment).  | 
40 |  | - throw new BaseException(`Could not resolve the url ${url} from ${baseUrl}`);  | 
 | 25 | + resolve(baseUrl: string, url: string): string { return _resolveUrl(baseUrl, url); }  | 
 | 26 | +}  | 
 | 27 | + | 
 | 28 | +// The code below is adapted from Traceur:  | 
 | 29 | +// https://github.com/google/traceur-compiler/blob/9511c1dafa972bf0de1202a8a863bad02f0f95a8/src/runtime/url.js  | 
 | 30 | + | 
 | 31 | +/**  | 
 | 32 | + * Builds a URI string from already-encoded parts.  | 
 | 33 | + *  | 
 | 34 | + * No encoding is performed. Any component may be omitted as either null or  | 
 | 35 | + * undefined.  | 
 | 36 | + *  | 
 | 37 | + * @param {?string=} opt_scheme The scheme such as 'http'.  | 
 | 38 | + * @param {?string=} opt_userInfo The user name before the '@'.  | 
 | 39 | + * @param {?string=} opt_domain The domain such as 'www.google.com', already  | 
 | 40 | + * URI-encoded.  | 
 | 41 | + * @param {(string|null)=} opt_port The port number.  | 
 | 42 | + * @param {?string=} opt_path The path, already URI-encoded. If it is not  | 
 | 43 | + * empty, it must begin with a slash.  | 
 | 44 | + * @param {?string=} opt_queryData The URI-encoded query data.  | 
 | 45 | + * @param {?string=} opt_fragment The URI-encoded fragment identifier.  | 
 | 46 | + * @return {string} The fully combined URI.  | 
 | 47 | + */  | 
 | 48 | +function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,  | 
 | 49 | + opt_port?: string, opt_path?: string, opt_queryData?: string,  | 
 | 50 | + opt_fragment?: string): string {  | 
 | 51 | + var out = [];  | 
 | 52 | + | 
 | 53 | + if (isPresent(opt_scheme)) {  | 
 | 54 | + out.push(opt_scheme + ':');  | 
 | 55 | + }  | 
 | 56 | + | 
 | 57 | + if (isPresent(opt_domain)) {  | 
 | 58 | + out.push('//');  | 
 | 59 | + | 
 | 60 | + if (isPresent(opt_userInfo)) {  | 
 | 61 | + out.push(opt_userInfo + '@');  | 
41 | 62 |  }  | 
42 | 63 | 
 
  | 
43 |  | - var m = RegExpWrapper.firstMatch(_schemeRe, url);  | 
 | 64 | + out.push(opt_domain);  | 
44 | 65 | 
 
  | 
45 |  | - if (isPresent(m[1])) {  | 
46 |  | - return url;  | 
 | 66 | + if (isPresent(opt_port)) {  | 
 | 67 | + out.push(':' + opt_port);  | 
47 | 68 |  }  | 
 | 69 | + }  | 
48 | 70 | 
 
  | 
49 |  | -  DOM.resolveAndSetHref(UrlResolver.a, baseUrl, url);  | 
50 |  | - return DOM.getHref(UrlResolver.a);  | 
 | 71 | + if (isPresent(opt_path)) {  | 
 | 72 | + out.push(opt_path);  | 
51 | 73 |  }  | 
 | 74 | + | 
 | 75 | + if (isPresent(opt_queryData)) {  | 
 | 76 | + out.push('?' + opt_queryData);  | 
 | 77 | + }  | 
 | 78 | + | 
 | 79 | + if (isPresent(opt_fragment)) {  | 
 | 80 | + out.push('#' + opt_fragment);  | 
 | 81 | + }  | 
 | 82 | + | 
 | 83 | + return out.join('');  | 
52 | 84 | }  | 
53 | 85 | 
 
  | 
54 |  | -var _schemeRe = RegExpWrapper.create('^([^:/?#]+:)?');  | 
 | 86 | +/**  | 
 | 87 | + * A regular expression for breaking a URI into its component parts.  | 
 | 88 | + *  | 
 | 89 | + * {@link http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234} says  | 
 | 90 | + * As the "first-match-wins" algorithm is identical to the "greedy"  | 
 | 91 | + * disambiguation method used by POSIX regular expressions, it is natural and  | 
 | 92 | + * commonplace to use a regular expression for parsing the potential five  | 
 | 93 | + * components of a URI reference.  | 
 | 94 | + *  | 
 | 95 | + * The following line is the regular expression for breaking-down a  | 
 | 96 | + * well-formed URI reference into its components.  | 
 | 97 | + *  | 
 | 98 | + * <pre>  | 
 | 99 | + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?  | 
 | 100 | + * 12 3 4 5 6 7 8 9  | 
 | 101 | + * </pre>  | 
 | 102 | + *  | 
 | 103 | + * The numbers in the second line above are only to assist readability; they  | 
 | 104 | + * indicate the reference points for each subexpression (i.e., each paired  | 
 | 105 | + * parenthesis). We refer to the value matched for subexpression <n> as $<n>.  | 
 | 106 | + * For example, matching the above expression to  | 
 | 107 | + * <pre>  | 
 | 108 | + * http://www.ics.uci.edu/pub/ietf/uri/#Related  | 
 | 109 | + * </pre>  | 
 | 110 | + * results in the following subexpression matches:  | 
 | 111 | + * <pre>  | 
 | 112 | + * $1 = http:  | 
 | 113 | + * $2 = http  | 
 | 114 | + * $3 = //www.ics.uci.edu  | 
 | 115 | + * $4 = www.ics.uci.edu  | 
 | 116 | + * $5 = /pub/ietf/uri/  | 
 | 117 | + * $6 = <undefined>  | 
 | 118 | + * $7 = <undefined>  | 
 | 119 | + * $8 = #Related  | 
 | 120 | + * $9 = Related  | 
 | 121 | + * </pre>  | 
 | 122 | + * where <undefined> indicates that the component is not present, as is the  | 
 | 123 | + * case for the query component in the above example. Therefore, we can  | 
 | 124 | + * determine the value of the five components as  | 
 | 125 | + * <pre>  | 
 | 126 | + * scheme = $2  | 
 | 127 | + * authority = $4  | 
 | 128 | + * path = $5  | 
 | 129 | + * query = $7  | 
 | 130 | + * fragment = $9  | 
 | 131 | + * </pre>  | 
 | 132 | + *  | 
 | 133 | + * The regular expression has been modified slightly to expose the  | 
 | 134 | + * userInfo, domain, and port separately from the authority.  | 
 | 135 | + * The modified version yields  | 
 | 136 | + * <pre>  | 
 | 137 | + * $1 = http scheme  | 
 | 138 | + * $2 = <undefined> userInfo -\  | 
 | 139 | + * $3 = www.ics.uci.edu domain | authority  | 
 | 140 | + * $4 = <undefined> port -/  | 
 | 141 | + * $5 = /pub/ietf/uri/ path  | 
 | 142 | + * $6 = <undefined> query without ?  | 
 | 143 | + * $7 = Related fragment without #  | 
 | 144 | + * </pre>  | 
 | 145 | + * @type {!RegExp}  | 
 | 146 | + * @private  | 
 | 147 | + */  | 
 | 148 | +var _splitRe =  | 
 | 149 | + RegExpWrapper.create('^' +  | 
 | 150 | + '(?:' +  | 
 | 151 | + '([^:/?#.]+)' + // scheme - ignore special characters  | 
 | 152 | + // used by other URL parts such as :,  | 
 | 153 | + // ?, /, #, and .  | 
 | 154 | + ':)?' +  | 
 | 155 | + '(?://' +  | 
 | 156 | + '(?:([^/?#]*)@)?' + // userInfo  | 
 | 157 | + '([\\w\\d\\-\\u0100-\\uffff.%]*)' + // domain - restrict to letters,  | 
 | 158 | + // digits, dashes, dots, percent  | 
 | 159 | + // escapes, and unicode characters.  | 
 | 160 | + '(?::([0-9]+))?' + // port  | 
 | 161 | + ')?' +  | 
 | 162 | + '([^?#]+)?' + // path  | 
 | 163 | + '(?:\\?([^#]*))?' + // query  | 
 | 164 | + '(?:#(.*))?' + // fragment  | 
 | 165 | + '$');  | 
 | 166 | + | 
 | 167 | +/**  | 
 | 168 | + * The index of each URI component in the return value of goog.uri.utils.split.  | 
 | 169 | + * @enum {number}  | 
 | 170 | + */  | 
 | 171 | +enum _ComponentIndex {  | 
 | 172 | + SCHEME = 1,  | 
 | 173 | + USER_INFO,  | 
 | 174 | + DOMAIN,  | 
 | 175 | + PORT,  | 
 | 176 | + PATH,  | 
 | 177 | + QUERY_DATA,  | 
 | 178 | + FRAGMENT  | 
 | 179 | +}  | 
 | 180 | + | 
 | 181 | +/**  | 
 | 182 | + * Splits a URI into its component parts.  | 
 | 183 | + *  | 
 | 184 | + * Each component can be accessed via the component indices; for example:  | 
 | 185 | + * <pre>  | 
 | 186 | + * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];  | 
 | 187 | + * </pre>  | 
 | 188 | + *  | 
 | 189 | + * @param {string} uri The URI string to examine.  | 
 | 190 | + * @return {!Array.<string|undefined>} Each component still URI-encoded.  | 
 | 191 | + * Each component that is present will contain the encoded value, whereas  | 
 | 192 | + * components that are not present will be undefined or empty, depending  | 
 | 193 | + * on the browser's regular expression implementation. Never null, since  | 
 | 194 | + * arbitrary strings may still look like path names.  | 
 | 195 | + */  | 
 | 196 | +function _split(uri: string): List<string | any> {  | 
 | 197 | + return RegExpWrapper.firstMatch(_splitRe, uri);  | 
 | 198 | +}  | 
 | 199 | + | 
 | 200 | +/**  | 
 | 201 | + * Removes dot segments in given path component, as described in  | 
 | 202 | + * RFC 3986, section 5.2.4.  | 
 | 203 | + *  | 
 | 204 | + * @param {string} path A non-empty path component.  | 
 | 205 | + * @return {string} Path component with removed dot segments.  | 
 | 206 | + */  | 
 | 207 | +function _removeDotSegments(path: string): string {  | 
 | 208 | + if (path == '/') return '/';  | 
 | 209 | + | 
 | 210 | + var leadingSlash = path[0] == '/' ? '/' : '';  | 
 | 211 | + var trailingSlash = path[path.length - 1] === '/' ? '/' : '';  | 
 | 212 | + var segments = path.split('/');  | 
 | 213 | + | 
 | 214 | + var out = [];  | 
 | 215 | + var up = 0;  | 
 | 216 | + for (var pos = 0; pos < segments.length; pos++) {  | 
 | 217 | + var segment = segments[pos];  | 
 | 218 | + switch (segment) {  | 
 | 219 | + case '':  | 
 | 220 | + case '.':  | 
 | 221 | + break;  | 
 | 222 | + case '..':  | 
 | 223 | + if (out.length > 0) {  | 
 | 224 | + ListWrapper.removeAt(out, out.length - 1);  | 
 | 225 | + } else {  | 
 | 226 | + up++;  | 
 | 227 | + }  | 
 | 228 | + break;  | 
 | 229 | + default:  | 
 | 230 | + out.push(segment);  | 
 | 231 | + }  | 
 | 232 | + }  | 
 | 233 | + | 
 | 234 | + if (leadingSlash == '') {  | 
 | 235 | + while (up-- > 0) {  | 
 | 236 | + ListWrapper.insert(out, 0, '..');  | 
 | 237 | + }  | 
 | 238 | + | 
 | 239 | + if (out.length === 0) out.push('.');  | 
 | 240 | + }  | 
 | 241 | + | 
 | 242 | + return leadingSlash + out.join('/') + trailingSlash;  | 
 | 243 | +}  | 
 | 244 | + | 
 | 245 | +/**  | 
 | 246 | + * Takes an array of the parts from split and canonicalizes the path part  | 
 | 247 | + * and then joins all the parts.  | 
 | 248 | + * @param {Array.<string?>} parts  | 
 | 249 | + * @return {string}  | 
 | 250 | + */  | 
 | 251 | +function _joinAndCanonicalizePath(parts: List<any>): string {  | 
 | 252 | + var path = parts[_ComponentIndex.PATH];  | 
 | 253 | + path = isBlank(path) ? '' : _removeDotSegments(path);  | 
 | 254 | + parts[_ComponentIndex.PATH] = path;  | 
 | 255 | + | 
 | 256 | + return _buildFromEncodedParts(parts[_ComponentIndex.SCHEME], parts[_ComponentIndex.USER_INFO],  | 
 | 257 | + parts[_ComponentIndex.DOMAIN], parts[_ComponentIndex.PORT], path,  | 
 | 258 | + parts[_ComponentIndex.QUERY_DATA], parts[_ComponentIndex.FRAGMENT]);  | 
 | 259 | +}  | 
 | 260 | + | 
 | 261 | +/**  | 
 | 262 | + * Resolves a URL.  | 
 | 263 | + * @param {string} base The URL acting as the base URL.  | 
 | 264 | + * @param {string} to The URL to resolve.  | 
 | 265 | + * @return {string}  | 
 | 266 | + */  | 
 | 267 | +function _resolveUrl(base: string, url: string): string {  | 
 | 268 | + var parts = _split(url);  | 
 | 269 | + var baseParts = _split(base);  | 
 | 270 | + | 
 | 271 | + if (isPresent(parts[_ComponentIndex.SCHEME])) {  | 
 | 272 | + return _joinAndCanonicalizePath(parts);  | 
 | 273 | + } else {  | 
 | 274 | + parts[_ComponentIndex.SCHEME] = baseParts[_ComponentIndex.SCHEME];  | 
 | 275 | + }  | 
 | 276 | + | 
 | 277 | + for (var i = _ComponentIndex.SCHEME; i <= _ComponentIndex.PORT; i++) {  | 
 | 278 | + if (isBlank(parts[i])) {  | 
 | 279 | + parts[i] = baseParts[i];  | 
 | 280 | + }  | 
 | 281 | + }  | 
 | 282 | + | 
 | 283 | + if (parts[_ComponentIndex.PATH][0] == '/') {  | 
 | 284 | + return _joinAndCanonicalizePath(parts);  | 
 | 285 | + }  | 
 | 286 | + | 
 | 287 | + var path = baseParts[_ComponentIndex.PATH];  | 
 | 288 | + if (isBlank(path)) path = '/';  | 
 | 289 | + var index = path.lastIndexOf('/');  | 
 | 290 | + path = path.substring(0, index + 1) + parts[_ComponentIndex.PATH];  | 
 | 291 | + parts[_ComponentIndex.PATH] = path;  | 
 | 292 | + return _joinAndCanonicalizePath(parts);  | 
 | 293 | +}  | 
0 commit comments