Smdn.Formats.Mimeのドキュメントとサンプルです。 ここに記載されているものはversion 0.36時点のものです。

このドキュメントは完全ではないため、ライブラリの使用方法に関する疑問やここに記載されていない事項については掲示板へどうぞ。

§1 ライブラリの構成

§1.1 アセンブリ構成

本ライブラリはいくつかのアセンブリに分かれています。

アセンブリに含まれる型と用途
アセンブリ 含まれる型
Smdn.dll 他のアセンブリで共通して使用される型、ユーティリティクラス、.NET Framework 4.x, 3.x互換の型など
Smdn.Core.Standards.dll MIMEエンコード/デコード、URLエンコード/デコード等の標準に関するクラス群 (Smdn.Net.Imap4.ClientSmdn.Net.Pop3.Clientと共用)
Smdn.Formats.Mime.dll MIMEメッセージのパース・デコード機能を含むクラス郡

本ライブラリを使用する場合は、上記アセンブリへの参照を追加してください。

§1.2 主なクラス・型

本ライブラリに含まれる主な型と用途は次のとおりです。

Smdn.Formats.Mime名前空間の型
用途 アセンブリ
MimeMessageクラス MIMEメッセージを読み込んでパース・デコードを行うクラス。 マルチパートメッセージでは、一つ一つのパートがMimeMessageとして分解された入れ子構造となります。 Smdn.Formats.Mime.dll
Mailクラス MimeMessageクラスをラップして、From/To/Subjectなどのヘッダの参照等をより簡単に行えるようにしたクラス。 Smdn.Formats.Mime.dll
AttachedFileクラス MimeMessageクラスをラップして、添付ファイルの操作をより簡単に行えるようにしたクラス。 Mail.AttachedFileプロパティから取得可能。 Smdn.Formats.Mime.dll
MimeHeaderNamesクラス よく使われるヘッダ名(Content-Type等)を定数としてまとめたクラス。 Smdn.Formats.Mime.dll
Charsetsクラス よく使われる文字セットを定数としてまとめたクラス。 また、文字セット名から対応するSystem.Text.Encodingを検索するためのメソッドも実装しています。 Smdn.Formats.Mime.dll
ContentTransferEncodingクラス 転送エンコーディング(Content-Transfer-Encoding)でエンコードされた本文のデコードを行うためのStream/StreamReader/BinaryReaderを作成するクラス。 Smdn.Core.Standards.dll
MimeEncodingクラス MIMEヘッダエンコーディング(MIME-Encoding)で文字列をエンコード・デコードするためのクラス。 Smdn.Core.Standards.dll
Smdn.Formats.Mime.Formatting名前空間の型
用途 アセンブリ
MimeMessageBuilderクラス MIMEメッセージの組立・エンコードを行うクラス。 Smdn.Formats.Mime.dll
MimeHeaderBuilderクラス MIMEヘッダの組立・エンコードを行うクラス。 Smdn.Formats.Mime.dll
MimeFormat メッセージ組立時の折り返し文字数・改行文字などの書式を定義するクラス。 Smdn.Formats.Mime.dll
Smdn.Formats.UUEncoding名前空間の型
用途 アセンブリ
UUDecoderクラス uuencodeされたデータを含むストリームからファイルをデコードして抽出するクラス。 Smdn.Core.Standards.dll

version 0.36時点ではSmdn.Formats.Mime.Formatting名前空間のクラスは実装・動作検証が不十分です。 また、今後のバージョンで大きな変更を行う可能性もあります。


§2 サンプルコード

§2.1 Mailクラス

§2.1.1 メールの読み込み・解析 (Loadメソッド)

emlファイルを読み込んで件名・差出人・日付と本文を表示する例。

using System;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args) {
    // 電子メール形式のファイルsample.emlを読み込む
    using (Mail mail = Mail.Load("sample.eml")) {
      // 各ヘッダのデコード済みの値を表示
      Console.WriteLine("From: {0}", mail.From);
      Console.WriteLine("Date: {0}", mail.Date);
      Console.WriteLine("Subject: {0}", mail.Subject);
      Console.WriteLine();

      // メールの本文を文字列として取得して表示
      Console.WriteLine(mail.ReadContentAsText());
    }
  }
}

Mail.Loadメソッドでは、ファイルからMailのインスタンスを作成した場合、内部で開いたファイルのFileStreamを保持します。 Mailインスタンスが不要になったら、Disposeメソッドを呼び出すようにしてください。

Mail.Loadメソッドの引数loadContentToMemoryにtrueを指定すると、ファイルの内容をメモリ上に展開して保持します。 この場合FileStreamは保持しないため、必ずしもDisposeメソッドを呼ぶ必要はありません。

