MIMEタイプ判定は「中身の種類をちゃんと確認する」ための技
拡張子はあくまで「ラベル」でしかなくて、virus.exe を virus.png にリネームすることも簡単にできます。
業務システムでファイルを扱うときは、「本当に画像なのか?」「本当に CSV なのか?」を、できるだけ中身ベースで確認したくなります。
そこで出てくるのが「MIMEタイプ判定」です。image/png や text/csv のような「コンテンツタイプ」を調べることで、拡張子だけに頼らない、少しマシなチェックができるようになります。
基本:Files.probeContentType(Path) で MIMEタイプを推測する
まずは標準APIでの最小サンプル
Java 7 以降には、標準で MIMEタイプを推測する API が用意されています。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class MimeBasic {
public static void main(String[] args) throws IOException {
Path path = Path.of("data/sample.png");
String mime = Files.probeContentType(path);
System.out.println("file = " + path);
System.out.println("mime = " + mime);
}
}
JavaFiles.probeContentType(path) は、OS やインストールされているファイルタイプ情報を使って、
そのファイルの MIMEタイプ(例: image/png, text/plain)を文字列で返してくれます。
判定できなかった場合は null が返ることもあります。
ここで重要なのは、「必ず正しいとは限らないが、“拡張子だけよりはマシ”な判定ができる」という位置づけです。
OS の設定や環境に依存する部分もあるので、「絶対に信用してよい真実」ではなく、「判断材料の一つ」として使う感覚が大事です。
実務で使える MIMEタイプ判定ユーティリティの最小形
null や例外を吸収して、扱いやすい形にする
probeContentType は IOException を投げる可能性があり、判定できないときは null になります。
そのまま使うと呼び出し側が毎回面倒なので、ユーティリティで包んでしまいます。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public final class MimeTypes {
private MimeTypes() {}
public static Optional<String> detect(Path path) {
if (path == null) {
return Optional.empty();
}
try {
String mime = Files.probeContentType(path);
return Optional.ofNullable(mime);
} catch (IOException e) {
return Optional.empty();
}
}
public static String detectOrUnknown(Path path) {
return detect(path).orElse("application/octet-stream");
}
}
Java使う側はこう書けます。
Path path = Path.of("data/sample.png");
System.out.println(MimeTypes.detect(path)); // Optional[image/png] など
System.out.println(MimeTypes.detectOrUnknown(path)); // image/png または application/octet-stream
Javaここで深掘りしたいポイントは二つです。
一つ目は、「判定できないケースを Optional やデフォルト値で吸収している」ことです。
呼び出し側が毎回 try-catch と null チェックを書くのはつらいので、ユーティリティ側で“扱いやすい形”に変換しておきます。
二つ目は、「application/octet-stream を“よく分からないバイナリ”のデフォルトとして使う」ことです。
これは「特定のタイプが分からないバイナリデータ」を表す MIMEタイプで、
「とりあえず何かのバイナリだけど、詳細は不明」という意味合いでよく使われます。
例題:アップロード画像の MIMEタイプチェック
拡張子+MIMEタイプの二段構えで弾く
画像アップロード機能を考えてみます。
「拡張子が .png か .jpg であること」に加えて、「MIMEタイプが image/ で始まること」も確認すると、
単純な偽装をある程度は防げます。
import java.nio.file.Path;
import java.util.Set;
public final class ImageUploadValidator {
private ImageUploadValidator() {}
private static final Set<String> ALLOWED_EXT = Set.of("png", "jpg", "jpeg");
public static void validate(Path uploadedFile) {
String ext = PathExtensions.getExtension(uploadedFile).toLowerCase();
if (!ALLOWED_EXT.contains(ext)) {
throw new IllegalArgumentException("許可されていない拡張子です: " + ext);
}
String mime = MimeTypes.detectOrUnknown(uploadedFile);
if (!mime.startsWith("image/")) {
throw new IllegalArgumentException("画像ではない可能性があります: mime=" + mime);
}
}
}
Java使い方はこうです。
Path uploaded = Path.of("work/upload/user-icon.png");
ImageUploadValidator.validate(uploaded);
Javaここでの重要ポイントは、「拡張子と MIMEタイプを“両方見る”ことで、チェックの精度を上げている」ことです。
拡張子だけだと簡単に偽装されますし、MIMEタイプだけだと環境依存で判定できないこともあります。
二つを組み合わせることで、「安価にできる範囲で、そこそこマシなチェック」を実現しています。
もちろん、これでも完璧ではありませんが、「何も見ない」よりはずっと安全側に寄せられます。
例題:ログに MIMEタイプを出しておく
障害調査で「何を扱っていたか」を一目で分かるようにする
ファイル処理の障害が起きたとき、「そのとき扱っていたファイルがどんな種類だったか」が分かると、原因に近づきやすくなります。
そこで、「ファイルパス+サイズ+MIMEタイプ」をまとめてログに出すユーティリティを用意しておくと便利です。
import java.io.IOException;
import java.nio.file.Path;
public final class FileDescribe {
private FileDescribe() {}
public static String describe(Path path) throws IOException {
String mime = MimeTypes.detectOrUnknown(path);
long size = FileSizes.size(path);
return path + " [" + mime + ", " + FileSizes.humanReadable(size) + "]";
}
}
Java使い方はこうです。
Path file = Path.of("data/input.csv");
System.out.println("processing " + FileDescribe.describe(file));
Javaこれでログには、例えばこんな感じで出ます。
processing data/input.csv [text/plain, 12.3 KB]
ここでのポイントは、「MIMEタイプを“ログの文脈情報”として使う」ことです。
「text/plain だと思っていたけど、実は application/zip だった」というようなミスマッチが、ログからすぐに見えるようになります。
サードパーティ(Apache Tika など)を使う選択肢
本気でやるなら「中身を解析するライブラリ」
標準の Files.probeContentType は、OS の設定や拡張子ベースの判定に依存することが多く、
「ファイルの中身をしっかり解析して判定する」ほどの精度はありません。
もっと本格的に MIMEタイプを判定したい場合は、Apache Tika のようなライブラリを使う選択肢があります。
import org.apache.tika.Tika;
public final class TikaMimeTypes {
private static final Tika tika = new Tika();
public static String detect(byte[] data) {
return tika.detect(data);
}
}
JavaTika はファイルのシグネチャ(先頭のバイト列)などを見て判定してくれるので、
拡張子に頼らない、より精度の高い MIMEタイプ判定ができます。
ただし、依存ライブラリが増える・処理が重くなるといったトレードオフもあるので、
「どこまでの精度が必要か」「どの処理にだけ使うか」をチームで決めて導入するのが現実的です。
MIMEタイプ判定の“限界”と注意点
「判定結果を過信しない」ことが一番大事
MIMEタイプ判定は便利ですが、「100% 正しいとは限らない」という前提を絶対に忘れてはいけません。
標準 API でも Tika でも、「判定ロジックの限界」「壊れたファイル」「意図的な偽装」などで、誤判定は起こり得ます。
だからこそ、次のようなスタンスが大事です。
- MIMEタイプは「許可するかどうかの一条件」であって、「安全性を完全に保証するもの」ではない。
- アップロード機能などでは、MIMEタイプチェックに加えて「サイズ上限」「拡張子」「中身のパース」など、複数の防御線を張る。
- 判定できなかった(null や
application/octet-stream)場合の扱いを、仕様として決めておく。
この「過信しない」という感覚を持っていれば、MIMEタイプ判定はとても頼りになる“補助ツール”になります。
まとめ:MIMEタイプ判定ユーティリティで身につけたい感覚
MIMEタイプ判定は、「拡張子だけに頼らず、ファイルの“中身の種類”をできる範囲で確認する」ための技です。
押さえておきたい感覚はこうです。
- 標準の
Files.probeContentType(Path)をユーティリティで包み、null や例外を扱いやすくする。 - 拡張子チェックと組み合わせて、「安価にできる範囲でそこそこマシなバリデーション」を行う。
- ログには「パス+サイズ+MIMEタイプ」を出しておき、障害調査で状況を再現しやすくする。
- 精度が本当に必要な箇所だけ、Apache Tika のようなライブラリ導入を検討する。
- MIMEタイプ判定は“完全な安全”ではなく、“判断材料の一つ”として使う、という距離感を保つ。
