|
| 1 | +/** |
| 2 | + * Dart port of |
| 3 | + * https://github.com/Polymer/platform-dev/blob/896e245a0046a397bfc0190d958d2bd162e8f53c/src/url.js |
| 4 | + * |
| 5 | + * This converts URIs within a document from relative URIs to being absolute URIs. |
| 6 | + */ |
| 7 | + |
| 8 | +library angular.core_dom.resource_url_resolver; |
| 9 | + |
| 10 | +import 'dart:html'; |
| 11 | +import 'dart:js' as js; |
| 12 | + |
| 13 | +import 'package:di/di.dart'; |
| 14 | +import 'package:di/annotations.dart'; |
| 15 | + |
| 16 | +import 'package:angular/core_dom/type_to_uri_mapper.dart'; |
| 17 | + |
| 18 | +@Injectable() |
| 19 | +class ResourceUrlResolver { |
| 20 | + static final RegExp cssUrlRegexp = new RegExp(r'''(\burl\((?:[\s]+)?)(['"]?)([^]*)(\2(?:[\s]+)?\))'''); |
| 21 | + static final RegExp cssImportRegexp = new RegExp(r'(@import[\s]+(?!url\())([^;]*)(;)'); |
| 22 | + static const List<String> urlAttrs = const ['href', 'src', 'action']; |
| 23 | + static final String urlAttrsSelector = '[${urlAttrs.join('],[')}]'; |
| 24 | + static final RegExp urlTemplateSearch = new RegExp('{{.*}}'); |
| 25 | + static final RegExp quotes = new RegExp("[\"\']"); |
| 26 | + |
| 27 | + // Ensures that Uri.base is http/https. |
| 28 | + final _baseUri = Uri.base.origin + ("/"); |
| 29 | + |
| 30 | + final TypeToUriMapper _uriMapper; |
| 31 | + final ResourceResolverConfig _config; |
| 32 | + |
| 33 | + ResourceUrlResolver(this._uriMapper, this._config); |
| 34 | + |
| 35 | + String resolveHtml(String html, [Uri baseUri]) { |
| 36 | + if (baseUri == null) { |
| 37 | + return html; |
| 38 | + } |
| 39 | + HtmlDocument document = new DomParser().parseFromString( |
| 40 | + "<!doctype html><html><body>$html</body></html>", "text/html"); |
| 41 | + _resolveDom(document.body, baseUri); |
| 42 | + return document.body.innerHtml; |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * Resolves all relative URIs within the DOM from being relative to |
| 47 | + * [originalBase] to being absolute. |
| 48 | + */ |
| 49 | + void _resolveDom(Node root, Uri baseUri) { |
| 50 | + _resolveAttributes(root, baseUri); |
| 51 | + _resolveStyles(root, baseUri); |
| 52 | + |
| 53 | + // handle template.content |
| 54 | + for (var template in _querySelectorAll(root, 'template')) { |
| 55 | + if (template.content != null) { |
| 56 | + _resolveDom(template.content, baseUri); |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + Iterable<Element> _querySelectorAll(Node node, String selectors) { |
| 62 | + if (node is DocumentFragment) { |
| 63 | + return node.querySelectorAll(selectors); |
| 64 | + } |
| 65 | + if (node is Element) { |
| 66 | + return node.querySelectorAll(selectors); |
| 67 | + } |
| 68 | + return const []; |
| 69 | + } |
| 70 | + |
| 71 | + void _resolveStyles(Node node, Uri baseUri) { |
| 72 | + var styles = _querySelectorAll(node, 'style'); |
| 73 | + for (var style in styles) { |
| 74 | + _resolveStyle(style, baseUri); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + void _resolveStyle(StyleElement style, Uri baseUri) { |
| 79 | + style.text = resolveCssText(style.text, baseUri); |
| 80 | + } |
| 81 | + |
| 82 | + String resolveCssText(String cssText, Uri baseUri) { |
| 83 | + cssText = _replaceUrlsInCssText(cssText, baseUri, cssUrlRegexp); |
| 84 | + return _replaceUrlsInCssText(cssText, baseUri, cssImportRegexp); |
| 85 | + } |
| 86 | + |
| 87 | + void _resolveAttributes(Node root, Uri baseUri) { |
| 88 | + if (root is Element) { |
| 89 | + _resolveElementAttributes(root, baseUri); |
| 90 | + } |
| 91 | + |
| 92 | + for (var node in _querySelectorAll(root, urlAttrsSelector)) { |
| 93 | + _resolveElementAttributes(node, baseUri); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + void _resolveElementAttributes(Element element, Uri baseUri) { |
| 98 | + var attrs = element.attributes; |
| 99 | + for (var attr in urlAttrs) { |
| 100 | + if (attrs.containsKey(attr)) { |
| 101 | + var value = attrs[attr]; |
| 102 | + if (!value.contains(urlTemplateSearch)) { |
| 103 | + attrs[attr] = combine(baseUri, value).toString(); |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + String _replaceUrlsInCssText(String cssText, Uri baseUri, RegExp regexp) { |
| 110 | + return cssText.replaceAllMapped(regexp, (match) { |
| 111 | + var url = match[3].trim(); |
| 112 | + var urlPath = combine(baseUri, url).toString(); |
| 113 | + return '${match[1].trim()}${match[2]}${urlPath}${match[2]})'; |
| 114 | + }); |
| 115 | + } |
| 116 | + /// Combines a type-based URI with a relative URI. |
| 117 | + /// |
| 118 | + /// [baseUri] is assumed to use package: syntax for package-relative |
| 119 | + /// URIs, while [uri] is assumed to use 'packages/' syntax for |
| 120 | + /// package-relative URIs. Resulting URIs will use 'packages/' to indicate |
| 121 | + /// package-relative URIs. |
| 122 | + String combine(Uri baseUri, String uri) { |
| 123 | + if (!_config.useRelativeUrls) { |
| 124 | + return uri; |
| 125 | + } |
| 126 | + |
| 127 | + if (uri == null) { |
| 128 | + uri = baseUri.path; |
| 129 | + } else { |
| 130 | + // if it's absolute but not package-relative, then just use that |
| 131 | + // The "packages/" test is just for backward compatibility. It's ok to |
| 132 | + // not resolve them, even through they're relative URLs, because in a Dart |
| 133 | + // application, "packages/" is managed by pub which creates a symlinked |
| 134 | + // hierarchy and they should all resolve to the same file at any level |
| 135 | + // that a "packages/" exists. |
| 136 | + if (uri.startsWith("/") || uri.startsWith('packages/')) { |
| 137 | + return uri; |
| 138 | + } |
| 139 | + } |
| 140 | + // If it's not absolute, then resolve it first |
| 141 | + Uri resolved = baseUri.resolve(uri); |
| 142 | + |
| 143 | + // If it's package-relative, tack on 'packages/' - Note that eventually |
| 144 | + // we may want to change this to be '/packages/' to make it truly absolute |
| 145 | + if (resolved.scheme == 'package') { |
| 146 | + return 'packages/${resolved.path}'; |
| 147 | + } else if (resolved.isAbsolute && resolved.toString().startsWith(_baseUri)) { |
| 148 | + var path = resolved.path; |
| 149 | + return path.startsWith("/") ? path.substring(1) : path; |
| 150 | + } else { |
| 151 | + return resolved.toString(); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + String combineWithType(Type type, String uri) { |
| 156 | + if (_config.useRelativeUrls) { |
| 157 | + return combine(_uriMapper.uriForType(type), uri); |
| 158 | + } else { |
| 159 | + return uri; |
| 160 | + } |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +@Injectable() |
| 165 | +class ResourceResolverConfig { |
| 166 | + bool useRelativeUrls; |
| 167 | + |
| 168 | + ResourceResolverConfig(): useRelativeUrls = true; |
| 169 | + |
| 170 | + ResourceResolverConfig.resolveRelativeUrls(this.useRelativeUrls); |
| 171 | +} |
0 commit comments