MimeMessage.Loadメソッドも、Mail.Loadメソッドと同様の動作となります。

§2.1.2 文字列からの読み込み (LoadMailメソッド)

文字列(string)に格納されているメール内容からMailインスタンスを作成する場合は、Mail.LoadMailメソッドが使えます。

using System;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    string input = @"MIME-Version: 1.0
Content-Type: text/plain

Hello, world!";

    // 文字列からMailのインスタンスを作成する
    Mail mail = Mail.LoadMail(input);

    // 本文を表示する
    Console.WriteLine(mail.ReadContentAsText());
  }
}

§2.1.3 添付ファイルの保存 (SaveAttachedFilesToDirectoryメソッド)

emlファイルを読み込んでメールに添付されているファイルを保存する例。

using System;
using System.IO;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    // 電子メール形式のファイルsample.emlを読み込む
    using (Mail mail = Mail.Load("sample.eml")) {
      // 電子メールに添付されている全てのファイルをディレクトリattachedに保存
      FileInfo[] files = mail.SaveAttachedFilesToDirectory("attached");

      // 保存したファイルの名前とサイズを表示する
      foreach (FileInfo file in files) {
        Console.WriteLine("{0} ({1} bytes)", file.FullName, file.Length);
      }
    }
  }
}

上記の例で使用しているSaveAttachedFilesToDirectoryメソッドでは、添付ファイルにファイル名が設定されていない場合ライブラリがファイル名を決定します。 個々の添付ファイルのファイル名を指定したい場合は、SaveAttachedFilesAsメソッドを使うか、次の例のようにMail.AttachedFilesプロパティを参照して添付ファイルを一つずつ保存してください。

§2.2 AttaachedFileクラス

メールに添付されているファイル(Mail.AttachedFiles)を一つずつ保存する例。

using System;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    // 電子メール形式のファイルsample.emlを読み込む
    using (Mail mail = Mail.Load("sample.eml")) {
      // 電子メールに添付されているすべてのファイルを参照
      foreach (AttachedFile attachedFile in mail.AttachedFiles) {
        // ファイル名を取得
        string filename = attachedFile.FileName;

        if (filename == null) {
          // 添付ファイルにファイル名が設定されていない場合、
          // Content-Typeに合わせて適当なファイル名を設定
          if (attachedFile.MimeType.Equals("image/jpeg"))
            filename = "attached.jpg";
          else if (attachedFile.MimeType.Equals("text/csv"))
            filename = "attached.csv";
          else if (attachedFile.MimeType.Equals("text/html"))
            filename = "attached.html";
        }

        // 添付ファイルを保存
        attachedFile.SaveContent(filename);
      }
    }
  }
}

§2.3 MimeMessageクラス

§2.3.1 分割メールの結合 (ReassembleFragmentedMessageメソッド)

分割メール(message/partial)を結合して、結合したメッセージのMimeMessageを取得する例。 ReassembleFragmentedMessageメソッドではメールヘッダを参照して適切な順序に並び替えた上で結合するため、このメソッドに渡す配列・コレクションは結合されるべき順序で並べ替えられている必要はありません。

using System;
using System.IO;

using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    // ディレクトリ 'fragmented-mails' に保存されている分割メールのファイルを取得
    string[] files = Directory.GetFiles("fragmented-mails", "*.eml");

    // ファイルを読み込んでMailインスタンスを作成
    Mail[] mails = new Mail[files.Length];

    for (int i = 0; i < files.Length; i++) {
      mails[i] = Mail.Load(files[i]);
    }

    // メールを結合
    using (MimeMessage reassembled = MimeMessage.ReassembleFragmentedMessage(mails)) {
      // 結合したメールをファイルに保存
      reassembled.Save("reassembled.eml");
    }
  }
}

なお、ReassembleFragmentedMessageメソッドはインスタンスメソッドと静的メソッドでidパラメータの取り扱いで動作が異なります。 インスタンスメソッドのReassembleFragmentedMessageでは、Content-Typeヘッダのidパラメータをチェックして同一のidを持つ分割メッセージのみを順番に結合します(引数に渡されたメッセージのうち、異なるidのメッセージは無視します)。 一方、静的メソッドのReassembleFragmentedMessageではidパラメータのチェックは行わず、numberパラメータのみをチェックして順番に結合します(引数に渡されたメッセージのうち、異なるidのメッセージがある場合でも結合します)。

§2.3.2 emlファイルの解析

emlファイルを読み込んでヘッダの一覧とボディの内容を表示する例。

