Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2015年03月15日] [更新 2015年03月16日] Delphi サンプルプログラム集
   
728_ファイルの種類と MIME タイプ ( Content-Type )
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro + Indy10.5.7
728_File_MimeType.zip [1,227 KB] 2015年03月16日版(EXE同梱)







ファイルの種類と MIME タイプ ( Content-Type )


一般に,Windows では拡張子によってファイルの種類が判定できます.
一方,ウェブ上では,ファイルの種類,つまりデータの種類を表すのに MIME タイプという概念が用いられています.Windows におけるローカルなファイルの識別子が拡張子,ウェブにおけるファイルの識別子は MIME タイプということになります.
MiME type (Multipurpose Internet Mail Extensions - マイムタイプ) は,Content-Type (コンテンツタイプ) の値として下図のように,レジストリに記録されています.標準化されている MIME タイプの一覧が以下にあります.2 番目のリンクには,標準化されていないものも含まれています. 本ページのサンプルは,拡張子から MIME タイプを取得するサンプルです.
ファイルの形式によっては,ファイルのバイトデータから,拡張子に関係なく MIME タイプを取得することができます.ローカルディスクのファイルではそのテストもしています.


[備考]
ウェブブラウザでは,MIME タイプが text/html のファイルは HTML 文書として扱います.HTML 文書の拡張子には,.htm, html 等があります.Windows では,それらの拡張子に応じて,それを操作するアプリケーションを設定します.MIME タイプで設定するのではありません.
拡張子に .htm があるのは,過去の Windows では拡張子が 3 文字までであったという歴史的な経過があります.


図1
拡張子と Content Type
  • この Content Type の値が MIME タイプ




01_拡張子の MIME タイプ


ファイルの拡張子から MIME タイプを取得するサンプルです.
レジストリを直接操作しなくても,UrlMon.pas 内の FindMimeFromData 関数を使用すると,MIME タイプの値を取得することができます.
本サンプルは,テストしやすいように,エクスプローラからファイル名をドラッグして Edit1 に入力できるようにしています.ただし,本サンプルを管理者権限で実行した場合は機能しません.


図2
設計時画面
図3
実行結果例
  • 拡張子に応じた Content Type の値が取得できている

リスト1
拡張子に対応した MIME タイプの値を取得するサンプル
FindMimeFromData 関数を使用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    ApplicationEvents1: TApplicationEvents;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
  private
    { Private 宣言 }
    function GetExtensionMimeType(AFileName: String): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  ShellAPI, UrlMon, IOUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  DragAcceptFilesの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //Edit1へのドラッグアンドドロップを許可
  //対象コントロールはEdit1のみ
  DragAcceptFiles(Edit1.Handle, True);
  Memo1.Lines.Clear;
end;

//=============================================================================
//  拡張子のMIMEタイプを取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMimeType : String;
begin
  LMimeType := GetExtensionMimeType(Trim(Edit1.Text));
  Memo1.Lines.Add(TPath.GetExtension(Edit1.Text) + ' :  ' + LMimeType);
end;

//=============================================================================
//  TApplicationEventsのOnMessageイベント処理
//  エクスプローラからのファイルのドラッグ・ドロップ処理
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LCount    : Integer;
  LBuffer   : array [0..MAX_PATH-1] of char;
  LFilePath : String;
begin
  if Msg.message = WM_DROPFILES then begin
    //ドラッグドロップしたファイル数を取得
    LCount := DragQueryFile(Msg.wParam, High(Cardinal), nil, 0);
    if LCount = 1 then begin
      //ドラッグドロップしたファイル名を取得
      DragQueryFile(Msg.WParam, 0, LBuffer, MAX_PATH);
      LFilePath  := LBuffer;
      Edit1.Text := LFilePath;
      //拡張子のMIME Typeを取得して表示
      Button1Click(nil);
    end;
    //ドロップが完了したことを伝える
    DragFinish(msg.wParam);
  end;
end;

