Index: mojo/public/dart/third_party/shelf_static/lib/src/static_handler.dart |
diff --git a/mojo/public/dart/third_party/shelf_static/lib/src/static_handler.dart b/mojo/public/dart/third_party/shelf_static/lib/src/static_handler.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c0afeb4246dfd7732e385ac1ff5da79910caf416 |
--- /dev/null |
+++ b/mojo/public/dart/third_party/shelf_static/lib/src/static_handler.dart |
@@ -0,0 +1,138 @@ |
+// 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. |
+ |
+library shelf_static.static_handler; |
+ |
+import 'dart:io'; |
+ |
+import 'package:http_parser/http_parser.dart'; |
+import 'package:mime/mime.dart' as mime; |
+import 'package:path/path.dart' as p; |
+import 'package:shelf/shelf.dart'; |
+ |
+import 'directory_listing.dart'; |
+import 'util.dart'; |
+ |
+// TODO option to exclude hidden files? |
+ |
+/// Creates a Shelf [Handler] that serves files from the provided |
+/// [fileSystemPath]. |
+/// |
+/// Accessing a path containing symbolic links will succeed only if the resolved |
+/// path is within [fileSystemPath]. To allow access to paths outside of |
+/// [fileSystemPath], set [serveFilesOutsidePath] to `true`. |
+/// |
+/// When a existing directory is requested and a [defaultDocument] is specified |
+/// the directory is checked for a file with that name. If it exists, it is |
+/// served. |
+/// |
+/// If no [defaultDocument] is found and [listDirectories] is true, then the |
+/// handler produces a listing of the directory. |
+Handler createStaticHandler(String fileSystemPath, |
+ {bool serveFilesOutsidePath: false, String defaultDocument, |
+ bool listDirectories: false}) { |
+ var rootDir = new Directory(fileSystemPath); |
+ if (!rootDir.existsSync()) { |
+ throw new ArgumentError('A directory corresponding to fileSystemPath ' |
+ '"$fileSystemPath" could not be found'); |
+ } |
+ |
+ fileSystemPath = rootDir.resolveSymbolicLinksSync(); |
+ |
+ if (defaultDocument != null) { |
+ if (defaultDocument != p.basename(defaultDocument)) { |
+ throw new ArgumentError('defaultDocument must be a file name.'); |
+ } |
+ } |
+ |
+ return (Request request) { |
+ var segs = [fileSystemPath]..addAll(request.url.pathSegments); |
+ |
+ var fsPath = p.joinAll(segs); |
+ |
+ var entityType = FileSystemEntity.typeSync(fsPath, followLinks: true); |
+ |
+ File file = null; |
+ |
+ if (entityType == FileSystemEntityType.FILE) { |
+ file = new File(fsPath); |
+ } else if (entityType == FileSystemEntityType.DIRECTORY) { |
+ file = _tryDefaultFile(fsPath, defaultDocument); |
+ if (file == null && listDirectories) { |
+ var uri = request.requestedUri; |
+ if (!uri.path.endsWith('/')) return _redirectToAddTrailingSlash(uri); |
+ return listDirectory(fileSystemPath, fsPath); |
+ } |
+ } |
+ |
+ if (file == null) { |
+ return new Response.notFound('Not Found'); |
+ } |
+ |
+ if (!serveFilesOutsidePath) { |
+ var resolvedPath = file.resolveSymbolicLinksSync(); |
+ |
+ // Do not serve a file outside of the original fileSystemPath |
+ if (!p.isWithin(fileSystemPath, resolvedPath)) { |
+ return new Response.notFound('Not Found'); |
+ } |
+ } |
+ |
+ // when serving the default document for a directory, if the requested |
+ // path doesn't end with '/', redirect to the path with a trailing '/' |
+ var uri = request.requestedUri; |
+ if (entityType == FileSystemEntityType.DIRECTORY && |
+ !uri.path.endsWith('/')) { |
+ return _redirectToAddTrailingSlash(uri); |
+ } |
+ |
+ var fileStat = file.statSync(); |
+ var ifModifiedSince = request.ifModifiedSince; |
+ |
+ if (ifModifiedSince != null) { |
+ var fileChangeAtSecResolution = toSecondResolution(fileStat.changed); |
+ if (!fileChangeAtSecResolution.isAfter(ifModifiedSince)) { |
+ return new Response.notModified(); |
+ } |
+ } |
+ |
+ var headers = <String, String>{ |
+ HttpHeaders.CONTENT_LENGTH: fileStat.size.toString(), |
+ HttpHeaders.LAST_MODIFIED: formatHttpDate(fileStat.changed) |
+ }; |
+ |
+ var contentType = mime.lookupMimeType(file.path); |
+ if (contentType != null) { |
+ headers[HttpHeaders.CONTENT_TYPE] = contentType; |
+ } |
+ |
+ return new Response.ok(file.openRead(), headers: headers); |
+ }; |
+} |
+ |
+Response _redirectToAddTrailingSlash(Uri uri) { |
+ var location = new Uri( |
+ scheme: uri.scheme, |
+ userInfo: uri.userInfo, |
+ host: uri.host, |
+ port: uri.port, |
+ path: uri.path + '/', |
+ query: uri.query); |
+ |
+ return new Response.movedPermanently(location.toString()); |
+} |
+ |
+File _tryDefaultFile(String dirPath, String defaultFile) { |
+ if (defaultFile == null) return null; |
+ |
+ var filePath = p.join(dirPath, defaultFile); |
+ |
+ var file = new File(filePath); |
+ |
+ if (file.existsSync()) { |
+ return file; |
+ } |
+ |
+ return null; |
+} |