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");
+        });
+      });
+    });
   });
 }