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

More than 1 year has passed since last update.


前置き

bitmapをjpegにするのはいいけれども、別にpngとか圧縮してほしくないものもあるでしょ」

とかいうのもあると思います。

そこでファイルの種別判断としてMIMEタイプを用いるのが考えられます。

HTML5のFileAPIのtypeを使えば、MIMEタイプ取得できます。

「MIMEタイプを見て例えばbitmapだけjpeg圧縮対象にする」

のような対応もできるかと思います。


言いわけ

「こんなのサーバー側の仕事でしょ」

という声が聞こえてきそうです。ええ、そうです、確かにそうなんです。

ですがリクエストとレスポンスのオーバーヘッドがどーたらとか、

まあいろいろあって、クライアント側でやってみてるわけですよ。

その意味でもタイトルの「がんばって」なわけです、はい。


しかし問題点が…

HTML5のFileAPIのtypeを使えば、MIMEタイプ取得できる!

できる…のですが、ご存知の通りどうもこれ、ファイルの拡張子で判断してるんですよね。

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

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


サンプルコード


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>



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

ちゃんとimage/bmpと表示されていますね。方や、file.typeではimage/jpegと表示されてしまってます。

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

実行結果サンプル2.png

正しく出てますね。


まとめ

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

という記事でした。


補足

DICOMについては、以下参照ください。※医療系以外の人は関係ない…かも?

DICOM - Wikipedia

[RFC3240] Digital Imaging and Communications in Medicine (DICOM) - Application/dicom MIME Sub-type Registration