Index: runtime/bin/vmservice/loader.dart |
diff --git a/runtime/bin/vmservice/loader.dart b/runtime/bin/vmservice/loader.dart |
index a95df089b91bb5319f2b9687e8cf8cf951d68653..83e57e439726ee4782b8556291527e87ecd11b16 100644 |
--- a/runtime/bin/vmservice/loader.dart |
+++ b/runtime/bin/vmservice/loader.dart |
@@ -4,10 +4,14 @@ |
part of vmservice_io; |
+_log(msg) { |
+ print("% $msg"); |
+} |
+ |
var _httpClient; |
// Send a response to the requesting isolate. |
-void _sendResponse(SendPort sp, int id, dynamic data) { |
+void _sendResourceResponse(SendPort sp, int id, dynamic data) { |
assert((data is List<int>) || (data is String)); |
var msg = new List(2); |
msg[0] = id; |
@@ -29,17 +33,17 @@ void _loadHttp(SendPort sp, int id, Uri uri) { |
if (response.statusCode != 200) { |
var msg = "Failure getting $uri:\n" |
" ${response.statusCode} ${response.reasonPhrase}"; |
- _sendResponse(sp, id, msg); |
+ _sendResourceResponse(sp, id, msg); |
} else { |
- _sendResponse(sp, id, builder.takeBytes()); |
+ _sendResourceResponse(sp, id, builder.takeBytes()); |
} |
}, |
onError: (e) { |
- _sendResponse(sp, d, e.toString()); |
+ _sendResourceResponse(sp, id, e.toString()); |
}); |
}) |
.catchError((e) { |
- _sendResponse(sp, id, e.toString()); |
+ _sendResourceResponse(sp, id, e.toString()); |
}); |
// It's just here to push an event on the event loop so that we invoke the |
// scheduled microtasks. |
@@ -50,11 +54,11 @@ void _loadFile(SendPort sp, int id, Uri uri) { |
var path = uri.toFilePath(); |
var sourceFile = new File(path); |
sourceFile.readAsBytes().then((data) { |
- _sendResponse(sp, id, data); |
+ _sendResourceResponse(sp, id, data); |
}, |
onError: (e) { |
var err = "Error loading $uri:\n $e"; |
- _sendResponse(sp, id, err); |
+ _sendResourceResponse(sp, id, err); |
}); |
} |
@@ -83,24 +87,301 @@ void _loadDataUri(SendPort sp, int id, Uri uri) { |
} |
var data = UTF8.encode(Uri.decodeComponent(encodedData)); |
- _sendResponse(sp, id, data); |
+ _sendResourceResponse(sp, id, data); |
} catch (e) { |
- _sendResponse(sp, id, "Invalid data uri ($uri):\n $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) { |
+ var result = []; |
+ 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 (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]; |
+ } |
+ } else if ((char == _CR) || (char == _LF)) { |
+ // Identify end of line. |
+ 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 packags 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."); |
+ } |
+} |
+ |
+_handlePackagesRequest(SendPort sp, |
+ bool traceLoading, |
+ int id, |
+ Uri resource) async { |
+ if (id == -1) { |
+ if (resource.scheme == 'file') { |
+ _findPackagesFile(sp, traceLoading, resource); |
+ } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
+ // TODO(iposva): Check for the existence of a .packages file when loading |
+ // from http or https. |
+ var packageRoot = resource.resolve('packages/'); |
+ sp.send([packageRoot.toString()]); |
+ } else { |
+ sp.send("Unsupported base URI to identify .packages file: '$resource'."); |
+ } |
+ } else if (id == -2) { |
+ if (traceLoading) { |
+ _log("Handling load of packages map: '$resource'."); |
+ } |
+ var exists = await new File.fromUri(resource).exists(); |
+ if (exists) { |
+ _loadPackagesFile(sp, traceLoading, resource); |
+ } else { |
+ sp.send("Packages file $resource not found."); |
+ } |
+ } else { |
+ sp.send("Unknown packages request id: $id for '$resource'."); |
+ } |
+} |
+ |
+ |
+// External entry point for loader requests. |
_processLoadRequest(request) { |
SendPort sp = request[0]; |
- int id = request[1]; |
- String resource = request[2]; |
- var uri = Uri.parse(request[2]); |
- if (uri.scheme == 'file') { |
- _loadFile(sp, id, uri); |
- } else if ((uri.scheme == 'http') || (uri.scheme == 'https')) { |
- _loadHttp(sp, id, uri); |
- } else if ((uri.scheme == 'data')) { |
- _loadDataUri(sp, id, uri); |
+ 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 { |
- sp.send([id, 'Unknown scheme (${uri.scheme}) for $uri']); |
+ _handlePackagesRequest(sp, traceLoading, id, uri); |
} |
} |