//-----------------------------------------------------------------------------
//  拡張子のMIMETYPEを取得する関数
//  FindMimeFromDataの使用にはusesにUrlMonが必要
//-----------------------------------------------------------------------------
function TForm1.GetExtensionMimeType(AFileName: String): String;
var
  LMimeType : PWideChar;
begin
  Result := '';
  LMimeType := nil;
  FindMimeFromData(nil, PWideChar(AFileName), nil, 0, nil, 0, LMimeType, 0);
  Result := LMimeType;
end;

end.


Indy の TIdMimeTable クラスの関数,GetFileMIMEType でも拡張子から MIME タイプを取得することができます.


リスト2
拡張子に対応した MIME タイプの値を取得するサンプル
Indy の TIdMimeTable クラスの関数を使用
//-----------------------------------------------------------------------------
//  拡張子のMIMEタイプを取得する関数
//  IndyのTIdMimeTableを使用する例
//  TIdMimeTableの使用にはusesにIdGlobalProtocolsが必要
//-----------------------------------------------------------------------------
function GetExtensionMimeType_TIdMIMETable(AFilePath: String): String;
var
  LMimeTable : TIdMimeTable;
begin
  LMimeTable := TIdMimeTable.Create;
  try
    Result := LMimeTable.GetFileMIMEType(AFilePath);
  finally
    FreeAndNil(LMimeTable);
  end;
end;




02_ファイルの内容で MIME タイプを判定  -  FindMimeFromData


上のサンプルは,拡張子に対応した MIME タイプを取得するものでした.
上と同じ FindMimeFromData 関数の第 3 引数に,ファイルの先頭から一定のバイトデータを渡すことで,ファイルの内容から MIME タイプを取得することができます.MSDN の記事によると,最初の 256 バイトまでを重視するとあります.そこで,本サンプルでは最初の 256 バイトまでを読み込んで判定しています.
ファイルの拡張子と関係なく MIME タイプが取得可能であることを確認するために,テストで使用したファイルの拡張子を全て .aaa にしています.

画像ファイルには,ヘッダ情報というのがあり,先頭部分に画像の種類を意味するバイト文字列が書き込まれています.それらの値を直接読み取る方法もありますが,MIME タイプで画像の種類を判別すれば,画像を扱うアプリケーションが,より便利になるのではないかと思われます.
本サンプルも,テストしやすいように,エクスプローラからファイル名をドラッグして Edit1 に入力できるようにしています.ただし,本サンプルを管理者権限で実行した場合は機能しません.



図4
設計時画面
図5
実行結果の例
  • 拡張子の名前とは関係なく,ファイルの種類が判定できる
  • 上から順に
    ビットマップファイル
    GIF 画像ファイル
    JPEG 画像ファイル
    PNG 画像ファイル
    Windows メタファイル
    テキストファイル
    ZIP 形式の圧縮ファイル

下図はエクセルのブックを,このサンプルで判定した結果です.
.xls 形式のブックは,application/octet-stream となっています.これはバイナリファイルまたはファイルの形式が不明な場合の結果です..xlsm, xlsx は Excel 2007 以降のブック形式です,これらのブックは zip 形式のファイルとなっています.そのため,zip という結果になっています.

下の図は,FindMimeFromData 関数の第 2 引数を nil にした結果ですが,ここにファイル名を渡すと,拡張子とバイトデータの両方の情報を使用して,より確実な判定が可能となります.



図6
エクセルのブックの場合

下図は EXE ファイルの MIME タイプを調べた結果です.
application/x-msdownload となっています.これは,ブラウザでこのファイルをクリックすると,ダウンロードすることを意味しています.この値がもし,text/plain になっていると,ブラウザで表示することになり,EXE ファイルの中身が表示されることになります.
もちろん,MIME タイプが text/plain になっていても,ディスク上の EXE をダブルクリックすれば,その EXE が実行されます.拡張子は Windows におけるファイルの識別子,MIME タイプはウェブ (インターネット) におけるファイルの識別子です.



図7
EXE ファイルの MIME タイプ