using System;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args) {
    // sample.emlを読み込む
    using (var message = MimeMessage.Load("sample.eml")) {
      // 内容を表示する
      Dump(message, 0);
    }
  }

  private static void Dump(MimeMessage message, int nest)
  {
    var indent = new string(' ', nest * 4);

    // 個々のヘッダの内容を表示
    DumpHeaders(message, indent);

    Console.Write(indent);

    // 本文がテキスト形式の場合は、本文を読み込んで表示
    if (message.MimeType.TypeEquals("text"))
      Console.WriteLine(message.ReadContentAsText());
    else
      Console.WriteLine(message.MimeType);

    // マルチパートメッセージ等、入れ子になっているメッセージがある場合は、再帰的に内容を表示
    foreach (var part in message.SubParts) {
      Console.WriteLine();

      Dump(part, nest + 1);
    }
  }

  private static void DumpHeaders(MimeMessage message, string indent)
  {
    foreach (var header in message.Headers) {
      Console.WriteLine("{0}{1}: {2}", indent, header.Name, header.Value);
    }
  }
}

§2.4 ContentTransferEncodingクラス

§2.4.1 BASE64のデコード

BASE64エンコードされたストリームのデコードを行う例。

using System;
using System.IO;
using System.Text;

using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    using (var stream = File.OpenRead("base64.txt")) {
      // ファイルの内容をそのまま読み込み表示する
      var reader = new StreamReader(stream, Encoding.ASCII);

      Console.WriteLine(reader.ReadToEnd());
    }

    using (var stream = File.OpenRead("base64.txt")) {
      // 文字コードUTF-8、Base64でエンコードされた内容を読み取るStreamReaderを作成して読み込む
      var reader = ContentTransferEncoding.CreateTextReader(stream,
                                                            ContentTransferEncodingMethod.Base64,
                                                            Encoding.UTF8);

      Console.WriteLine(reader.ReadToEnd());
    }
  }
}
実行結果
5oud5ZWTDQrmmYLkuIvjgb7jgZnjgb7jgZnjgZTmuIXmoITjga7jgZPjgajjgajjgYrllpzjgb
PnlLPjgZfjgYLjgZLjgb7jgZnjgIINCuW5s+e0oOOBr+agvOWIpeOBruOBiuW8leOBjeeri+OB
puOBq+OBguOBmuOBi+OCiuOAgeOBguOCiuOBjOOBn+OBj+WOmuOBj+OBiuekvOeUs+OBl+OBgu
OBkuOBvuOBmeOAgg==

拝啓
時下ますますご清栄のこととお喜び申しあげます。
平素は格別のお引き立てにあずかり、ありがたく厚くお礼申しあげます。

§2.4.2 quoted-printableのデコード

quoted-printable形式のストリームをデコードして読み込む場合は、BASE64の場合と同じコードを使い、ContentTransferEncodingMethod.Base64の代わりにContentTransferEncodingMethod.QuotedPrintableを指定するだけで出来ます。

using System;
using System.IO;
using System.Text;

using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args)
  {
    using (var stream = File.OpenRead("quoted-printable.txt")) {
      // ファイルの内容をそのまま読み込み表示する
      var reader = new StreamReader(stream, Encoding.ASCII);

      Console.WriteLine(reader.ReadToEnd());
    }

    using (var stream = File.OpenRead("quoted-printable.txt")) {
      // 文字コードUTF-8、quoted-printableでエンコードされた内容を読み取るStreamReaderを作成して読み込む
      var reader = ContentTransferEncoding.CreateTextReader(stream,
                                                            ContentTransferEncodingMethod.QuotedPrintable,
                                                            Encoding.UTF8);

      Console.WriteLine(reader.ReadToEnd());
    }
  }
}
実行結果
=E6=8B=9D=E5=95=93
=E6=99=82=E4=B8=8B=E3=81=BE=E3=81=99=E3=81=BE=E3=81=99=E3=81=94=E6=B8=85=E6=
=A0=84=E3=81=AE=E3=81=93=E3=81=A8=E3=81=A8=E3=81=8A=E5=96=9C=E3=81=B3=E7=94=
=B3=E3=81=97=E3=81=82=E3=81=92=E3=81=BE=E3=81=99=E3=80=82
=E5=B9=B3=E7=B4=A0=E3=81=AF=E6=A0=BC=E5=88=A5=E3=81=AE=E3=81=8A=E5=BC=95=E3=
=81=8D=E7=AB=8B=E3=81=A6=E3=81=AB=E3=81=82=E3=81=9A=E3=81=8B=E3=82=8A=E3=80=
=81=E3=81=82=E3=82=8A=E3=81=8C=E3=81=9F=E3=81=8F=E5=8E=9A=E3=81=8F=E3=81=8A=
=E7=A4=BC=E7=94=B3=E3=81=97=E3=81=82=E3=81=92=E3=81=BE=E3=81=99=E3=80=82

