| // 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; |
| |
| _log(msg) { |
| print("% $msg"); |
| } |
| |
| var _httpClient; |
| |
| // Send a response to the requesting isolate. |
| void _sendResourceResponse(SendPort sp, int id, dynamic data) { |
| assert((data is List<int>) || (data is String)); |
| var msg = new List(2); |
| msg[0] = id; |
| msg[1] = data; |
| sp.send(msg); |
| } |
| |
| void _loadHttp(SendPort sp, int id, Uri uri) { |
| if (_httpClient == null) { |
| _httpClient = new HttpClient()..maxConnectionsPerHost = 6; |
| } |
| _httpClient.getUrl(uri) |
| .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 $uri:\n" |
| " ${response.statusCode} ${response.reasonPhrase}"; |
| _sendResourceResponse(sp, id, msg); |
| } else { |
| _sendResourceResponse(sp, id, builder.takeBytes()); |
| } |
| }, |
| onError: (e) { |
| _sendResourceResponse(sp, id, e.toString()); |
| }); |
| }) |
| .catchError((e) { |
| _sendResourceResponse(sp, id, 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(SendPort sp, int id, Uri uri) { |
| var path = uri.toFilePath(); |
| var sourceFile = new File(path); |
| sourceFile.readAsBytes().then((data) { |
| _sendResourceResponse(sp, id, data); |
| }, |
| onError: (e) { |
| var err = "Error loading $uri:\n $e"; |
| _sendResourceResponse(sp, id, err); |
| }); |
| } |
| |
| void _loadDataUri(SendPort sp, int id, Uri uri) { |
| 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, id, uri.data.contentAsBytes()); |
| } catch (e) { |
| _sendResourceResponse(sp, id, "Invalid data uri ($uri):\n $e"); |
| } |
| } |
| |
| _handleResourceRequest(SendPort sp, bool traceLoading, int id, Uri resource) { |
| if (resource.scheme == 'file') { |
| _loadFile(sp, id, resource); |
| } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
| _loadHttp(sp, id, resource); |
| } else if ((resource.scheme == 'data')) { |
| _loadDataUri(sp, id, resource); |
| } else { |
| _sendResourceResponse(sp, id, |
| 'Unknown scheme (${resource.scheme}) for $resource'); |
| } |
| } |
| |
| |
| // 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 [ |
| // space ! " # $ % & ' |
| true , false, true , true , false, true , false, false, |
| // ( ) * + , - . / |
| false, false, false, false, false, false, false, true , |
| // 0 1 2 3 4 5 6 7 |
| false, false, false, false, false, false, false, false, |
| // 8 9 : ; < = > ? |
| false, false, true , false, true , false, true , true , |
| // @ A B C D E F G |
| false, false, false, false, false, false, false, false, |
| // H I J K L M N O |
| false, false, false, false, false, false, false, false, |
| // P Q R S T U V W |
| false, false, false, false, false, false, false, false, |
| // X Y Z [ \ ] ^ _ |
| false, false, false, true , true , true , true , false, |
| // ` a b c d e f g |
| true , false, false, false, false, false, false, false, |
| // h i j k l m n o |
| false, false, false, false, false, false, false, false, |
| // p q r s t u v w |
| false, false, false, false, false, false, false, false, |
| // x y z { | } ~ DEL |
| false, false, false, true , true , true , false, true |
| ]; |
| |
| _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 existense 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; |
| } |
| // On the first loop try whether there is a packages/ directory instead. |
| if (prev == null) { |
| var packageRoot = dirUri.resolve("packages/"); |
| if (traceLoading) { |
| _log("Checking for $packageRoot directory."); |
| } |
| exists = await new Directory.fromUri(packageRoot).exists(); |
| if (traceLoading) { |
| _log("$packageRoot exists: $exists"); |
| } |
| if (exists) { |
| if (traceLoading) { |
| _log("Found a package root at: $packageRoot"); |
| } |
| sp.send([packageRoot.toString()]); |
| 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."); |
| } |
| } |
| |
| |
| _handlePackagesRequest(SendPort sp, |
| bool traceLoading, |
| int id, |
| Uri resource) async { |
| try { |
| if (id == -1) { |
| if (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) { |
| // If the loading of the .packages file failed for http/https based |
| // scripts then setup the package root. |
| var packageRoot = resource.resolve('packages/'); |
| sp.send([packageRoot.toString()]); |
| } |
| } else { |
| sp.send("Unsupported scheme used to locate .packages file: " |
| "'$resource'."); |
| } |
| } else if (id == -2) { |
| if (traceLoading) { |
| _log("Handling load of packages map: '$resource'."); |
| } |
| if (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 id: $id for '$resource'."); |
| } |
| } catch (e, s) { |
| if (traceLoading) { |
| _log("Error handling packages request: $e\n$s"); |
| } |
| sp.send("Uncaught error ($e) handling packages request."); |
| } |
| } |
| |
| |
| // External entry point for loader requests. |
| _processLoadRequest(request) { |
| SendPort sp = request[0]; |
| assert(sp != null); |
| bool traceLoading = request[1]; |
| assert(traceLoading != null); |
| int id = request[2]; |
| assert(id != null); |
| String resource = request[3]; |
| assert(resource != null); |
| var uri = Uri.parse(resource); |
| if (id >= 0) { |
| _handleResourceRequest(sp, traceLoading, id, uri); |
| } else { |
| _handlePackagesRequest(sp, traceLoading, id, uri); |
| } |
| } |