ファイルタイプ判別関数群 - Life like a clown の補足記事.ここでは,Office 関連のファイルの判別方法だけ記述してみます.
OLE2
旧 Office ファイル (DOC, XLS, PPT) は OLE2 と呼ばれるフォーマットで記述されています.したがって,DOC, XLS, PPT ファイルの判別を行う場合には,まず OLE2 ファイルかどうかの判別を行います.
inline bool is_ole2(std::basic_istream<char>& in) { static const unsigned char sig[8] = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 }; unsigned char magic[8]; in.read(reinterpret_cast<char*>(magic), 8); if (in.fail() || in.gcount() < 8) return false; if (std::memcmp(magic, sig, 8) != 0) return false; return true; }
OLE2 ファイルは,ファイルの先頭に 512Byte のヘッダが付与されます.このヘッダの先頭 16Byte が "D0 CF 11 E0 A1 B1 1A E1" であるかどうかで OLE2 ファイルであるかどうかを判別します.
DOC (Microsoft Word)
DOC ファイルは,OLE2 ヘッダの直後に FIB (File Information Block) と呼ばれるヘッダが付与されます.この最初の 2 バイトが Magic Number (wIdent = 0xA5EC)なのでこの値で DOC ファイルかどうかを判別します(参考: FibBase - MSDN).
inline bool is_doc(std::basic_istream<char>& in) { static const unsigned short wIdent = 0xa5ec; if (!is_ole2(in)) return false; in.seekg(512); // OLE2 ヘッダサイズ分読み飛ばす unsigned short magic; in.read(reinterpret_cast<char*>(&magic), 2); if (in.fail() || in.gcount() < 2) return false; if (magic != wIdent) return false; return true; }
XLS (Microsoft Excel)
XLS ファイルと PPT ファイルは判別がなかなか困難です.本当は OLE2 ライブラリなどを使用して判別するのが良いのですが,ここでは自力で判別してみます.
まず,OLE2 ヘッダにデータの開始地点が書かれているので(48--51Byte),その地点まで seek します.
ここでのデータは 128Byte を一つのブロックとして格納されています.また,最初の 128Byte は OLE2 共通(?)の "Root Entry" と呼ばれるブロックなので,このブロックは読み飛ばします.Excel の場合は,この直後に "Workbook" と呼ばれるブロックが出現するので,そのブロックの先頭の Magic Number を用いて判別します(古いバージョンの Excel の場合,"Book" かも?).
inline bool is_xls(std::basic_istream<char>& in) { static const unsigned short sig[] = { // "Workbook" 0x57, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b }; static const unsigned short old[] = { // "Book" 0x42, 0x6f, 0x6f, 0x6b }; if (!is_ole2(in)) return false; /* * OLE2 は,セクターと呼ばれる単位でデータを管理している. * 30 -- 31Byte に 1 セクター当たりのバイト数が格納されている. * 格納されている値は, 2^(bits) と言う形で使用する. * ほとんどの場合,ここの値は 9(すなわち,1 セクターは 2^9 = 512Byte). */ in.seekg(30); unsigned short bits = 0; in.read(reinterpret_cast<char*>(&bits), 2); if (in.fail() || in.gcount() < 2) return false; in.seekg(48); size_t pos = 0; in.read(reinterpret_cast<char*>(&pos), 4); if (in.fail() || in.gcount() < 4) return false; in.seekg(512 + pos * (1 << bits) + 128); unsigned short magic[64]; in.read(reinterpret_cast<char*>(magic), 128); if (in.fail() || in.gcount() < 128) return false; if (std::memcmp(magic, sig, sizeof(sig)) != 0 && std::memcmp(magic, old, sizeof(old)) != 0) { return false; } return true; }
PPT (Microsoft PowerPoint)
PPT はもうすこし複雑になります.PPT も XLS と同様の方法を用いて,"PowerPoint Document" と呼ばれるブロックを見つければ良いのですが,ユーザが高速保存を行っているかどうか,画像ファイルを含んでいるかどうか,で出現するブロックの順番が異なるようです.
ユーザが高速保存を行っている場合は,"Current User" と呼ばれるブロックが "PowerPoint Document" ブロックよりも先に出現します.また,画像ファイルを含んでいる場合は,"Pictures" と呼ばれるブロックが出現することもあるようです.
以上から,"Pictures" ブロックは読み飛ばして,"Current User" ブロックか "PowerPoint Document" ブロックが存在するかどうかで判別する事とします.
inline bool is_ppt(std::basic_istream<char>& in) { static const unsigned short usr[] = { // "Current User" 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72 }; static const unsigned short sig[] = { // "PowerPoint Document" 0x50, 0x6f, 0x77, 0x65, 0x72, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74 }; static const unsigned short pic[] = { // "Pictures" 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73 }; if (!is_ole2(in)) return false; in.seekg(30); unsigned short bits = 0; in.read(reinterpret_cast<char*>(&bits), 2); if (in.fail() || in.gcount() < 2) return false; in.seekg(48); size_t pos = 0; in.read(reinterpret_cast<char*>(&pos), 4); if (in.fail() || in.gcount() < 4) return false; in.seekg(512 + pos * (1 << bits) + 128); unsigned short magic[64]; in.read(reinterpret_cast<char*>(magic), 128); if (in.fail() || in.gcount() < 128) return false; if (std::memcmp(magic, pic, sizeof(pic)) == 0) { in.read(reinterpret_cast<char*>(magic), 128); if (in.fail() || in.gcount() < 128) return false; } if (std::memcmp(magic, sig, sizeof(sig)) != 0 && std::memcmp(magic, usr, sizeof(usr)) != 0) { return false; } return true; }
DOCX (Microsoft Word 2007)
Microsoft Office 2007 で新たに導入されたファイル形式 (DOCX, XLSX, PPTX) は,中身は複数のファイルを zip で圧縮しただけのものです.なので,ここでは clx::unzip を用いて解凍し,特定の (OpenXML) ファイルが存在するかどうかで判別します.
DOCX では,起点となるファイルは doc/document.xml です.
inline bool is_docx(const std::basic_string<char>& path) { basic_unzip<char> in(path); if (!in.is_open()) return false; if (in.find("word/document.xml") == in.end()) return false; return true; }
XLSX (Microsoft Excel 2007)
DOCX と同様の方法を用いて判別します.XLSX では,起点となるファイルは xl/workbook.xml です.
inline bool is_xlsx(const std::basic_string<char>& path) { basic_unzip<char> in(path); if (!in.is_open()) return false; if (in.find("xl/workbook.xml") == in.end()) return false; return true; }
PPTX (Microsoft PowerPoint 2007)
DOCX, XLSX と同様.起点となるファイルは,ppt/presentation.xml です.
inline bool is_pptx(const std::basic_string<char>& path) { basic_unzip<char> in(path); if (!in.is_open()) return false; if (in.find("ppt/presentation.xml") == in.end()) return false; return true; }
以上です.それぞれ,結構不安の残る判別方法ですが,上記で概ね判別できるのではと思います.