| 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 /** | 7 /** |
| 8 * A [VirtualDirectory] can serve files and directory-listing from a root path, | 8 * A [VirtualDirectory] can serve files and directory-listing from a root path, |
| 9 * to [HttpRequest]s. | 9 * to [HttpRequest]s. |
| 10 * | 10 * |
| 11 * The [VirtualDirectory] providing secure handling of request uris and | 11 * The [VirtualDirectory] providing secure handling of request uris and |
| 12 * file-system links, correct mime-types and custom error pages. | 12 * file-system links, correct mime-types and custom error pages. |
| 13 */ | 13 */ |
| 14 abstract class VirtualDirectory { | 14 class VirtualDirectory { |
| 15 final String root; | 15 final String root; |
| 16 | 16 |
| 17 /** | 17 /** |
| 18 * Set or get if the [VirtualDirectory] should list the content of | 18 * Set or get if the [VirtualDirectory] should list the content of |
| 19 * directories. | 19 * directories. |
| 20 */ | 20 */ |
| 21 bool allowDirectoryListing = false; | 21 bool allowDirectoryListing = false; |
| 22 | 22 |
| 23 /** | 23 /** |
| 24 * Set or get if the [VirtualDirectory] should follow links, that point | 24 * Set or get if the [VirtualDirectory] should follow links, that point |
| 25 * to other resources within the [root] directory. | 25 * to other resources within the [root] directory. |
| 26 */ | 26 */ |
| 27 bool followLinks = true; | 27 bool followLinks = true; |
| 28 | 28 |
| 29 /** |
| 30 * 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. |
| 32 */ |
| 33 bool jailRoot = true; |
| 34 |
| 35 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]"); |
| 36 |
| 37 Function _errorCallback; |
| 38 Function _dirCallback; |
| 39 |
| 29 /* | 40 /* |
| 30 * Create a new [VirtualDirectory] for serving static file content of | 41 * Create a new [VirtualDirectory] for serving static file content of |
| 31 * the path [root]. | 42 * the path [root]. |
| 32 * | 43 * |
| 33 * The [root] is not required to exist. If the [root] doesn't exist at time of | 44 * The [root] is not required to exist. If the [root] doesn't exist at time of |
| 34 * a request, a 404 is generated. | 45 * a request, a 404 is generated. |
| 35 */ | 46 */ |
| 36 factory VirtualDirectory(String root) => new _VirtualDirectory(root); | 47 VirtualDirectory(this.root); |
| 37 | 48 |
| 38 /** | 49 /** |
| 39 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. | 50 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. |
| 40 */ | 51 */ |
| 41 void serve(Stream<HttpRequest> requests); | 52 void serve(Stream<HttpRequest> requests) { |
| 53 requests.listen(serveRequest); |
| 54 } |
| 42 | 55 |
| 43 /** | 56 /** |
| 44 * Serve a single [HttpRequest], in this [VirtualDirectory]. | 57 * Serve a single [HttpRequest], in this [VirtualDirectory]. |
| 45 */ | 58 */ |
| 46 void serveRequest(HttpRequest request); | |
| 47 | |
| 48 /** | |
| 49 * Set the [callback] to override the default directory listing. The | |
| 50 * [callback] will be called with the [Directory] to be listed and the | |
| 51 * [HttpRequest]. | |
| 52 */ | |
| 53 void setDirectoryHandler(void callback(Directory dir, HttpRequest request)); | |
| 54 | |
| 55 /** | |
| 56 * Set the [callback] to override the error page handler. When [callback] is | |
| 57 * invoked, the `statusCode` property of the response is set. | |
| 58 */ | |
| 59 void setErrorPageHandler(void callback(HttpRequest request)); | |
| 60 } | |
| 61 | |
| 62 class _VirtualDirectory implements VirtualDirectory { | |
| 63 final String root; | |
| 64 | |
| 65 bool allowDirectoryListing = false; | |
| 66 bool followLinks = true; | |
| 67 | |
| 68 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]"); | |
| 69 | |
| 70 Function _errorCallback; | |
| 71 Function _dirCallback; | |
| 72 | |
| 73 _VirtualDirectory(this.root); | |
| 74 | |
| 75 void serve(Stream<HttpRequest> requests) { | |
| 76 requests.listen(serveRequest); | |
| 77 } | |
| 78 | |
| 79 void serveRequest(HttpRequest request) { | 59 void serveRequest(HttpRequest request) { |
| 80 _locateResource('.', request.uri.pathSegments.iterator..moveNext()) | 60 _locateResource('.', request.uri.pathSegments.iterator..moveNext()) |
| 81 .then((entity) { | 61 .then((entity) { |
| 82 if (entity == null) { | 62 if (entity == null) { |
| 83 _serveErrorPage(HttpStatus.NOT_FOUND, request); | 63 _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 84 return; | 64 return; |
| 85 } | 65 } |
| 86 if (entity is File) { | 66 if (entity is File) { |
| 87 _serveFile(entity, request); | 67 _serveFile(entity, request); |
| 88 } else if (entity is Directory) { | 68 } else if (entity is Directory) { |
| 89 _serveDirectory(entity, request); | 69 _serveDirectory(entity, request); |
| 90 } else { | 70 } else { |
| 91 _serveErrorPage(HttpStatus.NOT_FOUND, request); | 71 _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 92 } | 72 } |
| 93 }); | 73 }); |
| 94 } | 74 } |
| 95 | 75 |
| 96 void setDirectoryHandler(void callback(Directory dir, HttpRequest request)) { | 76 /** |
| 77 * Set the [callback] to override the default directory listing. The |
| 78 * [callback] will be called with the [Directory] to be listed and the |
| 79 * [HttpRequest]. |
| 80 */ |
| 81 void set directoryHandler(void callback(Directory dir, HttpRequest request)) { |
| 97 _dirCallback = callback; | 82 _dirCallback = callback; |
| 98 } | 83 } |
| 99 | 84 |
| 100 void setErrorPageHandler(void callback(HttpRequest request)) { | 85 /** |
| 86 * Set the [callback] to override the error page handler. When [callback] is |
| 87 * invoked, the `statusCode` property of the response is set. |
| 88 */ |
| 89 void set errorPageHandler(void callback(HttpRequest request)) { |
| 101 _errorCallback = callback; | 90 _errorCallback = callback; |
| 102 } | 91 } |
| 103 | 92 |
| 104 Future<FileSystemEntity> _locateResource(String path, | 93 Future<FileSystemEntity> _locateResource(String path, |
| 105 Iterator<String> segments) { | 94 Iterator<String> segments) { |
| 95 // Don't allow navigating up paths. |
| 96 if (segments.current == "..") return new Future.value(null); |
| 106 path = normalize(path); | 97 path = normalize(path); |
| 107 if (split(path).first == "..") return new Future.value(null); | 98 // If we jail to root, the relative path can never go up. |
| 99 if (jailRoot && split(path).first == "..") return new Future.value(null); |
| 108 String fullPath() => join(root, path); | 100 String fullPath() => join(root, path); |
| 109 return FileSystemEntity.type(fullPath(), followLinks: false) | 101 return FileSystemEntity.type(fullPath(), followLinks: false) |
| 110 .then((type) { | 102 .then((type) { |
| 111 switch (type) { | 103 switch (type) { |
| 112 case FileSystemEntityType.FILE: | 104 case FileSystemEntityType.FILE: |
| 113 if (segments.current == null) { | 105 if (segments.current == null) { |
| 114 return new File(fullPath()); | 106 return new File(fullPath()); |
| 115 } | 107 } |
| 116 break; | 108 break; |
| 117 | 109 |
| 118 case FileSystemEntityType.DIRECTORY: | 110 case FileSystemEntityType.DIRECTORY: |
| 119 if (segments.current == null) { | 111 if (segments.current == null) { |
| 120 if (allowDirectoryListing) { | 112 if (allowDirectoryListing) { |
| 121 return new Directory(fullPath()); | 113 return new Directory(fullPath()); |
| 122 } | 114 } |
| 123 } else { | 115 } else { |
| 124 if (_invalidPathRegExp.hasMatch(segments.current)) break; | 116 if (_invalidPathRegExp.hasMatch(segments.current)) break; |
| 125 return _locateResource(join(path, segments.current), | 117 return _locateResource(join(path, segments.current), |
| 126 segments..moveNext()); | 118 segments..moveNext()); |
| 127 } | 119 } |
| 128 break; | 120 break; |
| 129 | 121 |
| 130 case FileSystemEntityType.LINK: | 122 case FileSystemEntityType.LINK: |
| 131 if (followLinks) { | 123 if (followLinks) { |
| 132 return new Link(fullPath()).target() | 124 return new Link(fullPath()).target() |
| 133 .then((target) { | 125 .then((target) { |
| 134 String targetPath = normalize(target); | 126 String targetPath = normalize(target); |
| 135 if (isAbsolute(targetPath)) return null; | 127 if (isAbsolute(targetPath)) { |
| 136 targetPath = join(dirname(path), targetPath); | 128 // If we jail to root, the path can never be absolute. |
| 137 return _locateResource(targetPath, segments); | 129 if (jailRoot) return null; |
| 130 return _locateResource(targetPath, segments); |
| 131 } else { |
| 132 targetPath = join(dirname(path), targetPath); |
| 133 return _locateResource(targetPath, segments); |
| 134 } |
| 138 }); | 135 }); |
| 139 } | 136 } |
| 140 break; | 137 break; |
| 141 } | 138 } |
| 142 // Return `null` on fall-through, to indicate NOT_FOUND. | 139 // Return `null` on fall-through, to indicate NOT_FOUND. |
| 143 return null; | 140 return null; |
| 144 }); | 141 }); |
| 145 } | 142 } |
| 146 | 143 |
| 147 void _serveFile(File file, HttpRequest request) { | 144 void _serveFile(File file, HttpRequest request) { |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 366 | 363 |
| 367 Future close() => new Future.value(); | 364 Future close() => new Future.value(); |
| 368 | 365 |
| 369 void setMimeType(var bytes) { | 366 void setMimeType(var bytes) { |
| 370 var mimeType = lookupMimeType(path, headerBytes: bytes); | 367 var mimeType = lookupMimeType(path, headerBytes: bytes); |
| 371 if (mimeType != null) { | 368 if (mimeType != null) { |
| 372 response.headers.contentType = ContentType.parse(mimeType); | 369 response.headers.contentType = ContentType.parse(mimeType); |
| 373 } | 370 } |
| 374 } | 371 } |
| 375 } | 372 } |
| OLD | NEW |