OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of vmservice_io; | 5 part of vmservice_io; |
6 | 6 |
| 7 _log(msg) { |
| 8 print("% $msg"); |
| 9 } |
| 10 |
7 var _httpClient; | 11 var _httpClient; |
8 | 12 |
9 // Send a response to the requesting isolate. | 13 // Send a response to the requesting isolate. |
10 void _sendResponse(SendPort sp, int id, dynamic data) { | 14 void _sendResourceResponse(SendPort sp, int id, dynamic data) { |
11 assert((data is List<int>) || (data is String)); | 15 assert((data is List<int>) || (data is String)); |
12 var msg = new List(2); | 16 var msg = new List(2); |
13 msg[0] = id; | 17 msg[0] = id; |
14 msg[1] = data; | 18 msg[1] = data; |
15 sp.send(msg); | 19 sp.send(msg); |
16 } | 20 } |
17 | 21 |
18 void _loadHttp(SendPort sp, int id, Uri uri) { | 22 void _loadHttp(SendPort sp, int id, Uri uri) { |
19 if (_httpClient == null) { | 23 if (_httpClient == null) { |
20 _httpClient = new HttpClient()..maxConnectionsPerHost = 6; | 24 _httpClient = new HttpClient()..maxConnectionsPerHost = 6; |
21 } | 25 } |
22 _httpClient.getUrl(uri) | 26 _httpClient.getUrl(uri) |
23 .then((HttpClientRequest request) => request.close()) | 27 .then((HttpClientRequest request) => request.close()) |
24 .then((HttpClientResponse response) { | 28 .then((HttpClientResponse response) { |
25 var builder = new BytesBuilder(copy: false); | 29 var builder = new BytesBuilder(copy: false); |
26 response.listen( | 30 response.listen( |
27 builder.add, | 31 builder.add, |
28 onDone: () { | 32 onDone: () { |
29 if (response.statusCode != 200) { | 33 if (response.statusCode != 200) { |
30 var msg = "Failure getting $uri:\n" | 34 var msg = "Failure getting $uri:\n" |
31 " ${response.statusCode} ${response.reasonPhrase}"; | 35 " ${response.statusCode} ${response.reasonPhrase}"; |
32 _sendResponse(sp, id, msg); | 36 _sendResourceResponse(sp, id, msg); |
33 } else { | 37 } else { |
34 _sendResponse(sp, id, builder.takeBytes()); | 38 _sendResourceResponse(sp, id, builder.takeBytes()); |
35 } | 39 } |
36 }, | 40 }, |
37 onError: (e) { | 41 onError: (e) { |
38 _sendResponse(sp, d, e.toString()); | 42 _sendResourceResponse(sp, id, e.toString()); |
39 }); | 43 }); |
40 }) | 44 }) |
41 .catchError((e) { | 45 .catchError((e) { |
42 _sendResponse(sp, id, e.toString()); | 46 _sendResourceResponse(sp, id, e.toString()); |
43 }); | 47 }); |
44 // It's just here to push an event on the event loop so that we invoke the | 48 // It's just here to push an event on the event loop so that we invoke the |
45 // scheduled microtasks. | 49 // scheduled microtasks. |
46 Timer.run(() {}); | 50 Timer.run(() {}); |
47 } | 51 } |
48 | 52 |
49 void _loadFile(SendPort sp, int id, Uri uri) { | 53 void _loadFile(SendPort sp, int id, Uri uri) { |
50 var path = uri.toFilePath(); | 54 var path = uri.toFilePath(); |
51 var sourceFile = new File(path); | 55 var sourceFile = new File(path); |
52 sourceFile.readAsBytes().then((data) { | 56 sourceFile.readAsBytes().then((data) { |
53 _sendResponse(sp, id, data); | 57 _sendResourceResponse(sp, id, data); |
54 }, | 58 }, |
55 onError: (e) { | 59 onError: (e) { |
56 var err = "Error loading $uri:\n $e"; | 60 var err = "Error loading $uri:\n $e"; |
57 _sendResponse(sp, id, err); | 61 _sendResourceResponse(sp, id, err); |
58 }); | 62 }); |
59 } | 63 } |
60 | 64 |
61 var dataUriRegex = new RegExp( | 65 var dataUriRegex = new RegExp( |
62 r"data:([\w-]+/[\w-]+)?(;charset=([\w-]+))?(;base64)?,(.*)"); | 66 r"data:([\w-]+/[\w-]+)?(;charset=([\w-]+))?(;base64)?,(.*)"); |
63 | 67 |
64 void _loadDataUri(SendPort sp, int id, Uri uri) { | 68 void _loadDataUri(SendPort sp, int id, Uri uri) { |
65 try { | 69 try { |
66 var match = dataUriRegex.firstMatch(uri.toString()); | 70 var match = dataUriRegex.firstMatch(uri.toString()); |
67 if (match == null) throw "Malformed data uri"; | 71 if (match == null) throw "Malformed data uri"; |
68 | 72 |
69 var mimeType = match.group(1); | 73 var mimeType = match.group(1); |
70 var encoding = match.group(3); | 74 var encoding = match.group(3); |
71 var maybeBase64 = match.group(4); | 75 var maybeBase64 = match.group(4); |
72 var encodedData = match.group(5); | 76 var encodedData = match.group(5); |
73 | 77 |
74 if (mimeType != "application/dart") { | 78 if (mimeType != "application/dart") { |
75 throw "MIME-type must be application/dart"; | 79 throw "MIME-type must be application/dart"; |
76 } | 80 } |
77 if (encoding != "utf-8") { | 81 if (encoding != "utf-8") { |
78 // Default is ASCII. The C++ portion of the embedder assumes UTF-8. | 82 // Default is ASCII. The C++ portion of the embedder assumes UTF-8. |
79 throw "Only utf-8 encoding is supported"; | 83 throw "Only utf-8 encoding is supported"; |
80 } | 84 } |
81 if (maybeBase64 != null) { | 85 if (maybeBase64 != null) { |
82 throw "Only percent encoding is supported"; | 86 throw "Only percent encoding is supported"; |
83 } | 87 } |
84 | 88 |
85 var data = UTF8.encode(Uri.decodeComponent(encodedData)); | 89 var data = UTF8.encode(Uri.decodeComponent(encodedData)); |
86 _sendResponse(sp, id, data); | 90 _sendResourceResponse(sp, id, data); |
87 } catch (e) { | 91 } catch (e) { |
88 _sendResponse(sp, id, "Invalid data uri ($uri):\n $e"); | 92 _sendResourceResponse(sp, id, "Invalid data uri ($uri):\n $e"); |
89 } | 93 } |
90 } | 94 } |
91 | 95 |
| 96 _handleResourceRequest(SendPort sp, bool traceLoading, int id, Uri resource) { |
| 97 if (resource.scheme == 'file') { |
| 98 _loadFile(sp, id, resource); |
| 99 } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
| 100 _loadHttp(sp, id, resource); |
| 101 } else if ((resource.scheme == 'data')) { |
| 102 _loadDataUri(sp, id, resource); |
| 103 } else { |
| 104 _sendResourceResponse(sp, id, |
| 105 'Unknown scheme (${resource.scheme}) for $resource'); |
| 106 } |
| 107 } |
| 108 |
| 109 |
| 110 // Handling of packages requests. Finding and parsing of .packages file or |
| 111 // packages/ directories. |
| 112 const _LF = 0x0A; |
| 113 const _CR = 0x0D; |
| 114 const _SPACE = 0x20; |
| 115 const _HASH = 0x23; |
| 116 const _DOT = 0x2E; |
| 117 const _COLON = 0x3A; |
| 118 const _DEL = 0x7F; |
| 119 |
| 120 const _invalidPackageNameChars = const [ |
| 121 // space ! " # $ % & ' |
| 122 true , false, true , true , false, true , false, false, |
| 123 // ( ) * + , - . / |
| 124 false, false, false, false, false, false, false, true , |
| 125 // 0 1 2 3 4 5 6 7 |
| 126 false, false, false, false, false, false, false, false, |
| 127 // 8 9 : ; < = > ? |
| 128 false, false, true , false, true , false, true , true , |
| 129 // @ A B C D E F G |
| 130 false, false, false, false, false, false, false, false, |
| 131 // H I J K L M N O |
| 132 false, false, false, false, false, false, false, false, |
| 133 // P Q R S T U V W |
| 134 false, false, false, false, false, false, false, false, |
| 135 // X Y Z [ \ ] ^ _ |
| 136 false, false, false, true , true , true , true , false, |
| 137 // ` a b c d e f g |
| 138 true , false, false, false, false, false, false, false, |
| 139 // h i j k l m n o |
| 140 false, false, false, false, false, false, false, false, |
| 141 // p q r s t u v w |
| 142 false, false, false, false, false, false, false, false, |
| 143 // x y z { | } ~ DEL |
| 144 false, false, false, true , true , true , false, true |
| 145 ]; |
| 146 |
| 147 _parsePackagesFile(SendPort sp, |
| 148 bool traceLoading, |
| 149 Uri packagesFile, |
| 150 List<int> data) { |
| 151 var result = []; |
| 152 var index = 0; |
| 153 var len = data.length; |
| 154 while (index < len) { |
| 155 var start = index; |
| 156 var char = data[index]; |
| 157 if ((char == _CR) || (char == _LF)) { |
| 158 // Skipping empty lines. |
| 159 index++; |
| 160 continue; |
| 161 } |
| 162 |
| 163 // Identify split within the line and end of the line. |
| 164 var separator = -1; |
| 165 var end = len; |
| 166 // Verifying validity of package name while scanning the line. |
| 167 var nonDot = false; |
| 168 var invalidPackageName = false; |
| 169 |
| 170 // Scan to the end of the line or data. |
| 171 while (index < len) { |
| 172 char = data[index++]; |
| 173 if (separator == -1) { |
| 174 if ((char == _COLON)) { |
| 175 // The first colon on a line is the separator between package name and |
| 176 // related URI. |
| 177 separator = index - 1; |
| 178 } else { |
| 179 // Still scanning the package name part. Check for the validity of |
| 180 // the characters. |
| 181 nonDot = nonDot || (char != _DOT); |
| 182 invalidPackageName = invalidPackageName || |
| 183 (char < _SPACE) || (char > _DEL) || |
| 184 _invalidPackageNameChars[char - _SPACE]; |
| 185 } |
| 186 } else if ((char == _CR) || (char == _LF)) { |
| 187 // Identify end of line. |
| 188 end = index - 1; |
| 189 break; |
| 190 } |
| 191 } |
| 192 |
| 193 // No further handling needed for comment lines. |
| 194 if (data[start] == _HASH) { |
| 195 if (traceLoading) { |
| 196 _log("Skipping comment in $packagesFile:\n" |
| 197 "${new String.fromCharCodes(data, start, end)}"); |
| 198 } |
| 199 continue; |
| 200 } |
| 201 |
| 202 // Check for a badly formatted line, starting with a ':'. |
| 203 if (separator == start) { |
| 204 var line = new String.fromCharCodes(data, start, end); |
| 205 if (traceLoading) { |
| 206 _log("Line starts with ':' in $packagesFile:\n" |
| 207 "$line"); |
| 208 } |
| 209 sp.send("Missing package name in $packagesFile:\n" |
| 210 "$line"); |
| 211 return; |
| 212 } |
| 213 |
| 214 // Ensure there is a separator on the line. |
| 215 if (separator == -1) { |
| 216 var line = new String.fromCharCodes(data, start, end); |
| 217 if (traceLoading) { |
| 218 _log("Line has no ':' in $packagesFile:\n" |
| 219 "$line"); |
| 220 } |
| 221 sp.send("Missing ':' separator in $packagesFile:\n" |
| 222 "$line"); |
| 223 return; |
| 224 } |
| 225 |
| 226 var packageName = new String.fromCharCodes(data, start, separator); |
| 227 |
| 228 // Check for valid package name. |
| 229 if (invalidPackageName || !nonDot) { |
| 230 var line = new String.fromCharCodes(data, start, end); |
| 231 if (traceLoading) { |
| 232 _log("Invalid package name $packageName in $packagesFile"); |
| 233 } |
| 234 sp.send("Invalid package name '$packageName' in $packagesFile:\n" |
| 235 "$line"); |
| 236 return; |
| 237 } |
| 238 |
| 239 if (traceLoading) { |
| 240 _log("packageName: $packageName"); |
| 241 } |
| 242 var packageUri = new String.fromCharCodes(data, separator + 1, end); |
| 243 if (traceLoading) { |
| 244 _log("original packageUri: $packageUri"); |
| 245 } |
| 246 // Ensure the package uri ends with a /. |
| 247 if (!packageUri.endsWith("/")) { |
| 248 packageUri = "$packageUri/"; |
| 249 } |
| 250 packageUri = packagesFile.resolve(packageUri).toString(); |
| 251 if (traceLoading) { |
| 252 _log("mapping: $packageName -> $packageUri"); |
| 253 } |
| 254 result.add(packageName); |
| 255 result.add(packageUri); |
| 256 } |
| 257 |
| 258 if (traceLoading) { |
| 259 _log("Parsed packages file at $packagesFile. Sending:\n$result"); |
| 260 } |
| 261 sp.send(result); |
| 262 } |
| 263 |
| 264 _loadPackagesFile(SendPort sp, bool traceLoading, Uri packagesFile) async { |
| 265 try { |
| 266 var data = await new File.fromUri(packagesFile).readAsBytes(); |
| 267 if (traceLoading) { |
| 268 _log("Loaded packages file from $packagesFile:\n" |
| 269 "${new String.fromCharCodes(data)}"); |
| 270 } |
| 271 _parsePackagesFile(sp, traceLoading, packagesFile, data); |
| 272 } catch (e, s) { |
| 273 if (traceLoading) { |
| 274 _log("Error loading packages: $e\n$s"); |
| 275 } |
| 276 sp.send("Uncaught error ($e) loading packags file."); |
| 277 } |
| 278 } |
| 279 |
| 280 _findPackagesFile(SendPort sp, bool traceLoading, Uri base) async { |
| 281 try { |
| 282 // Walk up the directory hierarchy to check for the existence of |
| 283 // .packages files in parent directories and for the existense of a |
| 284 // packages/ directory on the first iteration. |
| 285 var dir = new File.fromUri(base).parent; |
| 286 var prev = null; |
| 287 // Keep searching until we reach the root. |
| 288 while ((prev == null) || (prev.path != dir.path)) { |
| 289 // Check for the existence of a .packages file and if it exists try to |
| 290 // load and parse it. |
| 291 var dirUri = dir.uri; |
| 292 var packagesFile = dirUri.resolve(".packages"); |
| 293 if (traceLoading) { |
| 294 _log("Checking for $packagesFile file."); |
| 295 } |
| 296 var exists = await new File.fromUri(packagesFile).exists(); |
| 297 if (traceLoading) { |
| 298 _log("$packagesFile exists: $exists"); |
| 299 } |
| 300 if (exists) { |
| 301 _loadPackagesFile(sp, traceLoading, packagesFile); |
| 302 return; |
| 303 } |
| 304 // On the first loop try whether there is a packages/ directory instead. |
| 305 if (prev == null) { |
| 306 var packageRoot = dirUri.resolve("packages/"); |
| 307 if (traceLoading) { |
| 308 _log("Checking for $packageRoot directory."); |
| 309 } |
| 310 exists = await new Directory.fromUri(packageRoot).exists(); |
| 311 if (traceLoading) { |
| 312 _log("$packageRoot exists: $exists"); |
| 313 } |
| 314 if (exists) { |
| 315 if (traceLoading) { |
| 316 _log("Found a package root at: $packageRoot"); |
| 317 } |
| 318 sp.send([packageRoot.toString()]); |
| 319 return; |
| 320 } |
| 321 } |
| 322 // Move up one level. |
| 323 prev = dir; |
| 324 dir = dir.parent; |
| 325 } |
| 326 |
| 327 // No .packages file was found. |
| 328 if (traceLoading) { |
| 329 _log("Could not resolve a package location from $base"); |
| 330 } |
| 331 sp.send("Could not resolve a package location for base at $base"); |
| 332 } catch (e, s) { |
| 333 if (traceLoading) { |
| 334 _log("Error loading packages: $e\n$s"); |
| 335 } |
| 336 sp.send("Uncaught error ($e) loading packages file."); |
| 337 } |
| 338 } |
| 339 |
| 340 _handlePackagesRequest(SendPort sp, |
| 341 bool traceLoading, |
| 342 int id, |
| 343 Uri resource) async { |
| 344 if (id == -1) { |
| 345 if (resource.scheme == 'file') { |
| 346 _findPackagesFile(sp, traceLoading, resource); |
| 347 } else if ((resource.scheme == 'http') || (resource.scheme == 'https')) { |
| 348 // TODO(iposva): Check for the existence of a .packages file when loading |
| 349 // from http or https. |
| 350 var packageRoot = resource.resolve('packages/'); |
| 351 sp.send([packageRoot.toString()]); |
| 352 } else { |
| 353 sp.send("Unsupported base URI to identify .packages file: '$resource'."); |
| 354 } |
| 355 } else if (id == -2) { |
| 356 if (traceLoading) { |
| 357 _log("Handling load of packages map: '$resource'."); |
| 358 } |
| 359 var exists = await new File.fromUri(resource).exists(); |
| 360 if (exists) { |
| 361 _loadPackagesFile(sp, traceLoading, resource); |
| 362 } else { |
| 363 sp.send("Packages file $resource not found."); |
| 364 } |
| 365 } else { |
| 366 sp.send("Unknown packages request id: $id for '$resource'."); |
| 367 } |
| 368 } |
| 369 |
| 370 |
| 371 // External entry point for loader requests. |
92 _processLoadRequest(request) { | 372 _processLoadRequest(request) { |
93 SendPort sp = request[0]; | 373 SendPort sp = request[0]; |
94 int id = request[1]; | 374 assert(sp != null); |
95 String resource = request[2]; | 375 bool traceLoading = request[1]; |
96 var uri = Uri.parse(request[2]); | 376 assert(traceLoading != null); |
97 if (uri.scheme == 'file') { | 377 int id = request[2]; |
98 _loadFile(sp, id, uri); | 378 assert(id != null); |
99 } else if ((uri.scheme == 'http') || (uri.scheme == 'https')) { | 379 String resource = request[3]; |
100 _loadHttp(sp, id, uri); | 380 assert(resource != null); |
101 } else if ((uri.scheme == 'data')) { | 381 var uri = Uri.parse(resource); |
102 _loadDataUri(sp, id, uri); | 382 if (id >= 0) { |
| 383 _handleResourceRequest(sp, traceLoading, id, uri); |
103 } else { | 384 } else { |
104 sp.send([id, 'Unknown scheme (${uri.scheme}) for $uri']); | 385 _handlePackagesRequest(sp, traceLoading, id, uri); |
105 } | 386 } |
106 } | 387 } |
OLD | NEW |