Added option to allow for mimetype lookup via magic numbers
Closes https://github.com/dart-lang/shelf_static/pull/12
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7b9d5d..53c712c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.2.4
+
+* Add support for "sniffing" the content of the file for the content-type via an optional
+ `useHeaderBytesForContentType` argument on `createStaticHandler`.
+
## 0.2.3+4
* Support `http_parser` 3.0.0.
diff --git a/lib/src/static_handler.dart b/lib/src/static_handler.dart
index a8e7101..a2a1b5c 100644
--- a/lib/src/static_handler.dart
+++ b/lib/src/static_handler.dart
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
+import 'dart:math' as math;
+import 'package:convert/convert.dart';
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart' as mime;
import 'package:path/path.dart' as p;
@@ -27,9 +29,14 @@
///
/// If no [defaultDocument] is found and [listDirectories] is true, then the
/// handler produces a listing of the directory.
+///
+/// If [useHeaderBytesForContentType] is `true`, the contents of the
+/// file will be used along with the file path to determine the content type.
Handler createStaticHandler(String fileSystemPath,
- {bool serveFilesOutsidePath: false, String defaultDocument,
- bool listDirectories: false}) {
+ {bool serveFilesOutsidePath: false,
+ String defaultDocument,
+ bool listDirectories: false,
+ bool useHeaderBytesForContentType: false}) {
var rootDir = new Directory(fileSystemPath);
if (!rootDir.existsSync()) {
throw new ArgumentError('A directory corresponding to fileSystemPath '
@@ -44,7 +51,7 @@
}
}
- return (Request request) {
+ return (Request request) async {
var segs = [fileSystemPath]..addAll(request.url.pathSegments);
var fsPath = p.joinAll(segs);
@@ -100,7 +107,20 @@
HttpHeaders.LAST_MODIFIED: formatHttpDate(fileStat.changed)
};
- var contentType = mime.lookupMimeType(file.path);
+ String contentType;
+ if (useHeaderBytesForContentType) {
+ var length =
+ math.min(mime.defaultMagicNumbersMaxLength, file.lengthSync());
+
+ var byteSink = new ByteAccumulatorSink();
+
+ await file.openRead(0, length).listen(byteSink.add).asFuture();
+
+ contentType = mime.lookupMimeType(file.path, headerBytes: byteSink.bytes);
+ } else {
+ contentType = mime.lookupMimeType(file.path);
+ }
+
if (contentType != null) {
headers[HttpHeaders.CONTENT_TYPE] = contentType;
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 6396da8..f1cc8d4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,12 @@
name: shelf_static
-version: 0.2.3+4
+version: 0.2.4
author: Dart Team <misc@dartlang.org>
description: Static file server support for Shelf
homepage: https://github.com/dart-lang/shelf_static
environment:
sdk: '>=1.7.0 <2.0.0'
dependencies:
+ convert: '>=1.0.0 <3.0.0'
http_parser: '>=0.0.2+2 <4.0.0'
mime: '>=0.9.0 <0.10.0'
path: '>=1.1.0 <2.0.0'
diff --git a/test/basic_file_test.dart b/test/basic_file_test.dart
index c8bda81..8348761 100644
--- a/test/basic_file_test.dart
+++ b/test/basic_file_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
+import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as p;
import 'package:scheduled_test/descriptor.dart' as d;
@@ -24,12 +25,20 @@
d.file('index.html', '<html></html>').create();
d.file('root.txt', 'root txt').create();
d.file('random.unknown', 'no clue').create();
- d
- .dir('files', [
+
+ var pngBytesContent =
+ r"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAABmJLR0QA/wD/AP+gvae"
+ r"TAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AYRETkSXaxBzQAAAB1pVFh0Q2"
+ r"9tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAbUlEQVQI1wXBvwpBYRwA0"
+ r"HO/kjBKJmXRLWXxJ4PsnsMTeAEPILvNZrybF7B4A6XvQW6k+DkHwqgM1TnMpoEoDMtw"
+ r"OJE7pB/VXmF3CdseucmjxaAruR41Pl9p/Gbyoq5B9FeL2OR7zJ+3aC/X8QdQCyIArPs"
+ r"HkQAAAABJRU5ErkJggg==";
+
+ d.dir('files', [
d.file('test.txt', 'test txt content'),
- d.file('with space.txt', 'with space content')
- ])
- .create();
+ d.file('with space.txt', 'with space content'),
+ d.binaryFile('header_bytes_test_image', BASE64.decode(pngBytesContent))
+ ]).create();
currentSchedule.onComplete.schedule(() {
d.defaultRoot = null;
@@ -197,5 +206,17 @@
});
});
});
+
+ test('magic_bytes_test_image should be image/png', () {
+ schedule(() {
+ var handler = createStaticHandler(d.defaultRoot,
+ useHeaderBytesForContentType: true);
+
+ return makeRequest(handler, '/files/header_bytes_test_image')
+ .then((response) {
+ expect(response.mimeType, "image/png");
+ });
+ });
+ });
});
}