リスト3
ファイルの内容で MIME タイプを判定
FindMimeFromData 関数を使用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    ApplicationEvents1: TApplicationEvents;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
  private
    { Private 宣言 }
    function GetFileMimeType(AFileName: String; UseFileName: Boolean=True): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI, UrlMon, IOUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  DragAcceptFilesの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //Edit1へのドラッグアンドドロップを許可
  //対象コントロールはEdit1のみ
  DragAcceptFiles(Edit1.Handle, True);
  Memo1.Lines.Clear;
end;

//=============================================================================
//  ファイルのMIMETYPEを取得して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMimeType : String;
begin
  LMimeType := GetFileMimeType(Trim(Edit1.Text), CheckBox1.Checked);
  Memo1.Lines.Add(TPath.GetExtension(Edit1.Text) + ' :  ' + LMimeType);
end;

//=============================================================================
//  TApplicationEventsのOnMessageイベント処理
//  エクスプローラからのファイルのドラッグ・ドロップ処理
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LCount    : Integer;
  LBuffer   : array [0..MAX_PATH-1] of char;
  LFilePath : String;
begin
  if Msg.message = WM_DROPFILES then begin
    //ドラッグドロップしたファイル数を取得
    LCount := DragQueryFile(Msg.wParam, High(Cardinal), nil, 0);
    if LCount = 1 then begin
      //ドラッグドロップしたファイル名を取得
      DragQueryFile(Msg.WParam, 0, LBuffer, MAX_PATH);
      LFilePath  := LBuffer;
      Edit1.Text := LFilePath;
      //拡張子のMIME Typeを取得して表示
      Button1Click(nil);
    end;
    //ドロップが完了したことを伝える
    DragFinish(msg.wParam);
  end;
end;

//-----------------------------------------------------------------------------
//  ファイルのMIMETYPEを取得する関数
//  FindMimeFromDataの使用にはusesにUrlMonが必要
//-----------------------------------------------------------------------------
function TForm1.GetFileMimeType(AFileName: String;
  UseFileName: Boolean=True): String;
const
  MAX_COUNT = 256;
var
  LFileName  : PWideChar;
  LStream    : TFileStream;
  LMimeType  : PWideChar;
  LBinReader : TBinaryReader;
  LBinArray  : TArray<Byte>;
  LReadSize  : Cardinal;
begin
  Result := '';

  if UseFileName then begin
    LFileName := PWideChar(AFileName);
  end else begin
    LFileName := nil;
  end;

  //ファイルストリームを生成
  LStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  if LStream.Size > MAX_COUNT then begin
    LReadSize := MAX_COUNT;
  end else begin
    LReadSize := LStream.Size;
  end;

  //TBinaryReaderのインスタンス生成
  LBinReader := TBinaryReader.Create(LStream, TEncoding.Default, False);
  try
    //バイト型配列に格納
    LBinArray := LBinReader.ReadBytes(LReadSize);

    LMimeType := nil;
    if FindMimeFromData(nil,
                        LFileName,
                        LBinArray,
                        LReadSize,
                        nil,
                        0,
                        LMimeType,
                        0) = S_OK then begin
      Result := LMimeType;
    end;
  finally
    FreeAndNil(LBinReader);
    FreeAndNil(LStream);
  end;
end;

end.




03_ウェブ上のファイルの MIME タイプ


HTTP ウェブサーバにあるファイルの MIME タイプを取得するサンプルです.
ただし,本サンプルのコードは拡張子だけで MIME タイプを取得します.したがって,結果的には最初のサンプルとほとんど同じ動作仕様となります.

Indy の TIdHTTP1 コンポーネントと WinInet の関数を使用する例です.必要であればファイルの先頭から一定のバイト数だけのデータを取得し,FindMimeFromData 関数を使用して MIME タイプを判定する方法も考えられます.
ウェブブラウザ等,ウェブ関係のアプリケーションの中には,type 属性やファイルの内容も考慮して MIME タイプを判定するものあるようです.



[備考]
HTML の link 要素,a 要素,object 要素,script 要素,あるいは style 要素では,type 属性で MIME タイプを設定します.HTML コードを解析する際,この type 属性の値でリンク先のファイルの MIME タイプを調べることができます.


