blob: 9b7355e0b7ccad18fadf2d3fe8961877bdf15d54 [file] [log] [blame]
// 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);
}
}