ファイルの先頭8バイトだけで画像のフォーマットを調べる

PHPでファイルをアップロードしたとき $_FILES にアップロードされたファイルについての情報が入っています。 そして $_FILES[...]['type'] にはアップロードされたファイルの MIME type が入っています。しかしここに入っている値は PHP: ファイルアップロードの処理 - Manual に
$_FILES['userfile']['type'] ファイルの MIME 型。ただし、ブラウザがこの情報を提供する場合。 例えば、"image/gif" のようになります。 この MIME 型は PHP 側ではチェックされません。そのため、 この値は信用できません。
と書かれている通り信頼できません。ソースコードを見てみると php-5.1.4/main/rfc1867.c の SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) という関数の中でブラウザから送られた Content-Type をそのまま代入していることがわかります。WindowsのFirefoxの場合、ブラウザはファイルの拡張子だけを見て Content-Type を送ってくるので中身とtypeが一致しないこともあります。 それで Building Scalable Web Sites: Building, scaling, and optimizing the next generation of web application の "Wireless Carriers Hates You" という部分。最近はあんまりへんなヘッダを送ってくる端末はなくなりましたが、アメリカでも携帯電話の送ってくるメールは写真がBASE64でエンコードされているけど content-type が text/plain だったりするそうで、だから中身読んで調べるしかないよ、というのを思い出しました。

各画像フォーマットのヘッダ

ふつうは PHP: getimagesize - Manual でも使えばすむ話ですが、getimagesizeは引数にファイルしかとらないので、メモリ上にデータがあるときは一度ファイルに書き込む必要があります。そんなに実用性はありませんが、ここでもったいない精神を発揮して自分で画像のフォーマットを調べてみましょう。JPEGとGIFとPNGだけでよければアタマの8バイトを見ることで判別可能です。

JPEG

判別可能、といいつつもJPEGはフォーマットが柔軟なためほかのフォーマットにあるヘッダのようなものが存在しません。1バイト目は必ず0xFFになっているものの2バイト目以降はJPEGファイルによってまちまちです。ですが、多くのファイルは先頭2バイトは0xFF0xD8になっています。JPEG フォーマット辞典 - しいしせねっと にJPEGファイルの先頭によくくるバイト列(マーカ)が載っていました。

GIF

GIF89a Specification によると、GIFファイルはファイルの先頭5バイトが必ず GIF87a もしくは GIF89a になっています。

PNG

PNGのヘッダは以下の8バイトで構成されています。
\x89PNG\x0d\x0a\x1a\x0a
なぜこの8バイトなのか PNG - Wikipedia, the free encyclopedia に書かれているのですが、これがよく考えられていています。

1バイト目の0x89は、データの転送路が8ビットクリーンかどうかを調べるためのものです。昔アメリカ人がネットワークでデータをやり取りするときにASCIIだけなら8ビット目は常に0だから節約できるよね、ということで8ビット目を節約するようにしたと聞きましたが詳しいことはよく知りません。あとからやっぱりASCIIだけじゃ困るということでバイナリをASCIIテキストにエンコードするBASE64だとか日本語を8ビット目を使わないようにエンコードする ISO-2022-JP(JIS) だとかで苦労しているのはその名残です。いまでも8ビット目がなくなっちゃうようなネットワークはあるのでしょうか。

2バイト目から4バイト目までの"PNG"はテキストエディタ等で人間が見たときにわかりやすいように入れられています。

3バイト目と4バイト目の 0x0D 0x0A は、Windowsの改行コードになっています。ファイルがテキストモードで転送されていると、画像データの中のこの並びがすべてそれぞれのプラットフォームに合わせて変わってしまい、データが壊れてしまいます。この2バイトが 0x0D 0x0A になっているかを調べることで、改行コードの変換が行われて転送時にデータが壊れていないかがわかります。

4バイト目の0x1Aは、MSDOSの元になったCP/Mのファイルシステムでファイルの終端を示していた値です。DOSプロンプトでTYPEコマンドを使ってファイル中身を表示すると、このコードがでできた場所をファイルの終端とみなしてそれ以降は表示されません。これはかなり実用性が低くてマニアックです。

5バイト目の 0x0A はUNIX系システムの改行コードです。3-4バイト目の 0x0D 0x0A 同様、改行コードが変換されていないかどうかを調べるためにあります。

まとめ