図8
設計時画面
図9
実行結果の例

リスト4
ウェブ上のファイルの MIME タイプの判定
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdHTTP;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    IdHTTP1: TIdHTTP;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    function GetUrlMimeType_TIdHTTP(UrlPath: String): String;
    function GetUrlMimeType_WinInet(UrlPath: String): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  IOUtils, WinInet;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Edit1.Text := 'http://mrxray.on.coocan.jp/test/pngimg.png';
end;

//=============================================================================
//  MIMEタイプを取得
//  IndyのTidHTTPコンポを使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMimeType : String;
begin
  LMimeType := GetUrlMimeType_TIdHTTP(Trim(Edit1.Text));

  if LMimeType <> '' then begin
    Memo1.Lines.Add(TPath.GetFileName(Edit1.Text) + ' :  ' + LMimeType);
  end else begin
    Memo1.Lines.Add('-------');
  end;
end;

//=============================================================================
//  MIMEタイプを取得
//  WinInetの関数を使用
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMimeType : String;
begin
  LMimeType := GetUrlMimeType_WinInet(Trim(Edit1.Text));

  if LMimeType <> '' then begin
    Memo1.Lines.Add(TPath.GetFileName(Edit1.Text) + ' :  ' + LMimeType);
  end else begin
    Memo1.Lines.Add('-------');
  end;
end;

//-----------------------------------------------------------------------------
//  MIMEタイプを取得する関数
//  IndyのTidHTTPコンポを使用する例
//  ファイルの存在する場合は拡張子のMIMEタイプを返す
//-----------------------------------------------------------------------------
function TForm1.GetUrlMimeType_TIdHTTP(UrlPath: String): String;
begin
  Result := '';

  Screen.Cursor := crHourGlass;
  IdHTTP1.HandleRedirects := False;
  try
    //このURLが存在しないと,Headメソッド実行でエラーとなる
    IdHTTP1.Head(UrlPath);
    Result := IdHTTP1.Response.ContentType;
  except
  end;
  IdHTTP1.Disconnect;
  Screen.Cursor := crDefault;
end;

//-----------------------------------------------------------------------------
//  MIMEタイプを取得する関数
//  WinInetの関数を使用する例
//  usseにWinInettが必要
//-----------------------------------------------------------------------------
function TForm1.GetUrlMimeType_WinInet(UrlPath: String): String;
var
  LhSession   : HINTERNET;
  LhService   : HINTERNET;
  LStatusCode : Integer;
  LdwSize     : DWORD;
  LReserved   : DWORD;
  LlpvBuffer  : array[0..MAX_PATH-1] of Char;
begin
  Result := '';

  Screen.Cursor := crHourGlass;
  LhSession := InternetOpen(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0 );
  if Assigned(LhSession) then begin
    LhService := InternetOpenUrl(LhSession,
                                 PChar(UrlPath),
                                 nil,
                                 0,
                                 INTERNET_FLAG_RELOAD or INTERNET_FLAG_RAW_DATA,
                                 0);

    if LhService <> nil then begin
      //ファイルの存在を確認
      FillChar(LlpvBuffer, MAX_PATH, #0);
      LdwSize   := SizeOf(LStatusCode);
      LReserved := 0;
      HttpQueryInfo(LhService,
                    HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER,
                    @LStatusCode,
                    LdwSize,
                    LReserved);

      if (LStatusCode >= 200) and (LStatusCode < 300) then begin
        FillChar(LlpvBuffer, MAX_PATH, #0);
        LdwSize   := SizeOf(LlpvBuffer);
        LReserved := 0;
        HttpQueryInfo(LhService,
                      HTTP_QUERY_CONTENT_TYPE,
                      @LlpvBuffer,
                      LdwSize,
                      LReserved);
        Result := LlpvBuffer;
      end;
    end;
    InternetCloseHandle(LhService);
  end;
  InternetCloseHandle(LhSession);
  Screen.Cursor := crDefault;
end;

end.