| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of vmservice_io; |
| |
| _sanitizeWindowsPath(path) { |
| // For Windows we need to massage the paths a bit according to |
| // http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx |
| // |
| // Convert |
| // C:\one\two\three |
| // to |
| // /C:/one/two/three |
| |
| if (_isWindows == false) { |
| // Do nothing when not running Windows. |
| return path; |
| } |
| |
| var fixedPath = "${path.replaceAll('\\', '/')}"; |
| |
| if ((path.length > 2) && (path[1] == ':')) { |
| // Path begins with a drive letter. |
| return '/$fixedPath'; |
| } |
| |
| return fixedPath; |
| } |
| |
| _trimWindowsPath(path) { |
| // Convert /X:/ to X:/. |
| if (_isWindows == false) { |
| // Do nothing when not running Windows. |
| return path; |
| } |
| if (!path.startsWith('/') || (path.length < 3)) { |
| return path; |
| } |
| // Match '/?:'. |
| if ((path[0] == '/') && (path[2] == ':')) { |
| // Remove leading '/'. |
| return path.substring(1); |
| } |
| return path; |
| } |
| |
| // Ensure we have a trailing slash character. |
| _enforceTrailingSlash(uri) { |
| if (!uri.endsWith('/')) { |
| return '$uri/'; |
| } |
| return uri; |
| } |
| |
| class FileRequest { |
| final SendPort sp; |
| final int tag; |
| final Uri uri; |
| final Uri resolvedUri; |
| final String libraryUrl; |
| FileRequest(this.sp, this.tag, this.uri, this.resolvedUri, this.libraryUrl); |
| } |
| |
| @pragma("vm:entry-point") |
| bool _traceLoading = false; |
| @pragma("vm:entry-point") |
| bool _deterministic = false; |
| |
| // State associated with the isolate that is used for loading. |
| class IsolateLoaderState extends IsolateEmbedderData { |
| IsolateLoaderState(this.isolateId); |
| |
| final int isolateId; |
| bool _dead = false; |
| SendPort sp; |
| |
| void init(String packageRootFlag, String packagesConfigFlag, |
| String workingDirectory, String rootScript) { |
| if (_dead) { |
| return; |
| } |
| // _workingDirectory must be set first. |
| _workingDirectory = new Uri.directory(workingDirectory); |
| if (rootScript != null) { |
| _rootScript = Uri.parse(rootScript); |
| } |
| // If the --package-root flag was passed. |
| if (packageRootFlag != null) { |
| _setPackageRoot(packageRootFlag); |
| } |
| // If the --packages flag was passed. |
| if (packagesConfigFlag != null) { |
| _setPackagesConfig(packagesConfigFlag); |
| } |
| } |
| |
| void updatePackageMap(String packagesConfigFlag) { |
| if (packagesConfigFlag == null) { |
| return; |
| } |
| _packageMap = null; |
| _setPackagesConfig(packagesConfigFlag); |
| } |
| |
| void cleanup() { |
| _dead = true; |
| if (_packagesPort != null) { |
| _packagesPort.close(); |
| _packagesPort = null; |
| } |
| } |
| |
| // The working directory when the embedder started. |
| Uri _workingDirectory; |
| |
| // The root script's uri. |
| Uri _rootScript; |
| |
| // Packages are either resolved looking up in a map or resolved from within a |
| // package root. |
| bool get _packagesReady => |
| (_packageRoot != null) || |
| (_packageMap != null) || |
| (_packageError != null); |
| |
| // Error string set if there was an error resolving package configuration. |
| // For example not finding a .packages file or packages/ directory, malformed |
| // .packages file or any other related error. |
| String _packageError = null; |
| |
| // The directory to look in to resolve "package:" scheme URIs. By default it |
| // is the 'packages' directory right next to the script. |
| Uri _packageRoot = null; |
| |
| // The map describing how certain package names are mapped to Uris. |
| Uri _packageConfig = null; |
| Map<String, Uri> _packageMap = null; |
| |
| // We issue only 16 concurrent calls to File.readAsBytes() to stay within |
| // platform-specific resource limits (e.g. max open files). The rest go on |
| // _fileRequestQueue and are processed when we can safely issue them. |
| static final int _maxFileRequests = _deterministic ? 1 : 16; |
| int currentFileRequests = 0; |
| final List<FileRequest> _fileRequestQueue = new List<FileRequest>(); |
| |
| bool get shouldIssueFileRequest => currentFileRequests < _maxFileRequests; |
| void enqueueFileRequest(FileRequest fr) { |
| _fileRequestQueue.add(fr); |
| } |
| |
| FileRequest dequeueFileRequest() { |
| if (_fileRequestQueue.length == 0) { |
| return null; |
| } |
| return _fileRequestQueue.removeAt(0); |
| } |
| |
| _setPackageRoot(String packageRoot) { |
| packageRoot = _sanitizeWindowsPath(packageRoot); |
| if (packageRoot.startsWith('file:') || |
| packageRoot.startsWith('http:') || |
| packageRoot.startsWith('https:')) { |
| packageRoot = _enforceTrailingSlash(packageRoot); |
| _packageRoot = _workingDirectory.resolve(packageRoot); |
| } else { |
| packageRoot = _sanitizeWindowsPath(packageRoot); |
| packageRoot = _trimWindowsPath(packageRoot); |
| _packageRoot = |
| _workingDirectory.resolveUri(new Uri.directory(packageRoot)); |
| } |
| } |
| |
| _setPackagesConfig(String packagesParam) { |
| var packagesName = _sanitizeWindowsPath(packagesParam); |
| var packagesUri = Uri.parse(packagesName); |
| if (packagesUri.scheme == '') { |
| // Script does not have a scheme, assume that it is a path, |
| // resolve it against the working directory. |
| packagesUri = _workingDirectory.resolveUri(packagesUri); |
| } |
| _requestPackagesMap(packagesUri); |
| _pendingPackageLoads.add(() { |
| // Dummy action. |
| }); |
| } |
| |
| // Handling of access to the package root or package map from user code. |
| _triggerPackageResolution(action) { |
| if (_packagesReady) { |
| // Packages are ready. Execute the action now. |
| action(); |
| } else { |
| if (_pendingPackageLoads.isEmpty) { |
| // Package resolution has not been setup yet, and this is the first |
| // request for package resolution & loading. |
| _requestPackagesMap(); |
| } |
| // Register the action for when the package resolution is ready. |
| _pendingPackageLoads.add(action); |
| } |
| } |
| |
| // A list of callbacks which should be invoked after the package map has been |
| // loaded. |
| List<Function> _pendingPackageLoads = []; |
| |
| // Given a uri with a 'package' scheme, return a Uri that is prefixed with |
| // the package root or resolved relative to the package configuration. |
| Uri _resolvePackageUri(Uri uri) { |
| assert(uri.scheme == "package"); |
| assert(_packagesReady); |
| |
| if (uri.host.isNotEmpty) { |
| var path = '${uri.host}${uri.path}'; |
| var right = 'package:$path'; |
| var wrong = 'package://$path'; |
| |
| throw "URIs using the 'package:' scheme should look like " |
| "'$right', not '$wrong'."; |
| } |
| |
| var packageNameEnd = uri.path.indexOf('/'); |
| if (packageNameEnd == 0) { |
| // Package URIs must have a non-empty package name (not start with "/"). |
| throw "URIS using the 'package:' scheme should look like " |
| "'package:packageName${uri.path}', not 'package:${uri.path}'"; |
| } |
| if (_traceLoading) { |
| _log('Resolving package with uri path: ${uri.path}'); |
| } |
| var resolvedUri; |
| if (_packageError != null) { |
| if (_traceLoading) { |
| _log("Resolving package with pending resolution error: $_packageError"); |
| } |
| throw _packageError; |
| } else if (_packageRoot != null) { |
| resolvedUri = _packageRoot.resolve(uri.path); |
| } else { |
| if (packageNameEnd < 0) { |
| // Package URIs must have a path after the package name, even if it's |
| // just "/". |
| throw "URIS using the 'package:' scheme should look like " |
| "'package:${uri.path}/', not 'package:${uri.path}'"; |
| } |
| var packageName = uri.path.substring(0, packageNameEnd); |
| var mapping = _packageMap[packageName]; |
| if (_traceLoading) { |
| _log("Mapped '$packageName' package to '$mapping'"); |
| } |
| if (mapping == null) { |
| throw "No mapping for '$packageName' package when resolving '$uri'."; |
| } |
| var path; |
| assert(uri.path.length > packageName.length); |
| path = uri.path.substring(packageName.length + 1); |
| if (_traceLoading) { |
| _log("Path to be resolved in package: $path"); |
| } |
| resolvedUri = mapping.resolve(path); |
| } |
| if (_traceLoading) { |
| _log("Resolved '$uri' to '$resolvedUri'."); |
| } |
| return resolvedUri; |
| } |
| |
| RawReceivePort _packagesPort; |
| |
| void _requestPackagesMap([Uri packageConfig]) { |
| if (_packagesPort != null) { |
| // Already scheduled. |
| return; |
| } |
| // Create a port to receive the packages map on. |
| _packagesPort = new RawReceivePort(_handlePackagesReply); |
| var sp = _packagesPort.sendPort; |
| |
| if (packageConfig != null) { |
| // Explicitly specified .packages path. |
| _handlePackagesRequest(sp, _traceLoading, -2, packageConfig); |
| } else { |
| // Search for .packages starting at the root script. |
| _handlePackagesRequest(sp, _traceLoading, -1, _rootScript); |
| } |
| |
| if (_traceLoading) { |
| _log("Requested packages map for '$_rootScript'."); |
| } |
| } |
| |
| void _handlePackagesReply(msg) { |
| if (_packagesPort == null) { |
| return; |
| } |
| // Make sure to close the _packagePort before any other action. |
| _packagesPort.close(); |
| _packagesPort = null; |
| |
| if (_traceLoading) { |
| _log("Got packages reply: $msg"); |
| } |
| if (msg is String) { |
| if (_traceLoading) { |
| _log("Got failure response on package port: '$msg'"); |
| } |
| // Remember the error message. |
| _packageError = msg; |
| } else if (msg is List) { |
| if (msg.length == 1) { |
| if (_traceLoading) { |
| _log("Received package root: '${msg[0]}'"); |
| } |
| _packageRoot = Uri.parse(msg[0]); |
| } else { |
| // First entry contains the location of the loaded .packages file. |
| assert((msg.length % 2) == 0); |
| assert(msg.length >= 2); |
| assert(msg[1] == null); |
| _packageConfig = Uri.parse(msg[0]); |
| _packageMap = new Map<String, Uri>(); |
| for (var i = 2; i < msg.length; i += 2) { |
| // TODO(iposva): Complain about duplicate entries. |
| _packageMap[msg[i]] = Uri.parse(msg[i + 1]); |
| } |
| if (_traceLoading) { |
| _log("Setup package map: $_packageMap"); |
| } |
| } |
| } else { |
| _packageError = "Bad type of packages reply: ${msg.runtimeType}"; |
| if (_traceLoading) { |
| _log(_packageError); |
| } |
| } |
| |
| // Resolve all pending package loads now that we know how to resolve them. |
| while (_pendingPackageLoads.length > 0) { |
| // Order does not matter as we queue all of the requests up right now. |
| var req = _pendingPackageLoads.removeLast(); |
| // Call the registered closure, to handle the delayed action. |
| req(); |
| } |
| // Reset the pending package loads to empty. So that we eventually can |
| // finish loading. |
| _pendingPackageLoads = []; |
| } |
| } |
| |
| _log(msg) { |
| print("% $msg"); |
| } |
| |
| var _httpClient; |
| |
| // Send a response to the requesting isolate. |
| void _sendResourceResponse(SendPort sp, int tag, Uri uri, Uri resolvedUri, |
| String libraryUrl, dynamic data) { |
| assert((data is List<int>) || (data is String)); |
| var msg = new List(5); |
| if (data is String) { |
| // We encountered an error, flip the sign of the tag to indicate that. |
| tag = -tag; |
| if (libraryUrl == null) { |
| data = 'Could not load "$uri": $data'; |
| } else { |
| data = 'Could not import "$uri" from "$libraryUrl": $data'; |
| } |
| } |
| msg[0] = tag; |
| msg[1] = uri.toString(); |
| msg[2] = resolvedUri.toString(); |
| msg[3] = libraryUrl; |
| msg[4] = data; |
| sp.send(msg); |
| } |
| |
| // Send a response to the requesting isolate. |
| void _sendExtensionImportResponse( |
| SendPort sp, Uri uri, String libraryUrl, String resolvedUri) { |
| var msg = new List(5); |
| int tag = _Dart_kImportExtension; |
| if (resolvedUri == null) { |
| // We could not resolve the dart-ext: uri. |
| tag = -tag; |
| resolvedUri = 'Could not resolve "$uri" from "$libraryUrl"'; |
| } |
| msg[0] = tag; |
| msg[1] = uri.toString(); |
| msg[2] = resolvedUri; |
| msg[3] = libraryUrl; |
| msg[4] = resolvedUri; |
| sp.send(msg); |
| } |
| |
| void _loadHttp( |
| SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) { |
| if (_httpClient == null) { |
| _httpClient = new HttpClient()..maxConnectionsPerHost = 6; |
| } |
| _httpClient |
| .getUrl(resolvedUri) |
| .then((HttpClientRequest request) => request.close()) |
| .then((HttpClientResponse response) { |
| var builder = new BytesBuilder(copy: false); |
| response.listen(builder.add, onDone: () { |
| if (response.statusCode != 200) { |
| var msg = "Failure getting $resolvedUri:\n" |
| " ${response.statusCode} ${response.reasonPhrase}"; |
| _sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, msg); |
| } else { |
| _sendResourceResponse( |
| sp, tag, uri, resolvedUri, libraryUrl, builder.takeBytes()); |
| } |
| }, onError: (e) { |
| _sendResourceResponse( |
| sp, tag, uri, resolvedUri, libraryUrl, e.toString()); |
| }); |
| }).catchError((e) { |
| _sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString()); |
| }); |
| // It's just here to push an event on the event loop so that we invoke the |
| // scheduled microtasks. |
| Timer.run(() {}); |
| } |
| |
| void _loadFile(IsolateLoaderState loaderState, SendPort sp, int tag, Uri uri, |
| Uri resolvedUri, String libraryUrl) { |
| var path = resolvedUri.toFilePath(); |
| var sourceFile = new File(path); |
| sourceFile.readAsBytes().then((data) { |
| _sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, data); |
| }, onError: (e) { |
| _sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, e.toString()); |
| }).whenComplete(() { |
| loaderState.currentFileRequests--; |
| while (loaderState.shouldIssueFileRequest) { |
| FileRequest fr = loaderState.dequeueFileRequest(); |
| if (fr == null) { |
| break; |
| } |
| _loadFile( |
| loaderState, fr.sp, fr.tag, fr.uri, fr.resolvedUri, fr.libraryUrl); |
| loaderState.currentFileRequests++; |
| } |
| }); |
| } |
| |
| void _loadDataUri( |
| SendPort sp, int tag, Uri uri, Uri resolvedUri, String libraryUrl) { |
| try { |
| var mime = uri.data.mimeType; |
| if ((mime != "application/dart") && (mime != "text/plain")) { |
| throw "MIME-type must be application/dart or text/plain: $mime given."; |
| } |
| var charset = uri.data.charset; |
| if ((charset != "utf-8") && (charset != "US-ASCII")) { |
| // The C++ portion of the embedder assumes UTF-8. |
| throw "Only utf-8 or US-ASCII encodings are supported: $charset given."; |
| } |
| _sendResourceResponse( |
| sp, tag, uri, resolvedUri, libraryUrl, uri.data.contentAsBytes()); |
| } catch (e) { |
| _sendResourceResponse(sp, tag, uri, resolvedUri, libraryUrl, |
| "Invalid data uri ($uri):\n $e"); |
| } |
| } |
| |
| // Loading a package URI needs to first map the package name to a loadable |
| // URI. |
| _loadPackage(IsolateLoaderState loaderState, SendPort sp, bool traceLoading, |
| int tag, Uri uri, Uri resolvedUri, String libraryUrl) { |
| if (loaderState._packagesReady) { |
| var resolvedUri; |
| try { |
| resolvedUri = loaderState._resolvePackageUri(uri); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Exception ($e) when resolving package URI: $uri"); |
| } |
| // Report error. |
| _sendResourceResponse( |
| sp, tag, uri, resolvedUri, libraryUrl, e.toString()); |
| return; |
| } |
| // Recursively call with the new resolved uri. |
| _handleResourceRequest( |
| loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl); |
| } else { |
| if (loaderState._pendingPackageLoads.isEmpty) { |
| // Package resolution has not been setup yet, and this is the first |
| // request for package resolution & loading. |
| loaderState._requestPackagesMap(); |
| } |
| // Register the action of loading this package once the package resolution |
| // is ready. |
| loaderState._pendingPackageLoads.add(() { |
| _handleResourceRequest( |
| loaderState, sp, traceLoading, tag, uri, uri, libraryUrl); |
| }); |
| if (traceLoading) { |
| _log("Pending package load of '$uri': " |
| "${loaderState._pendingPackageLoads.length} pending"); |
| } |
| } |
| } |
| |
| // TODO(johnmccutchan): This and most other top level functions in this file |
| // should be turned into methods on the IsolateLoaderState class. |
| _handleResourceRequest(IsolateLoaderState loaderState, SendPort sp, |
| bool traceLoading, int tag, Uri uri, Uri resolvedUri, String libraryUrl) { |
| if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') { |
| if (loaderState.shouldIssueFileRequest) { |
| _loadFile(loaderState, sp, tag, uri, resolvedUri, libraryUrl); |
| loaderState.currentFileRequests++; |
| } else { |
| FileRequest fr = new FileRequest(sp, tag, uri, resolvedUri, libraryUrl); |
| loaderState.enqueueFileRequest(fr); |
| } |
| } else if ((resolvedUri.scheme == 'http') || |
| (resolvedUri.scheme == 'https')) { |
| _loadHttp(sp, tag, uri, resolvedUri, libraryUrl); |
| } else if ((resolvedUri.scheme == 'data')) { |
| _loadDataUri(sp, tag, uri, resolvedUri, libraryUrl); |
| } else if ((resolvedUri.scheme == 'package')) { |
| _loadPackage( |
| loaderState, sp, traceLoading, tag, uri, resolvedUri, libraryUrl); |
| } else { |
| _sendResourceResponse( |
| sp, |
| tag, |
| uri, |
| resolvedUri, |
| libraryUrl, |
| 'Unknown scheme (${resolvedUri.scheme}) for ' |
| '$resolvedUri'); |
| } |
| } |
| |
| // Handling of packages requests. Finding and parsing of .packages file or |
| // packages/ directories. |
| const _LF = 0x0A; |
| const _CR = 0x0D; |
| const _SPACE = 0x20; |
| const _HASH = 0x23; |
| const _DOT = 0x2E; |
| const _COLON = 0x3A; |
| const _DEL = 0x7F; |
| |
| const _invalidPackageNameChars = const [ |
| true, // space |
| false, // ! |
| true, // " |
| true, // # |
| false, // $ |
| true, // % |
| false, // & |
| false, // ' |
| false, // ( |
| false, // ) |
| false, // * |
| false, // + |
| false, // , |
| false, // - |
| false, // . |
| true, // / |
| false, // 0 |
| false, // 1 |
| false, // 2 |
| false, // 3 |
| false, // 4 |
| false, // 5 |
| false, // 6 |
| false, // 7 |
| false, // 8 |
| false, // 9 |
| true, // : |
| false, // ; |
| true, // < |
| false, // = |
| true, // > |
| true, // ? |
| false, // @ |
| false, // A |
| false, // B |
| false, // C |
| false, // D |
| false, // E |
| false, // F |
| false, // G |
| false, // H |
| false, // I |
| false, // J |
| false, // K |
| false, // L |
| false, // M |
| false, // N |
| false, // O |
| false, // P |
| false, // Q |
| false, // R |
| false, // S |
| false, // T |
| false, // U |
| false, // V |
| false, // W |
| false, // X |
| false, // Y |
| false, // Z |
| true, // [ |
| true, // \ |
| true, // ] |
| true, // ^ |
| false, // _ |
| true, // ` |
| false, // a |
| false, // b |
| false, // c |
| false, // d |
| false, // e |
| false, // f |
| false, // g |
| false, // h |
| false, // i |
| false, // j |
| false, // k |
| false, // l |
| false, // m |
| false, // n |
| false, // o |
| false, // p |
| false, // q |
| false, // r |
| false, // s |
| false, // t |
| false, // u |
| false, // v |
| false, // w |
| false, // x |
| false, // y |
| false, // z |
| true, // { |
| true, // | |
| true, // } |
| false, // ~ |
| true, // DEL |
| ]; |
| |
| _parsePackagesFile( |
| SendPort sp, bool traceLoading, Uri packagesFile, List<int> data) { |
| // The first entry contains the location of the identified .packages file |
| // instead of a mapping. |
| var result = [packagesFile.toString(), null]; |
| var index = 0; |
| var len = data.length; |
| while (index < len) { |
| var start = index; |
| var char = data[index]; |
| if ((char == _CR) || (char == _LF)) { |
| // Skipping empty lines. |
| index++; |
| continue; |
| } |
| |
| // Identify split within the line and end of the line. |
| var separator = -1; |
| var end = len; |
| // Verifying validity of package name while scanning the line. |
| var nonDot = false; |
| var invalidPackageName = false; |
| |
| // Scan to the end of the line or data. |
| while (index < len) { |
| char = data[index++]; |
| // If we have not reached the separator yet, determine whether we are |
| // scanning legal package name characters. |
| if (separator == -1) { |
| if ((char == _COLON)) { |
| // The first colon on a line is the separator between package name and |
| // related URI. |
| separator = index - 1; |
| } else { |
| // Still scanning the package name part. Check for the validity of |
| // the characters. |
| nonDot = nonDot || (char != _DOT); |
| invalidPackageName = invalidPackageName || |
| (char < _SPACE) || |
| (char > _DEL) || |
| _invalidPackageNameChars[char - _SPACE]; |
| } |
| } |
| // Identify end of line. |
| if ((char == _CR) || (char == _LF)) { |
| end = index - 1; |
| break; |
| } |
| } |
| |
| // No further handling needed for comment lines. |
| if (data[start] == _HASH) { |
| if (traceLoading) { |
| _log("Skipping comment in $packagesFile:\n" |
| "${new String.fromCharCodes(data, start, end)}"); |
| } |
| continue; |
| } |
| |
| // Check for a badly formatted line, starting with a ':'. |
| if (separator == start) { |
| var line = new String.fromCharCodes(data, start, end); |
| if (traceLoading) { |
| _log("Line starts with ':' in $packagesFile:\n" |
| "$line"); |
| } |
| sp.send("Missing package name in $packagesFile:\n" |
| "$line"); |
| return; |
| } |
| |
| // Ensure there is a separator on the line. |
| if (separator == -1) { |
| var line = new String.fromCharCodes(data, start, end); |
| if (traceLoading) { |
| _log("Line has no ':' in $packagesFile:\n" |
| "$line"); |
| } |
| sp.send("Missing ':' separator in $packagesFile:\n" |
| "$line"); |
| return; |
| } |
| |
| var packageName = new String.fromCharCodes(data, start, separator); |
| |
| // Check for valid package name. |
| if (invalidPackageName || !nonDot) { |
| var line = new String.fromCharCodes(data, start, end); |
| if (traceLoading) { |
| _log("Invalid package name $packageName in $packagesFile"); |
| } |
| sp.send("Invalid package name '$packageName' in $packagesFile:\n" |
| "$line"); |
| return; |
| } |
| |
| if (traceLoading) { |
| _log("packageName: $packageName"); |
| } |
| var packageUri = new String.fromCharCodes(data, separator + 1, end); |
| if (traceLoading) { |
| _log("original packageUri: $packageUri"); |
| } |
| // Ensure the package uri ends with a /. |
| if (!packageUri.endsWith("/")) { |
| packageUri = "$packageUri/"; |
| } |
| packageUri = packagesFile.resolve(packageUri).toString(); |
| if (traceLoading) { |
| _log("mapping: $packageName -> $packageUri"); |
| } |
| result.add(packageName); |
| result.add(packageUri); |
| } |
| |
| if (traceLoading) { |
| _log("Parsed packages file at $packagesFile. Sending:\n$result"); |
| } |
| sp.send(result); |
| } |
| |
| _loadPackagesFile(SendPort sp, bool traceLoading, Uri packagesFile) async { |
| try { |
| var data = await new File.fromUri(packagesFile).readAsBytes(); |
| if (traceLoading) { |
| _log("Loaded packages file from $packagesFile:\n" |
| "${new String.fromCharCodes(data)}"); |
| } |
| _parsePackagesFile(sp, traceLoading, packagesFile, data); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Error loading packages: $e\n$s"); |
| } |
| sp.send("Uncaught error ($e) loading packages file."); |
| } |
| } |
| |
| _findPackagesFile(SendPort sp, bool traceLoading, Uri base) async { |
| try { |
| // Walk up the directory hierarchy to check for the existence of |
| // .packages files in parent directories and for the existence of a |
| // packages/ directory on the first iteration. |
| var dir = new File.fromUri(base).parent; |
| var prev = null; |
| // Keep searching until we reach the root. |
| while ((prev == null) || (prev.path != dir.path)) { |
| // Check for the existence of a .packages file and if it exists try to |
| // load and parse it. |
| var dirUri = dir.uri; |
| var packagesFile = dirUri.resolve(".packages"); |
| if (traceLoading) { |
| _log("Checking for $packagesFile file."); |
| } |
| var exists = await new File.fromUri(packagesFile).exists(); |
| if (traceLoading) { |
| _log("$packagesFile exists: $exists"); |
| } |
| if (exists) { |
| _loadPackagesFile(sp, traceLoading, packagesFile); |
| return; |
| } |
| // Move up one level. |
| prev = dir; |
| dir = dir.parent; |
| } |
| |
| // No .packages file was found. |
| if (traceLoading) { |
| _log("Could not resolve a package location from $base"); |
| } |
| sp.send("Could not resolve a package location for base at $base"); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Error loading packages: $e\n$s"); |
| } |
| sp.send("Uncaught error ($e) loading packages file."); |
| } |
| } |
| |
| Future<bool> _loadHttpPackagesFile( |
| SendPort sp, bool traceLoading, Uri resource) async { |
| try { |
| if (_httpClient == null) { |
| _httpClient = new HttpClient()..maxConnectionsPerHost = 6; |
| } |
| if (traceLoading) { |
| _log("Fetching packages file from '$resource'."); |
| } |
| var req = await _httpClient.getUrl(resource); |
| var rsp = await req.close(); |
| var builder = new BytesBuilder(copy: false); |
| await for (var bytes in rsp) { |
| builder.add(bytes); |
| } |
| if (rsp.statusCode != 200) { |
| if (traceLoading) { |
| _log("Got status ${rsp.statusCode} fetching '$resource'."); |
| } |
| return false; |
| } |
| var data = builder.takeBytes(); |
| if (traceLoading) { |
| _log("Loaded packages file from '$resource':\n" |
| "${new String.fromCharCodes(data)}"); |
| } |
| _parsePackagesFile(sp, traceLoading, resource, data); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Error loading packages file from '$resource': $e\n$s"); |
| } |
| sp.send("Uncaught error ($e) loading packages file from '$resource'."); |
| } |
| return false; |
| } |
| |
| _loadPackagesData(sp, traceLoading, resource) { |
| try { |
| var data = resource.data; |
| var mime = data.mimeType; |
| if (mime != "text/plain") { |
| throw "MIME-type must be text/plain: $mime given."; |
| } |
| var charset = data.charset; |
| if ((charset != "utf-8") && (charset != "US-ASCII")) { |
| // The C++ portion of the embedder assumes UTF-8. |
| throw "Only utf-8 or US-ASCII encodings are supported: $charset given."; |
| } |
| _parsePackagesFile(sp, traceLoading, resource, data.contentAsBytes()); |
| } catch (e) { |
| sp.send("Uncaught error ($e) loading packages data."); |
| } |
| } |
| |
| // This code used to exist in a second isolate and so it uses a SendPort to |
| // report it's return value. This could be refactored so that it returns it's |
| // value and the caller could wait on the future rather than a message on |
| // SendPort. |
| _handlePackagesRequest( |
| SendPort sp, bool traceLoading, int tag, Uri resource) async { |
| try { |
| if (tag == -1) { |
| if (resource.scheme == '' || resource.scheme == 'file') { |
| _findPackagesFile(sp, traceLoading, resource); |
| } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
| // Try to load the .packages file next to the resource. |
| var packagesUri = resource.resolve(".packages"); |
| var exists = await _loadHttpPackagesFile(sp, traceLoading, packagesUri); |
| if (!exists) { |
| // Loading of the .packages file failed for http/https based scripts |
| sp.send([null]); |
| } |
| } else { |
| sp.send("Unsupported scheme used to locate .packages file: " |
| "'$resource'."); |
| } |
| } else if (tag == -2) { |
| if (traceLoading) { |
| _log("Handling load of packages map: '$resource'."); |
| } |
| if (resource.scheme == '' || resource.scheme == 'file') { |
| var exists = await new File.fromUri(resource).exists(); |
| if (exists) { |
| _loadPackagesFile(sp, traceLoading, resource); |
| } else { |
| sp.send("Packages file '$resource' not found."); |
| } |
| } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
| var exists = await _loadHttpPackagesFile(sp, traceLoading, resource); |
| if (!exists) { |
| sp.send("Packages file '$resource' not found."); |
| } |
| } else if (resource.scheme == 'data') { |
| _loadPackagesData(sp, traceLoading, resource); |
| } else { |
| sp.send("Unknown scheme (${resource.scheme}) for package file at " |
| "'$resource'."); |
| } |
| } else { |
| sp.send("Unknown packages request tag: $tag for '$resource'."); |
| } |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Error handling packages request: $e\n$s"); |
| } |
| sp.send("Uncaught error ($e) handling packages request."); |
| } |
| } |
| |
| // Shutdown all active loaders by sending an error message. |
| void shutdownLoaders() { |
| String message = 'Service shutdown'; |
| if (_httpClient != null) { |
| _httpClient.close(force: true); |
| _httpClient = null; |
| } |
| isolateEmbedderData.values.toList().forEach((ied) { |
| IsolateLoaderState ils = ied; |
| ils.cleanup(); |
| assert(ils.sp != null); |
| _sendResourceResponse(ils.sp, 1, null, null, null, message); |
| }); |
| } |
| |
| // See Dart_LibraryTag in dart_api.h |
| const _Dart_kCanonicalizeUrl = 0; // Canonicalize the URL. |
| const _Dart_kScriptTag = 1; // Load the root script. |
| const _Dart_kSourceTag = 2; // Load a part source. |
| const _Dart_kImportTag = 3; // Import a library. |
| |
| // Extra requests. Keep these in sync between loader.dart and builtin.dart. |
| const _Dart_kInitLoader = 4; // Initialize the loader. |
| const _Dart_kResourceLoad = 5; // Resource class support. |
| const _Dart_kGetPackageRootUri = 6; // Uri of the packages/ directory. |
| const _Dart_kGetPackageConfigUri = 7; // Uri of the .packages file. |
| const _Dart_kResolvePackageUri = 8; // Resolve a package: uri. |
| |
| // Extra requests. Keep these in sync between loader.dart and loader.cc. |
| const _Dart_kImportExtension = 9; // Import a dart-ext: file. |
| const _Dart_kResolveAsFilePath = 10; // Resolve uri to file path. |
| |
| // External entry point for loader requests. |
| _processLoadRequest(request) { |
| assert(request is List); |
| assert(request.length > 4); |
| |
| // Should we trace loading? |
| bool traceLoading = request[0]; |
| |
| // This is the sending isolate's Dart_GetMainPortId(). |
| int isolateId = request[1]; |
| |
| // The tag describing the operation. |
| int tag = request[2]; |
| |
| // The send port to send the response on. |
| SendPort sp = request[3]; |
| |
| // Grab the loader state for the requesting isolate. |
| IsolateLoaderState loaderState = isolateEmbedderData[isolateId]; |
| |
| // We are either about to initialize the loader, or, we already have. |
| assert((tag == _Dart_kInitLoader) || (loaderState != null)); |
| |
| // Handle the request specified in the tag. |
| switch (tag) { |
| case _Dart_kScriptTag: |
| { |
| Uri uri = Uri.parse(request[4]); |
| // Remember the root script. |
| loaderState._rootScript = uri; |
| _handleResourceRequest( |
| loaderState, sp, traceLoading, tag, uri, uri, null); |
| } |
| break; |
| case _Dart_kSourceTag: |
| case _Dart_kImportTag: |
| { |
| // The url of the file being loaded. |
| var uri = Uri.parse(request[4]); |
| // The library that is importing/parting the file. |
| String libraryUrl = request[5]; |
| _handleResourceRequest( |
| loaderState, sp, traceLoading, tag, uri, uri, libraryUrl); |
| } |
| break; |
| case _Dart_kInitLoader: |
| { |
| String packageRoot = request[4]; |
| String packagesFile = request[5]; |
| String workingDirectory = request[6]; |
| String rootScript = request[7]; |
| bool isReloading = request[8]; |
| if (loaderState == null) { |
| loaderState = new IsolateLoaderState(isolateId); |
| isolateEmbedderData[isolateId] = loaderState; |
| loaderState.init( |
| packageRoot, packagesFile, workingDirectory, rootScript); |
| } else if (isReloading) { |
| loaderState.updatePackageMap(packagesFile); |
| } |
| loaderState.sp = sp; |
| assert(isolateEmbedderData[isolateId] == loaderState); |
| } |
| break; |
| case _Dart_kResourceLoad: |
| { |
| Uri uri = Uri.parse(request[4]); |
| _handleResourceRequest( |
| loaderState, sp, traceLoading, tag, uri, uri, null); |
| } |
| break; |
| case _Dart_kGetPackageRootUri: |
| loaderState._triggerPackageResolution(() { |
| // The package root is deprecated and now always returns null. |
| sp.send(null); |
| }); |
| break; |
| case _Dart_kGetPackageConfigUri: |
| loaderState._triggerPackageResolution(() { |
| // Respond with the packages config (if any) after package resolution. |
| sp.send(loaderState._packageConfig); |
| }); |
| break; |
| case _Dart_kResolvePackageUri: |
| Uri uri = Uri.parse(request[4]); |
| loaderState._triggerPackageResolution(() { |
| // Respond with the resolved package uri after package resolution. |
| Uri resolvedUri; |
| try { |
| resolvedUri = loaderState._resolvePackageUri(uri); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Exception ($e) when resolving package URI: $uri"); |
| } |
| resolvedUri = null; |
| } |
| sp.send(resolvedUri); |
| }); |
| break; |
| case _Dart_kImportExtension: |
| Uri uri = Uri.parse(request[4]); |
| String libraryUri = request[5]; |
| // Strip any filename off of the libraryUri's path. |
| int index = libraryUri.lastIndexOf('/'); |
| var path; |
| if (index == -1) { |
| path = './'; |
| } else { |
| path = libraryUri.substring(0, index + 1); |
| } |
| var pathUri = Uri.parse(path); |
| switch (pathUri.scheme) { |
| case '': |
| case 'file': |
| _sendExtensionImportResponse( |
| sp, uri, libraryUri, pathUri.toFilePath()); |
| break; |
| case 'data': |
| case 'http': |
| case 'https': |
| _sendExtensionImportResponse(sp, uri, libraryUri, pathUri.toString()); |
| break; |
| case 'package': |
| // Start package resolution. |
| loaderState._triggerPackageResolution(() { |
| // Attempt to find the fully resolved uri of [path]. |
| Uri resolvedUri; |
| try { |
| resolvedUri = loaderState._resolvePackageUri(pathUri); |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Exception ($e) when resolving package URI: $uri"); |
| } |
| resolvedUri = null; |
| } |
| _sendExtensionImportResponse( |
| sp, uri, libraryUri, resolvedUri.toString()); |
| }); |
| break; |
| default: |
| if (traceLoading) { |
| _log('Unknown scheme (${pathUri.scheme}) in $pathUri.'); |
| } |
| _sendExtensionImportResponse(sp, uri, libraryUri, null); |
| break; |
| } |
| break; |
| case _Dart_kResolveAsFilePath: |
| loaderState._triggerPackageResolution(() { |
| String uri = request[4]; |
| Uri resolvedUri = Uri.parse(_sanitizeWindowsPath(uri)); |
| try { |
| if (resolvedUri.scheme == 'package') { |
| resolvedUri = loaderState._resolvePackageUri(resolvedUri); |
| } |
| if (resolvedUri.scheme == '' || resolvedUri.scheme == 'file') { |
| resolvedUri = loaderState._workingDirectory.resolveUri(resolvedUri); |
| var msg = new List(5); |
| msg[0] = tag; |
| msg[1] = uri; |
| msg[2] = resolvedUri.toString(); |
| msg[3] = null; |
| msg[4] = resolvedUri.toFilePath(); |
| sp.send(msg); |
| } else { |
| throw "Cannot resolve scheme (${resolvedUri.scheme}) to file path" |
| " for $resolvedUri"; |
| } |
| } catch (e) { |
| var msg = new List(5); |
| msg[0] = -tag; |
| msg[1] = uri; |
| msg[2] = resolvedUri.toString(); |
| msg[3] = null; |
| msg[4] = e.toString(); |
| sp.send(msg); |
| } |
| }); |
| break; |
| default: |
| _log('Unknown loader request tag=$tag from $isolateId'); |
| } |
| } |