というわけで、正規表現にすると
if ( preg_match( '/^\x89PNG\x0d\x0a\x1a\x0a/', $image_stream) )  {
    $type = "png";
} elseif ( preg_match( '/^GIF8[79]a/', $image_stream) )  {
    $type = "gif";
} elseif ( preg_match( '/^\xff\xd8/', $image_stream) )  {
    $type = "jpg";
}
というコードで画像のフォーマットを判別できます。

追記 5/21

t-tanakaさんにはてなブックマークで /usr/share/magicの再発明のような気が・・・ というコメントをいただいたので、さっそくmagicファイルを調べました。 ファイルの内容を見て種類を調べるfileコマンドが、いろいろなファイルのヘッダが記述されているmagicファイルは知りませんでした。 OSXのmagicファイルのJPEGファイルヘッダ定義は
# JPEG images
# SunOS 5.5.1 had
#
#       0       string          \377\330\377\340        JPEG file
#       0       string          \377\330\377\356        JPG file
#
# both of which turn into "JPEG image data" here.
#
0       beshort         0xffd8          JPEG image data
こうなっています。 アップロードされたファイルのフォーマットをいちおうテストしたい、というときにはmagicを見てどうチェックしたらいいかを調べるとよさそうです。 ちなみにOSによってmagicファイルがとこにあるかはまちまちなようで、それぞれ
OSX
/usr/share/file/magic
Solaris
/etc/magic
Linux
/usr/share/magic
にありました。man file で下の方を見ると載っています。

tags

  • PHP
  • binary
  • 「ファイルの先頭8バイトだけで画像のフォーマットを調べる」のはてなブックマーク数
  • 「ファイルの先頭8バイトだけで画像のフォーマットを調べる」deliciousブックマーク数
  • 「ファイルの先頭8バイトだけで画像のフォーマットを調べる」をはてなブックマークに追加
  • save "ファイルの先頭8バイトだけで画像のフォーマットを調べる" to del.icio.us
  • 「ファイルの先頭8バイトだけで画像のフォーマットを調べる」をリアルタイムブログ検索
  • permalink
  • VOXのSCRIPT対応とユーザ認証用クッキーのドメイン
  • gmailで添付ファイルが自動でアップロードされるしくみ

comments

TypeKey Enabled
スタイル用のHTMLタグが使えます。
*required

trackbacks

トラックバック元エントリにこのエントリへのリンクがない場合はトラックバックを受け付けません。

http://labs.gmo.jp/mt/mt-tb.cgi/126
へっぽこ開発室ファイルの先頭8バイトだけで画像のフォーマットを調べる方法
2007.08.29 15:43
へっぽこ開発室
ファイルの先頭8バイトだけで画像のフォーマットを調べる方法 http://labs.gmo.jp/blog/ku/2007/05/8.html こりゃー便...
©2010 Kentaro Kumagai, GMO Internet Labs., GMO Internet, inc.
bits and bytes
2007 .05. 18 21:26

tagcloud

  • API1
  • C/C++2
  • E4X1
  • FUSE2
  • Firefox18
  • HTML4
  • IE1
  • MySQL1
  • OSX4
  • Opera2
  • PHP4
  • XML1
  • XPCOM4
  • XPath3
  • apache2
  • binary2
  • book1
  • data11
  • debug4
  • design1
  • experiments3
  • extension10
  • google gears1
  • google maps API1
  • greasemonkey3
  • httpd5
  • javascript17
  • linux1
  • logging2
  • mobile3
  • perl4
  • tips4
  • tool11
  • vim2
  • visualization2
  • widget1
  • wii1
  • windows7
  • サービス6
  • 和訳1

Archives

  • 2008.02 (6)
  • 2008.01 (3)
  • 2007.12 (4)
  • 2007.11 (5)
  • 2007.10 (4)
  • 2007.09 (4)
  • 2007.08 (4)
  • 2007.07 (8)
  • 2007.06 (7)
  • 2007.05 (4)
  • 2007.04 (5)
  • 2007.03 (6)
  • 2007.02 (4)
  • 2007.01 (6)

about

  • bits and bytesのXML
  • 「bits and bytes」のCreative Commons
  • Powered by Movable Type
  • イベントと地図 - モグ
  • Use ecto to blog!
  • bits and bytesのはてなブックマーク数
  • bits and bytesをMy Yahoo!に追加
  • Subscribe with Bloglines