拝啓
時下ますますご清栄のこととお喜び申しあげます。
平素は格別のお引き立てにあずかり、ありがたく厚くお礼申しあげます。

§2.5 MimeEncodingクラス

§2.5.1 Qエンコードされた文字列のデコード・エンコード (Encode, Decode)

Qエンコードされた文字列のデコード・エンコードを行う例。

using System;
using System.Text;
using Smdn.Formats.Mime;

class Sample {
  static void Main(string[] args) {
    // Qエンコードされた文字列をデコードする
    var text = "=?iso-2022-jp?q?=1B$B%5%s%W%k=1B(B?=";
    var decoded = MimeEncoding.Decode(text);

    Console.WriteLine("[decode]");
    Console.WriteLine(text);
    Console.WriteLine(decoded);
    Console.WriteLine();

    text = decoded;

    // 文字列を文字コードISO-2022-JPでQエンコードする
    var encoded = MimeEncoding.Encode(text, MimeEncodingMethod.QEncoding, Encoding.GetEncoding("ISO-2022-JP"));

    Console.WriteLine("[encode]");
    Console.WriteLine(text);
    Console.WriteLine(encoded);
    Console.WriteLine();
  }
}
実行結果
[decode]
=?iso-2022-jp?q?=1B$B%5%s%W%k=1B(B?=
サンプル

[encode]
サンプル
=?iso-2022-jp?q?=1B$B%5%s%W%k=1B(B?=

MimeEncodingMethod.QEncodingの代わりにMimeEncodingMethod.BEncodingを指定すれば、Bエンコード(BASE64)した文字列を取得出来ます。

§2.6 UUDecoderクラス

§2.6.1 uuencodeされたファイルの抽出 (ExtractFiles)

uuencodeされたファイルをデコードし、抽出したファイルを保存する例。

using System;
using System.IO;

using Smdn.Formats.UUEncoding;

class Sample {
  static void Main(string[] args)
  {
    // uuencodeされたファイル 'uuencoded.txt' を開く
    using (Stream stream = File.OpenRead("uuencoded.txt")) {
      // ストリーム内に含まれる全てのファイルエントリを抽出する
      foreach (UUDecoder.FileEntry fileEntry in UUDecoder.ExtractFiles(stream)) {
        // エンコード時に指定されたファイル名を使い、デコードしたファイルをカレントディレクトリに保存
        fileEntry.Save();

        // メモリ上に展開されているデコード済みのデータを開放
        fileEntry.Dispose();
      }
    }
  }
}

以下のようなコマンドでエンコードされたファイルを読み込んだ場合、カレントディレクトリに foo.zip と bar.zip が保存されます。 なお、現在の実装ではファイルに指定されているパーミッションは無視されます。

uuencode foo.zip bar.zip > uuencoded.txt

§2.6.2 ReadContentメソッドとUUDecoder.ExtractFiles

uuencodeコマンドとmailコマンドを使って送信された、複数のファイルが添付されているメールを解析する例。

uuencode foo.zip bar.zip | mail user@example.net

上記のようにして送信されたメールを読み込んだ場合、Mail.AttachedFileプロパティでは添付されたファイルを参照することはできません。 また、SaveContent(), ReadContentAsText(), ReadContent(Action<BinaryReader>)等のメソッドは、添付されているファイルのうち最初の一つのみしか読み込みません(この例ではfoo.zip)。 添付されている全てのファイルを読み込む必要がある場合は、ReadContent(Action<Stream>)メソッドを使ってデコードされていないストリームを取得し、そのストリームをUUDecoder.ExtractFilesメソッドに渡します。

using System;
using System.IO;

using Smdn.Formats.Mime;
using Smdn.Formats.UUEncoding;

class Sample {
  static void Main(string[] args)
  {
    // uuencodeで添付されたファイルを含むメール 'uuencoded.eml' を開く
    using (Mail mail = Mail.Load("uuencoded.eml")) {
      // 本文を読み込む
      mail.ReadContent(ReadUUencodedContent);
    }
  }

  private static void ReadUUencodedContent(Stream stream)
  {
    // uuencodeでエンコードされたファイルを抽出する
    foreach (UUDecoder.FileEntry fileEntry in UUDecoder.ExtractFiles(stream)) {
      // ファイルの名前・パーミッションを表示
      Console.WriteLine("{0} {1:x}", fileEntry.FileName, fileEntry.Permissions);

      // デコードされたファイルのストリームを使って必要な処理を行う
      fileEntry.Stream.ReadByte();

      // メモリ上に展開されているデコード済みのデータを開放
      fileEntry.Dispose();
    }
  }
}