Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 http_server; | 5 part of http_server; |
| 6 | 6 |
| 7 typedef dynamic _DirCallback(Directory dir, HttpRequest request); | |
| 8 typedef dynamic _ErrorCallback(HttpRequest request); | |
| 9 | |
| 7 /** | 10 /** |
| 8 * A [VirtualDirectory] can serve files and directory-listing from a root path, | 11 * A [VirtualDirectory] can serve files and directory-listing from a root path, |
| 9 * to [HttpRequest]s. | 12 * to [HttpRequest]s. |
| 10 * | 13 * |
| 11 * The [VirtualDirectory] providing secure handling of request uris and | 14 * The [VirtualDirectory] providing secure handling of request uris and |
| 12 * file-system links, correct mime-types and custom error pages. | 15 * file-system links, correct mime-types and custom error pages. |
| 13 */ | 16 */ |
| 14 class VirtualDirectory { | 17 class VirtualDirectory { |
| 15 final String root; | 18 final String root; |
| 16 | 19 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 27 bool followLinks = true; | 30 bool followLinks = true; |
| 28 | 31 |
| 29 /** | 32 /** |
| 30 * Set or get if the [VirtualDirectory] should jail the root. When the root is | 33 * Set or get if the [VirtualDirectory] should jail the root. When the root is |
| 31 * not jailed, links can be followed to outside the [root] directory. | 34 * not jailed, links can be followed to outside the [root] directory. |
| 32 */ | 35 */ |
| 33 bool jailRoot = true; | 36 bool jailRoot = true; |
| 34 | 37 |
| 35 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]"); | 38 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]"); |
| 36 | 39 |
| 37 Function _errorCallback; | 40 _ErrorCallback _errorCallback; |
| 38 Function _dirCallback; | 41 _DirCallback _dirCallback; |
| 39 | 42 |
| 40 /* | 43 /* |
| 41 * Create a new [VirtualDirectory] for serving static file content of | 44 * Create a new [VirtualDirectory] for serving static file content of |
| 42 * the path [root]. | 45 * the path [root]. |
| 43 * | 46 * |
| 44 * The [root] is not required to exist. If the [root] doesn't exist at time of | 47 * The [root] is not required to exist. If the [root] doesn't exist at time of |
| 45 * a request, a 404 is generated. | 48 * a request, a 404 is generated. |
| 46 */ | 49 */ |
| 47 VirtualDirectory(this.root); | 50 VirtualDirectory(this.root); |
| 48 | 51 |
| 49 /** | 52 /** |
| 50 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. | 53 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. |
| 51 */ | 54 */ |
| 52 void serve(Stream<HttpRequest> requests) { | 55 StreamSubscription<HttpRequest> serve(Stream<HttpRequest> requests) => |
| 53 requests.listen(serveRequest); | 56 requests.listen(serveRequest); |
| 54 } | |
| 55 | 57 |
| 56 /** | 58 /** |
| 57 * Serve a single [HttpRequest], in this [VirtualDirectory]. | 59 * Serve a single [HttpRequest], in this [VirtualDirectory]. |
| 58 */ | 60 */ |
| 59 void serveRequest(HttpRequest request) { | 61 Future serveRequest(HttpRequest request) { |
| 60 _locateResource('.', request.uri.pathSegments.iterator..moveNext()) | 62 return _locateResource('.', request.uri.pathSegments.iterator..moveNext()) |
| 61 .then((entity) { | 63 .then((FileSystemEntity entity) { |
| 62 if (entity == null) { | |
| 63 _serveErrorPage(HttpStatus.NOT_FOUND, request); | |
| 64 return; | |
| 65 } | |
| 66 if (entity is File) { | 64 if (entity is File) { |
| 67 serveFile(entity, request); | 65 serveFile(entity, request); |
| 68 } else if (entity is Directory) { | 66 } else if (entity is Directory) { |
| 69 _serveDirectory(entity, request); | 67 _serveDirectory(entity, request); |
| 70 } else { | 68 } else { |
| 71 _serveErrorPage(HttpStatus.NOT_FOUND, request); | 69 _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 72 } | 70 } |
| 71 return request.response.done; | |
| 73 }); | 72 }); |
| 74 } | 73 } |
| 75 | 74 |
| 76 /** | 75 /** |
| 77 * Set the [callback] to override the default directory listing. The | 76 * Set the [callback] to override the default directory listing. The |
| 78 * [callback] will be called with the [Directory] to be listed and the | 77 * [callback] will be called with the [Directory] to be listed and the |
| 79 * [HttpRequest]. | 78 * [HttpRequest]. |
| 80 */ | 79 */ |
| 81 void set directoryHandler(void callback(Directory dir, HttpRequest request)) { | 80 void set directoryHandler(void callback(Directory dir, HttpRequest request)) { |
| 82 _dirCallback = callback; | 81 _dirCallback = callback; |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 151 * This is usefull when e.g. overriding [directoryHandler] to redirect to | 150 * This is usefull when e.g. overriding [directoryHandler] to redirect to |
| 152 * some index file. | 151 * some index file. |
| 153 * | 152 * |
| 154 * In the request contains the [HttpStatus.IF_MODIFIED_SINCE] header, | 153 * In the request contains the [HttpStatus.IF_MODIFIED_SINCE] header, |
| 155 * [serveFile] will send a [HttpStatus.NOT_MODIFIED] response if the file | 154 * [serveFile] will send a [HttpStatus.NOT_MODIFIED] response if the file |
| 156 * was not changed. | 155 * was not changed. |
| 157 * | 156 * |
| 158 * Note that if it was unabled to read from [file], the [request]s response | 157 * Note that if it was unabled to read from [file], the [request]s response |
| 159 * is closed with error-code [HttpStatus.NOT_FOUND]. | 158 * is closed with error-code [HttpStatus.NOT_FOUND]. |
| 160 */ | 159 */ |
| 161 void serveFile(File file, HttpRequest request) { | 160 void serveFile(File file, HttpRequest request) { |
|
sethladd
2014/01/09 05:27:48
why not return the future from here?
Anders Johnsen
2014/01/09 06:12:23
response.done is the right way of doing it. Also,
| |
| 162 var response = request.response; | 161 var response = request.response; |
| 163 // TODO(ajohnsen): Set up Zone support for these errors. | 162 // TODO(ajohnsen): Set up Zone support for these errors. |
| 164 file.lastModified().then((lastModified) { | 163 file.lastModified().then((lastModified) { |
| 165 if (request.headers.ifModifiedSince != null && | 164 if (request.headers.ifModifiedSince != null && |
| 166 !lastModified.isAfter(request.headers.ifModifiedSince)) { | 165 !lastModified.isAfter(request.headers.ifModifiedSince)) { |
| 167 response.statusCode = HttpStatus.NOT_MODIFIED; | 166 response.statusCode = HttpStatus.NOT_MODIFIED; |
| 168 response.close(); | 167 response.close(); |
| 169 return; | 168 return null; |
| 170 } | 169 } |
| 171 | 170 |
| 172 response.headers.set(HttpHeaders.LAST_MODIFIED, lastModified); | 171 response.headers.set(HttpHeaders.LAST_MODIFIED, lastModified); |
| 173 response.headers.set(HttpHeaders.ACCEPT_RANGES, "bytes"); | 172 response.headers.set(HttpHeaders.ACCEPT_RANGES, "bytes"); |
| 174 | 173 |
| 175 if (request.method == 'HEAD') { | 174 if (request.method == 'HEAD') { |
| 176 response.close(); | 175 response.close(); |
| 177 return; | 176 return null; |
| 178 } | 177 } |
| 179 | 178 |
| 180 return file.length().then((length) { | 179 return file.length().then((length) { |
| 181 String range = request.headers.value("range"); | 180 String range = request.headers.value("range"); |
| 182 if (range != null) { | 181 if (range != null) { |
| 183 // We only support one range, where the standard support several. | 182 // We only support one range, where the standard support several. |
| 184 Match matches = new RegExp(r"^bytes=(\d*)\-(\d*)$").firstMatch(range); | 183 Match matches = new RegExp(r"^bytes=(\d*)\-(\d*)$").firstMatch(range); |
| 185 // If the range header have the right format, handle it. | 184 // If the range header have the right format, handle it. |
| 186 if (matches != null) { | 185 if (matches != null) { |
| 187 // Serve sub-range. | 186 // Serve sub-range. |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 276 <td>$modified</td> | 275 <td>$modified</td> |
| 277 <td style="text-align: right">$size</td> | 276 <td style="text-align: right">$size</td> |
| 278 </tr>'''; | 277 </tr>'''; |
| 279 response.write(entry); | 278 response.write(entry); |
| 280 } | 279 } |
| 281 | 280 |
| 282 if (path != '/') { | 281 if (path != '/') { |
| 283 add('../', null, null); | 282 add('../', null, null); |
| 284 } | 283 } |
| 285 | 284 |
| 286 dir.list(followLinks: true).listen((entity) { | 285 dir.list(followLinks: true).forEach((entity) { |
|
Anders Johnsen
2014/01/09 06:12:23
This is not handling errors (they can happen if FS
kevmoo
2014/01/10 18:12:31
I think I've addressed this, with TODOs for loggin
| |
| 287 // TODO(ajohnsen): Consider async dir listing. | |
| 288 if (entity is File) { | 286 if (entity is File) { |
| 289 var stat = entity.statSync(); | 287 var stat = entity.statSync(); |
| 290 add(basename(entity.path), | 288 add(basename(entity.path), |
| 291 stat.modified.toString(), | 289 stat.modified.toString(), |
| 292 stat.size); | 290 stat.size); |
| 293 } else if (entity is Directory) { | 291 } else if (entity is Directory) { |
| 294 add(basename(entity.path) + '/', | 292 add(basename(entity.path) + '/', |
| 295 entity.statSync().modified.toString(), | 293 entity.statSync().modified.toString(), |
| 296 null); | 294 null); |
| 297 } | 295 } |
| 298 }, onError: (e) { | 296 }) |
| 299 }, onDone: () { | 297 .then((_) { |
| 300 response.write(footer); | 298 response.write(footer); |
| 301 response.close(); | 299 }) |
| 300 .whenComplete(() { | |
|
sethladd
2014/01/09 05:27:48
can this be a one-line function?
kevmoo
2014/01/10 05:02:00
Done.
| |
| 301 return response.close(); | |
|
Anders Johnsen
2014/01/09 06:12:23
Why return here? This value is not kept.
kevmoo
2014/01/10 05:02:00
Done.
| |
| 302 }); | 302 }); |
| 303 }, onError: (e) => response.close()); | 303 }); |
| 304 } | 304 } |
| 305 | 305 |
| 306 void _serveErrorPage(int error, HttpRequest request) { | 306 void _serveErrorPage(int error, HttpRequest request) { |
| 307 var response = request.response; | 307 var response = request.response; |
| 308 response.statusCode = error; | 308 response.statusCode = error; |
| 309 if (_errorCallback != null) { | 309 if (_errorCallback != null) { |
| 310 _errorCallback(request); | 310 _errorCallback(request); |
| 311 return; | 311 return; |
| 312 } | 312 } |
| 313 // Default error page. | 313 // Default error page. |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 381 | 381 |
| 382 Future close() => new Future.value(); | 382 Future close() => new Future.value(); |
| 383 | 383 |
| 384 void setMimeType(var bytes) { | 384 void setMimeType(var bytes) { |
| 385 var mimeType = lookupMimeType(path, headerBytes: bytes); | 385 var mimeType = lookupMimeType(path, headerBytes: bytes); |
| 386 if (mimeType != null) { | 386 if (mimeType != null) { |
| 387 response.headers.contentType = ContentType.parse(mimeType); | 387 response.headers.contentType = ContentType.parse(mimeType); |
| 388 } | 388 } |
| 389 } | 389 } |
| 390 } | 390 } |
| OLD | NEW |