がんばってできるだけ正確にMIMEタイプを判定する方法(拡張子なし対応可)

##前置き
「[bitmapをjpegにする](https://qiita.com/Toshino3/items/e0d4da381e16b2cc4d1d "HTML5のcanvasを用いた bitmap⇒jpegの圧縮変換方法 (サイズそのままに、base64で取得)")のはいいけれども、別にpngとか圧縮してほしくないものもあるでしょ」
とかいうのもあると思います。
そこでファイルの種別判断としてMIMEタイプを用いるのが考えられます。
HTML5のFileAPIのtypeを使えば、MIMEタイプ取得できます。
「MIMEタイプを見て例えばbitmapだけjpeg圧縮対象にする」
のような対応もできるかと思います。

##言いわけ
「こんなのサーバー側の仕事でしょ」
という声が聞こえてきそうです。ええ、そうです、確かにそうなんです。
ですがリクエストとレスポンスのオーバーヘッドがどーたらとか、
まあいろいろあって、クライアント側でやってみてるわけですよ。
その意味でもタイトルの「がんばって」なわけです、はい。

##しかし問題点が…
HTML5のFileAPIのtypeを使えば、MIMEタイプ取得できる！
できる…のですが、ご存知の通りどうもこれ、ファイルの拡張子で判断してるんですよね。

となると、以下のようなケースで問題になるわけです。

* [拡張子が「jpeg」なのに実体は「bitmap」のようなファイル](https://qiita.com/Toshino3/items/e0d4da381e16b2cc4d1d#経緯 "HTML5のcanvasを用いた bitmap⇒jpegの圧縮変換方法 (サイズそのままに、base64で取得)")
* 私らが実務で使う医用画像 可搬DICOM(ファイル)のように拡張子がそもそも無いファイル

そこで、FileAPIのtypeに頼らないでファイルシグネチャを判別する方法を考えてみました。

##サンプルコード
```html:index.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <meta charset="utf-8" />
</head>
<body>
    ファイル選択
    <input type="file" id="targetBinFile">
    <input type="button" id="selectBinFileButton" value="読込">
    <br />
    <span id="fileTypeView"></span>
    <span>　</span>
    <span id="mimeTypeView"></span>

    <script src="scripts/mimeTypeChecker.js"></script>
</body>
</html>
```
```javascript:mimeTypeChecker.js
// 実際のヘッダを見てMIMEタイプをチェックする。
function getMimeType(header) {
    'use strict';

    var retv = '';
    switch (true) {
        case header.startsWith('89504e47'):
            retv = 'image/png';
            break;
        case header.startsWith('424d'):
            retv = 'image/bmp';
            break;
        case header.startsWith('47494638'):
            retv = 'image/gif';
            break;
        case header.startsWith('ffd8ff'):
            retv = 'image/jpeg';
            break;
        case header.startsWith('25504446'):
            // こんな風に画像以外にも応用可能ですよ。
            retv = 'application/pdf';
            break;
        case header.startsWith('0'.repeat(128) + '4449434d'):
            // DICOMは先頭128byteのNULL文字のあと、DICMが来る。
            // …まあ、DICOMヘッダが無い
            // いわゆる規約違反のDICOMファイルもたまにあるんですけどね…
            retv = 'application/dicom';
            break;
        default:
            retv = 'unknown';
            break;
            // 他にもシグネチャに応じた対応は可能かと思いますので、
            // Wikipediaなど参考にされるのもよいのではないでしょか。
    }
    return retv;
}

(function () {
    'use strict';

    document.getElementById('selectBinFileButton').onclick = function () {

        var file = document.getElementById('targetBinFile').files[0];

        var fileReader = new FileReader();
        fileReader.onloadend = function (e) {

            // 先頭150byteも取得すれば
            // 大方のシグネチャはカバーできるはず。
            // そう、医用画像のDICOMファイルもね。
            var arr = (new Uint8Array(e.target.result)).subarray(0, 150);

            var header = '';
            for (var i = 0; i < arr.length; i++) {
                header += arr[i].toString(16);
            }

            // 拡張子で判断してるっぽい。
            document.getElementById('fileTypeView').innerHTML = 'file.type: ' + (file.type === '' ? '(空)' : file.type);
            // 自前で判定したMIMEタイプを表示してます。
            document.getElementById('mimeTypeView').innerHTML = 'getMimeType: ' + getMimeType(header);
        };
        fileReader.readAsArrayBuffer(file);
    }
}());
```
※以下のメソッドはECMAScript6から追加されたものなので、IE11などではサポートされていません。
　適宜、MDNなどを参考にPolyfill対応するなどしてください(不親切)。

  * String.prototype.startsWith()
  * String.prototype.repeat()

##実行結果のサンプル
試しに、bitmapなファイルを複製して、拡張子を「jpeg」としたものを見てみますと、
以下のようになります。

|![実行結果サンプル.png](https://qiita-image-store.s3.amazonaws.com/0/258849/145d19d7-21a8-ef06-7d91-d23894e7e43c.png)|
|:-:|
ちゃんと```image/bmp```と表示されていますね。方や、file.typeでは```image/jpeg```と表示されてしまってます。

拡張子の無いDICOMファイルはというと…

|![実行結果サンプル2.png](https://qiita-image-store.s3.amazonaws.com/0/258849/fc469a8e-81cf-9df5-0ca9-9decaf6a124a.png)|
|:-:|
正しく出てますね。

##まとめ
というわけで、「できるだけ正確にMIMEタイプを判定」して、応じた実装を行うことができるのではないか、
という記事でした。

##補足
DICOMについては、以下参照ください。※医療系以外の人は関係ない…かも？
[DICOM - Wikipedia](https://ja.wikipedia.org/wiki/DICOM "DICOM")
[[RFC3240] Digital Imaging and Communications in Medicine (DICOM) - Application/dicom MIME Sub-type Registration](https://tools.ietf.org/html/rfc3240 "RFC3240")