| 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);
|
| }
|
| }
|
|
|