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

サンプルコードはこのページの最後にあります。

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


§1 用語と表記

このドキュメントでは、以下の表記を用いています。

メッセージ
POPサーバ上のメールボックスにある個々のメールの本文または/および属性を表す意味で用いています。
メッセージ本文
POPサーバ上のメールボックスにある個々のメールの本文のみを表す意味で用いています。
SSL/TLS
SSLおよびTLSを特に区別なく表す場合、SSL/TLSと表記しています。
認証方式
認証に用いるメカニズムを表す意味で用いています。 ほとんどの場合、SASLメカニズムを表す意味で用いていますが、場合によってはPOP標準の平文による認証も含みます。
クライアント
ほとんどの場合、本ライブラリに含まれるクライアント実装を表す意味で用いていますが、場合によっては一般的なPOPクライアントを含みます。
一覧
リスト(IList)・コレクション(ICollection)・その他の集合(IEnumerable, ISet等)を表す意味で用いている場合があります。

上記以外は、RFC 1939および.NET Frameworkの用語に準じます。

§2 ライブラリの構成

§2.1 アセンブリ構成

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

アセンブリに含まれる型と用途
アセンブリ 含まれる型
Smdn.dll 他のアセンブリで共通して使用される型、ユーティリティクラス、.NET Framework 4.x, 3.x互換の型など
Smdn.Core.Standards.dll MIMEエンコード/デコード、URLエンコード/デコード等の標準に関するクラス群
Smdn.Net.MessageAccessProtocols.dll Smdn.Net.Pop3.ClientおよびSmdn.Net.Imap4.Clientで共通して使用されるクラス群
Smdn.Security.Authentication.Sasl.dll Smdn.Security.Authentication.Sasl(SASLクライアントの実装)
Smdn.Net.Pop3.Client.dll PopClient, PopSessionを含むPOP3クライアント実装

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

§2.2 クライアント実装

クライアントの実装は次の2種類があります。

クライアント実装の種類と概要
名前空間 クラス 概要
Smdn.Net.Pop3.Client PopClient
PopMessageInfo
POPの操作を抽象化したクラスです。 メッセージに対する操作をSystem.IO.FileInfoクラスに似たインターフェイスで行えるようにしてあります。 このドキュメントではこれらのクラスについて解説しています。
Smdn.Net.Pop3.Client.Session PopSession POPコマンドと1対1に対応するメソッドを持つクラスです。 POPの操作は抽象化していません。 仕様と1対1で対応するような実装にしてあります。 このクラスは上記のクライアント実装で内部的に使用しています。
直接使用することもできますが、インターフェイスを変更することがあるので推奨はできません。

§3 概略

PopClientクラスはPOPサーバに接続してPOPの操作を行うためのクラスです。 以下の例はPopClientクラスの基本的な機能を使ってPOPの操作を行う例です。 PopClientクラスの詳細については以下で個別に解説していきます。

using System;

using Smdn.Net.Pop3;
using Smdn.Net.Pop3.Client;

class Sample {
  static void Main(string[] args)
  {
    // ユーザ名'user'、CRAM-MD5による認証でpop.example.netに接続するクライアントを作成
    using (PopClient client = new PopClient(new Uri("pop://user;AUTH=CRAM-MD5@pop.example.net/"))) {
      // パスワード'pass'で接続
      client.Connect("pass");

      // メールボックスにある1件目のメッセージをダウンロードしてファイルsample.emlに保存
      client.DownloadFirstMessageToFile("sample.eml");
    }
  }
}

§4 接続・認証とSSL/TLS

PopClientクラスはPOP URLを使った接続に対応しています。 接続時の動作は、基本的にPOP URLの仕様に準じた動作となるようにしています※ただし、popsスキームを使用した場合にSSL/TLSでの接続を試みる動作は、仕様には無い、本ライブラリ独自の拡張です

§4.1 接続と認証

ここでは接続時と認証時の動作について解説します。

§4.1.1 接続・認証のコードと動作の例

以下は接続・認証処理の記述例です。 個々のパラメータの指定方法と動作の詳細についてはこの後順に解説していきます。 なお、実際にSSL/TLSを使用する場合は証明書の検証等が必要になります。

PopClient
/*
 * ホスト"pop.example.net"のPOPSデフォルトポート(995)にSSL/TLSを用いて接続。
 * ユーザ名に"user"、パスワードに"pass"を使用。 認証方式は対応しているものを順に試行。
 */
var client1 = new PopClient(new Uri("pops://user@pop.example.net/"));

client1.Connect("pass");

/*
 * ホスト"pop.example.net"のポート10110に接続、可能ならSSL/TLSにアップグレード。
 * ユーザ名に"user"を使用、パスワードはNetworkCredentialから取得。 認証方式はAPOPを試行。
 */
var client2 = new PopClient(new Uri("pop://user;AUTH=+APOP@pop.example.net:10110/"));

client2.Profile.UseTlsIfAvailable = true;
client2.Connect(new NetworkCredential("user", "pass"));

/*
 * ホスト"pop.example.net"のポート10110にSSL/TLSを用いて接続。
 * ユーザ名に"user"、パスワードに"pass"を使用。 認証方式はDIGEST-MD5を試行。
 */
var client3 = new PopClient("pop.example.net", 10110, true, "user", "DIGEST-MD5");

client3.Connect("pass");

/*
 * ホスト"pop.example.net"のPOPデフォルトポート(110)に接続、SSL/TLSは使用しない。
 * ユーザ名に"user"を使用、パスワードはNetworkCredentialから取得。 認証方式はDIGEST-MD5, CRAM-MD5, APOP, USER/PASSの順に試行。
 */
var client4 = new PopClient("pop.example.net", -1, false, null, "*");

client4.Profile.UseTlsIfAvailable = false;
client4.Profile.UsingSaslMechanisms = new[] {"DIGEST-MD5", "CRAM-MD5"};

client4.Connect(new NetworkCredential("user", "pass"));

/*
 * ホスト"pop.example.net"のPOPデフォルトポート(110)に接続、可能ならSSL/TLSを使用する。
 * ユーザ名に"user"を使用、パスワードはNetworkCredentialから取得。
 * 認証方式はCRAM-MD5, PLAIN, APOP, USER/PASSの順に試行。 ただしSSL/TLSが使用できない場合、平文による認証を許可しない。
 */
var client5 = new PopClient(new Uri("pop://user@pop.example.net/"));

client5.Profile.UseTlsIfAvailable = true;
client5.Profile.UsingSaslMechanisms = new[] {"CRAM-MD5", "PLAIN"};
client5.Profile.AllowInsecureLogin = false;

client5.Connect(new NetworkCredential("user", "pass"));

/*
 * GMailのアカウント"user"にPOPで接続。 パスワードは"pass"を使用。
 */
var client6 = new PopClient(new Uri("pops://user@pop.gmail.com/"));

client6.Connect("pass");

§4.1.2 接続・認証時のパラメータ

接続・認証時のパラメータは、PopClientクラスのコンストラクタまたはPopClient.Profileプロパティで指定します。 指定できるパラメータは次のとおりです。

接続・認証時のパラメータ
PopClient.Profileのプロパティ 対応するPopClientコンストラクタの引数 意味と接続・認証時の動作 補足 詳細
接続に関するパラメータ Authority authority 接続先のURL (POP URL形式) Host, Port, SecurePort, UserName, AuthTypeに指定されているものと同等の値が反映されます §.接続に関するパラメータと動作
Host host 接続先のホスト名 Authorityのホスト名部分に指定されているものと同じ値が反映されます
Port port 接続先のポート番号 Authorityのポート番号部分に指定されているものと同じ値が反映されます
-1を指定した場合は標準のポート番号で接続します(popsなら995、popなら110)
SecurePort securePort 接続先のポートがSSL/TLSで保護されているかどうか
(SSL/TLSを使用して接続する必要があるかどうか)
Authorityに指定されているものと同じ設定が反映されます
スキーム部分がpopsならtrue、popならfalseになります
UseTlsIfAvailable - 可能なら接続後にSSL/TLSへアップグレードするかどうか SecurePortがfalse(スキーム部分がpop)の場合のみ有効
デフォルト:true
認証に関するパラメータ UserName userName 認証時に使用するユーザー名 Authorityのユーザー名部分に指定されているものと同じ値が反映されます §.認証に関するパラメータと動作
AuthType authType 認証時に使用する方式 Authorityの認証方式部分に指定されているものと同じ値が反映されます
UsingSaslMechanisms - 認証時に試行するSASL認証方式 AuthTypeを指定しない(null)、あるいは"*"を指定した場合のみ有効
AllowInsecureLogin - 平文およびAPOPによる安全でない認証を許可するかどうか 現在の接続がSSL/TLSで保護されていない場合のみ有効
保護されている場合は常に平文およびAPOPによる認証を試行します
デフォルト:false
AllowStandardLogin - POP標準のUSER/PASSによる認証を許可するかどうか 現在の接続がSSL/TLSで保護されているかどうかに関わらず有効
デフォルト:true
AllowApopLogin - APOPによる認証を許可するかどうか
PopClient.Profileのプロパティ 対応するPopClientコンストラクタの引数 意味と接続・認証時の動作 補足 詳細

なお、パスワードはPopClient.Connect()など接続用のメソッドの引数として指定します。 Profileプロパティやコンストラクタで指定することはできません。

PopClientコンストラクタで指定した値はPopClient.Profileプロパティに反映されます。 また、PopClient.Profileプロパティはシリアライズ可能なので設定ファイル等に保存されている値を読み込むこともできます。

PopClientProfileを指定してPopClientのインスタンスを作成することもできます。 これにより同じ接続設定を使用して複数のインスタンスを作成することができます。

共通のPopClientProfileからインスタンスを作成する例
// ホスト"pop.example.net"のPOPデフォルトポートに接続し、
// ユーザ名"user"で認証を行う設定を作成
var profile = new PopClientProfile(new Uri("pop://user@pop.example.net/"));

// SSL/TLSが使用できない場合は、平文による認証を許可しない
profile.AllowInsecureLogin = false;

// 上記の設定でクライアントを2つ作成する
var c1 = new PopClient(profile);
var c2 = new PopClient(profile);

c1.Connect("pass");
c2.Connect("pass");

§4.1.3 接続・切断・サーバ情報に関するメンバ

以下は接続・切断・サーバ情報に関するメソッドとプロパティです。

PopClientクラスのメソッド
メソッド 解説 対応するPOPコマンド
Connect(string)
Connect(ICredentialsByHost)
PopClient.Profileプロパティで指定された内容、および引数で指定されたパスワード(もしくはICredentialsByHost)を使って接続・認証を試みます。
接続・認証できた場合は、サーバの能力も取得します。
USER/PASS, STLS, APOP, AUTH, CAPA
BeginConnect(string)
BeginConnect(string, AsyncCallback, object)
BeginConnect(ICredentialsByHost)
BeginConnect(ICredentialsByHost, AsyncCallback, object)
Connect()メソッドと同じ処理を非同期的に実行します。 オプションでコールバックメソッドを指定できます。 USER/PASS, STLS, APOP, AUTH, CAPA
EndConnect(IAsyncResult) BeginConnect()で開始した非同期接続を完了します。 BeginConnect()を呼び出した場合、EndConnect()で操作を完了する必要があります。 -
ConnectAsync(string)
ConnectAsync(ICredentialsByHost)
[.NET 4.0]
Connect()メソッドと同じ処理を非同期的に実行し、非同期操作を表すTaskを返します。
USER/PASS, STLS, APOP, AUTH, CAPA
Logout() ログアウト処理を行った後に接続を切断します。 削除マークが付けられているメッセージは削除されます。 QUIT
Disconnect()
IDisposable.Dispose()
ログアウト処理を行わずに接続を切断します。 削除マークが付けられているメッセージがあっても削除されません。 -
KeepAlive()
KeepAliveAsync()
自動ログアウトタイマの更新、セッションの接続性確認などに使います。 NOOP
PopClientクラスのプロパティ
プロパティ 解説
Profile 接続先のホスト・ポート・ユーザ名などを取得/設定します。 コンストラクタで指定した内容はこのプロパティに反映されます。
Timeout 接続時およびコマンド処理のタイムアウト時間をミリ秒単位で取得/設定します。 Timeout.Infinite(-1)を指定した場合はタイムアウトしません。
SendTimeout ソケット送信時(コマンド送信)のタイムアウト時間をミリ秒単位で取得/設定します。 Timeout.Infinite(-1)を指定した場合はタイムアウトしません。
ReceiveTimeout ソケット受信時(レスポンス受信)のタイムアウト時間をミリ秒単位で取得/設定します。 Timeout.Infinite(-1)を指定した場合はタイムアウトしません。
IsConnected 接続中かどうかを表す値を取得します。
IsBusy 操作が進行中かどうかを表す値を取得します。 trueの場合、同一クライアントから他の操作を開始することはできません。
ServerCapabilities サーバがサポートする機能の一覧(CAPAコマンドの結果)を取得します。 サーバがCAPAコマンドをサポートしていない場合は、空のインスタンスを返します。
IDisposable.Dispose()
PopClientクラスはIDisposableインターフェイスを実装しています。 IDisposable.Dispose()を呼び出した場合、削除マークが付けられているメッセージを削除せずに切断します。 usingステートメントを使って記述する場合でメッセージを削除したい場合は、切断する前にLogout()メソッド呼び出すようにしてください。
Logout(), Disconnect(), IDisposable.Dispose()
これらのメソッドのいずれかを呼び出した後でも、再度PopClient.Connect()を呼び出すことで同じインスタンスを使って再度接続することはできます。
Timeout, SendTimeout, ReceiveTimeout
これらのプロパティはそれぞれPopClient.Profile.Timeout, PopClient.Profile.SendTimeout, PopClient.Profile.RecieveTimeoutを設定することと同じですが、接続後はPopClient.Profileの値を変更してもPopClientの動作には反映されません。 接続後はPopClient.Timeout, PopClient.SendTimeout, PopClient.ReceiveTimeoutプロパティを設定してください。
操作のタイムアウトと中断に関しての注意点は§.非同期操作の中断とタイムアウトを参照してください。
ServerCapabilities
切断されている状態で取得しようとした場合(PopClient.IsConnectedがfalseの場合)、例外をスローします。

§4.1.4 接続に関するパラメータと動作

接続時の動作は、接続時のパラメータにより次のように変わります。

SSL/TLSを使用する (POP over SSL)
常にSSL/TLSを使用して接続を試みます。 デフォルトのポート番号は995です。
接続先URL*1のスキームに"pops"を指定した場合、SecurePortプロパティ*2trueを指定した場合はこの動作になります。
可能ならSSL/TLSを使用する (STLS)
SSL/TLSを使用せず接続を試み、サーバがSTLSをサポートしていれば認証を開始する前にSSL/TLSへのアップグレードを試みます。 デフォルトのポート番号は110です。
接続先URL*1のスキームに"pop"を指定した場合、SecurePortプロパティ*2falseを指定した場合で、UseTlsIfAvailableプロパティ*3trueを指定した場合はこの動作になります。
SSL/TLSを使用しない
常にSSL/TLSを使用せずに接続を試みます。 サーバがSTLSをサポートしていてもSSL/TLSへのアップグレードはしません。 デフォルトのポート番号は110です。
接続先URL*1のスキームに"pop"を指定した場合、SecurePortプロパティ*2falseを指定した場合で、UseTlsIfAvailableプロパティ*3falseを指定した場合はこの動作になります。

これら接続時のパラメータは次の箇所で指定します。

接続先URL*1
PopClientクラスのコンストラクタで指定する引数authority
SecurePortプロパティ*2
PopClientクラスのコンストラクタで指定する引数securePort、もしくはPopClient.Profile.SecurePortプロパティ
UseTlsIfAvailableプロパティ*3
PopClient.Profile.UseTlsIfAvailableプロパティ

接続先URLもしくはパラメータでポート番号を明示的に指定しない限り、デフォルトポートへの接続を試みます。 なお、ポート番号の指定ではSSL/TLSを使用するかどうかの動作は変わりません※ポート番号に995を指定してもURLのスキームが"pop"なら、接続時にSSL/TLSは使用しません

例として接続先のURLと接続時の動作を表にまとめると以下のようになります。

URLと接続動作の例
接続先URL 接続ポート SSL/TLS
pops://user@pop.example.net/ 995 SSL/TLSを使用して接続
pops://user@pop.example.net:10110/ 10110
pop://user@pop.example.net/ 110 SSL/TLSを使用しないで接続
もしくは可能なら接続後にSSL/TLSへアップグレード(UseTlsIfAvailable=trueの場合)
pop://user@pop.example.net:995/ 995

§4.1.5 認証に関するパラメータと動作

接続にPOP URLを用いる場合、認証に用いるユーザ名と認証方式はURLから取得します。 パスワードはPopClient.Connect()メソッドに指定する引数credentials(ICredentialsByHostインターフェイス)を参照し、接続しようとしているホスト名・ポート番号および指定された認証メカニズムをもとに適切なものを取得します。 POP URLではFTPやHTTPのURLとは異なり、URLに平文パスワードを含めることが許可されていないので、URLからはパスワードを取得しません(指定されていてもパスワードとしては解釈しません)。

認証方式にAPOPを使用する場合は、"APOP"ではなく"+APOP"を指定してください。 認証方式を指定しない場合、もしくは"*"が指定されている場合は次の順で認証を試行します。

  1. AUTHコマンド (サーバ・クライアントが対応している認証メカニズムを順に試行)
  2. APOPコマンド (サーバがサポートしている場合のみ)
  3. USER/PASSコマンド

URLもしくはパラメータでユーザ名・認証メカニズムの両方とも指定されていない場合は、ANONYMOUS認証を行います。 認証方式にANONYMOUSを指定して認証を行う場合、ユーザ名の部分をログイントークンとして送信します。 ユーザ名が指定されていない場合は"anonymous@"をログイントークンとして送信します。 ログイントークンに@などの記号を含める場合はURLエスケープする必要があります。

認証方式の大文字小文字の違いは無視します(POP URLの場合は;AUTH=の部分も大文字小文字を無視します)。

例として接続先のURLと認証時の動作を表にまとめると以下のようになります。

URLと認証動作の例
接続先URL 認証に使用するユーザ名 使用する認証メカニズム
pop://user;AUTH=+APOP@pop.example.net/ user APOP
pop://user;AUTH=DIGEST-MD5@pop.example.net/ user DIGEST-MD5
pop://;AUTH=DIGEST-MD5@pop.example.net/ ICredentialsByHostインターフェイスより取得 DIGEST-MD5
pop://user;AUTH=*@pop.example.net/ user サーバ・クライアントが対応しているものを順に試行
pop://user@pop.example.net/ user サーバ・クライアントが対応しているものを順に試行
pop://;AUTH=ANONYMOUS@pop.example.net/ 匿名ユーザ
(ログイントークン: anonymous@)
ANONYMOUS
pop://user@localhost;AUTH=ANONYMOUS@pop.example.net/ 匿名ユーザ
(ログイントークン: user@localhost)
ANONYMOUS
pop://pop.example.net/ 匿名ユーザ
(ログイントークン: anonymous@)
ANONYMOUSもしくはUSER/PASSコマンドを使用

試行する認証メカニズムを制御するには、PopClient.Profile.UsingSaslMechanismsプロパティの値を変更してください。 UsingSaslMechanismsプロパティにANONYMOUSが含まれていても匿名ログインは試行しません。

PopClient.Profile.AllowInsecureLoginプロパティにfalseを指定した場合で、かつ現在の接続がSSL/TLSで保護されていない場合、平文およびAPOPによる認証は試行されません(デフォルトはfalseです)。

サーバが対応している認証方式と認証試行順の例
サーバが対応している認証方式 UsingSaslMechanismsの値 認証の試行順序
接続がSSL/TLSで保護されている
もしくはAllowInsecureLoginがtrueの場合
接続がSSL/TLSで保護されていない
かつAllowInsecureLoginがfalseの場合
DIGEST-MD5
CRAM-MD5
APOP
USER/PASS
{"DIGEST-MD5", "CRAM-MD5"} 1.DIGEST-MD5
2.CRAM-MD5
3.APOP
4.USER/PASS
1.DIGEST-MD5
2.CRAM-MD5
DIGEST-MD5
CRAM-MD5
PLAIN
{"PLAIN", "DIGEST-MD5"} 1.PLAIN
2.DIGEST-MD5
1.DIGEST-MD5
PLAIN
APOP
USER/PASS
null 1.APOP
2.USER/PASS
PopNoAppropriateAuthMechanismExceptionをスロー
(試行できる認証方式なし)

APOPを使用した認証については脆弱性が指摘されているため、本ライブラリではAPOPは平文による認証と同程度のものとして扱います。 参考:情報処理推進機構:情報セキュリティ:脆弱性関連情報取扱い:APOP方式におけるセキュリティ上の弱点(脆弱性)の注意喚起について

各認証方式を個別に無効にする・試行しないようにする場合は、次のように各プロパティの値を設定してください。 PopClient.Profile.AllowInsecureLoginプロパティとは異なり、これらの設定は現在の接続がSSL/TLSで保護されているかどうかに関わらず各認証方式を無効にします。

認証方式を無効にする設定
無効にする認証方式 設定するプロパティ 設定する値
SASL
(CRAM-MD5, PLAINなど)
PopClient.Profile.UsingSaslMechanisms nullあるいは空の配列
USER/PASS PopClient.Profile.AllowStandardLogin false
APOP PopClient.Profile.AllowApopLogin false

§4.1.6 認証時に参照する資格情報

認証時に必要なユーザ名・パスワードはSystem.Net.ICredentialsByHostインターフェイスを通して取得します。 ICredentialsインターフェイスではなく、ICredentialsByHostインターフェイスを実装していて、GetCredentialメソッドが適切なSystem.Net.NetworkCredentialを返すクラスなら何でも設定できます。

§4.2 SSL/TLSを使用した接続

§4.2.1 証明書の選択と検証

SSL/TLS接続時に使用する証明書はX509Certificate2Collectionで設定できます。 また、証明書の選択と検証にはRemoteCertificateValidationCallbackデリゲートLocalCertificateSelectionCallbackデリゲートを指定できます。 デフォルトの状態では、SSL/TLS接続時にこれらのコールバックメソッドを呼び出して証明書の検証と選択を行い、SslStreamを作成します。

デフォルトでは、接続・認証時にPopSslConnectionクラス(Smdn.Net.Pop3.Client名前空間)の以下のメンバを参照して証明書の選択と検証を行います。

SSL/TLS接続時に参照するメンバ(いずれもクラスプロパティ)
PopClientクラスが参照するメンバ
X509Certificate2Collection PopConnection.ClientCertificates
RemoteCertificateValidationCallback PopConnection.ServerCertificateValidationCallback
LocalCertificateSelectionCallback PopConnection.ClientCertificateSelectionCallback

以下は証明書の検証を行う簡単な例です。

証明書の検証を行う例
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Smdn.Net.Pop3.Client;

public class ValidateServerCerts {
  static void Main(string[] args)
  {
    // 証明書の検証を行うコールバックメソッドを指定
    PopSslConnection.ServerCertificateValidationCallback += ValidateRemoteCertificate;

    using (var client = new PopClient(new Uri("pops://user@localhost/"))) {
      client.Connect("pass");
    }
  }

  private static bool ValidateRemoteCertificate(object sender,
                                                X509Certificate certificate,
                                                X509Chain chain,
                                                SslPolicyErrors sslPolicyErrors)
  {
#if DEBUG
    // デバッグ時のみSslPolicyErrors.RemoteCertificateNameMismatchを無視
    if ((int)(sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
      sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch;
#endif

    if (sslPolicyErrors == SslPolicyErrors.None) {
      return true;
    }
    else {
      // エラーがあれば標準エラーに表示
      Console.Error.WriteLine(sslPolicyErrors);
      return false;
    }
  }
}

SSL/TLS接続時の動作をデフォルトからカスタマイズしたい場合は、PopClient.Connect()メソッドの引数に適切なコールバックメソッドを指定してください。
(このドキュメントは作成中です)

§4.2.2 SSL/TLS接続のカスタマイズ

(このドキュメントは作成中です)
SslStream以外の実装を使いたい場合や、より高度な検証が必要な場合など、SSL/TLS接続時にデフォルトの動作を変更してカスタマイズする場合は、UpgradeConnectionStreamCallbackデリゲートを使用してコールバックメソッドを指定してください。
コールバックメソッドはPopClient.Connect()メソッドの引数に指定してください。 実装例はPopSslConnection.CreateSslStreamメソッドを参照してください。


§5 POPの操作

§5.1 クラスと機能

Smdn.Net.Pop3.Client名前空間のクラスを使ったPOPの操作は、それぞれ以下のクラスのインスタンスを用いて行います。

クラスと機能
クラス 機能
PopClient クライアント本体およびメールボックスを表すクラス。
PopMessageInfo 単一のメッセージを表すクラス。

PopMessageInfoクラスのインスタンスは直接作成することはできません。 PopClient.GetMessage()などのメソッドを呼び出すことで取得できます。

次の例は上記のクラスを使ってメッセージの操作を行う一例です。

using System;

using Smdn.Net.Pop3;
using Smdn.Net.Pop3.Client;

class Sample {
  static void Main(string[] args)
  {
    using (PopClient client = new PopClient(new Uri("pop://user@localhost/"))) {
      client.Connect("pass");

      // メールボックスにあるすべてのメッセージとIDを取得
      foreach (PopMessageInfo message in client.GetMessages(true)) {
        // メッセージをダウンロードし、IDをファイル名として保存
        message.Save(message.UniqueId + ".eml");
        // 保存できたら削除マークを付ける
        message.MarkAsDeleted();
      }

      // ログアウトしてメッセージを削除
      client.Logout();
    }
  }
}

以下でPopClientと関連するクラスの使い方について解説します。

§5.2 メッセージの取得

以下はメールボックスにあるメッセージを取得するメソッドの一覧です。

PopClientクラスのメソッド
メソッド 解説 対応するPOPコマンド
GetMessage(long)
GetMessage(long, bool)
GetMessageAsync(long)
GetMessageAsync(long, bool)
およびオーバーロード
指定された番号のメッセージを取得します。 オプションでメッセージのIDも取得します。 最初の番号は1です。 LIST, UIDL
GetMessage(string)
GetMessageAsync(string)
およびオーバーロード
指定されたIDを持つメッセージを取得します。 LIST, UIDL
GetFirstMessage()
GetFirstMessage(bool)
GetFirstMessageAsync()
GetFirstMessageAsync(bool)
およびオーバーロード
メールボックスにある最初のメッセージを取得します。 オプションでメッセージのIDも取得します。
このメソッドを呼び出すことは、メッセージ番号に1を指定してGetMessage()を呼び出すこと同じです。
LIST, UIDL
GetLastMessage()
GetLastMessage(bool)
GetLastMessageAsync()
GetLastMessageAsync(bool)
およびオーバーロード
メールボックスにある最後のメッセージを取得します。 オプションでメッセージのIDも取得します。
このメソッドを呼び出すことは、メッセージ番号にPopClient.MessageCountを指定してGetMessage()を呼び出すこと同じです。
LIST, UIDL
GetMessages()
GetMessages(bool)
GetMessagesAsync()
GetMessagesAsync(bool)
およびオーバーロード
メールボックスにあるすべてのメッセージを取得します。 オプションでメッセージのIDも取得します。 LIST, UIDL
  • これらのメソッドは、接続してから切断するまでの間、同じ番号のメッセージに対しては常に同じPopMessageInfoインスタンスを返します。
  • これらのメソッドが返すPopMessageInfoインスタンスは、現在の接続の間のみ有効です。 切断した後にインスタンスのメンバを参照しようとした場合、例外をスローします。 また再接続しても有効にはならないので、再接続後にメソッドを呼び出して新たにインスタンスを取得しなおしてください。
  • これらのメソッドを呼び出し、PopMessageInfoインスタンスを取得した時点ではメッセージの本文は取得しません。 メッセージ本文を取得するには、取得したPopMessageInfoインスタンスに対してOpen*(), Read*(), Save()などのメソッドを呼び出す必要があります。
  • これらのメソッドを呼び出す際、該当するメッセージ存在しない場合はPopMessageNotFoundException、削除マークが付けられている場合はPopMessageDeletedExceptionをスローします。
  • メールボックスにあるメッセージが0件の場合、PopClient.GetFirstMessage(), GetLastMessage()はPopMessageNotFoundExceptionをスローします。
  • メールボックスにあるメッセージが0件の場合でも、PopClient.GetMessages()は例外をスローせず空のIEnumerable<PopMessageInfo>を返します。

§5.3 メッセージ本文の取得

以下はメッセージ本文の取得に関するメソッド・プロパティの一覧です。

PopMessageInfoクラスのメソッド
メソッド 解説 対応するPOPコマンド
OpenRead()
OpenReadAsync()
およびオーバーロード
メッセージ本文を読み込むためのStreamを取得します。 RETRまたはTOP
OpenText()
OpenTextAsync()
およびオーバーロード
メッセージ本文を読み込むためのStreamReaderを取得します。 RETRまたはTOP
ReadAllBytes()
ReadAllBytesAsync()
およびオーバーロード
メッセージ本文をbyte[]で取得します。 RETRまたはTOP
ReadAllLines()
ReadAllLinesAsync()
およびオーバーロード
メッセージ本文をstring[]で取得します。 RETRまたはTOP
ReadAllText()
ReadAllTextAsync()
およびオーバーロード
メッセージ本文をstringで取得します。 RETRまたはTOP
ReadLines()
およびオーバーロード
メッセージ本文をIEnumerable<string>で取得します。 RETRまたはTOP
ReadAs<TResult>(Converter<Stream, TResult>)
ReadAsAsync<TResult>(Converter<Stream, TResult>)
およびオーバーロード
メッセージ本文を読み込むためのStreamを取得し、指定されたConverter<Stream, TResult>で変換された結果を取得します。 RETRまたはTOP
ReadAs<T, TResult>
(Func<Stream, T, TResult>, T)
ReadAs<T1, T2, TResult>
(Func<Stream, T1, T2,TResult>, T1, T2)
ReadAs<T1, T2, T3, TResult>
(Func<Stream, T1, T2, T3, TResult>, T1, T2, T3)
ReadAs<T1, T2, T3, T4, TResult>
(Func<Stream, T1, T2, T3, T4, TResult>, T1, T2, T3, T4)
およびオーバーロード
メッセージ本文を読み込むためのStreamを取得し、指定されたFunc<Stream, T, TResult>~Func<Stream, T1, T2, T3, T4, TResult>で変換された結果を取得します。 RETRまたはTOP
ReadAs<TResult>(Converter<StreamReader, TResult>)
ReadAsAsync<TResult>(Converter<StreamReader, TResult>)
およびオーバーロード
メッセージ本文を読み込むためのStreamReaderを取得し、指定されたConverter<StreamReader, TResult>で変換された結果を取得します。 RETRまたはTOP
Save(string)
SaveAsync(string)
およびオーバーロード
メッセージ本文を指定されたファイルに保存します。 RETRまたはTOP
WriteTo(Stream)
WriteToAsync(Stream)
およびオーバーロード
メッセージ本文を指定されたStreamに書き込みます。 RETRまたはTOP
WriteTo(BinaryWriter)
WriteToAsync(BinaryWriter)
およびオーバーロード
メッセージ本文を指定されたBinaryWriterに書き込みます。 RETRまたはTOP
PopClientクラスのメソッド
メソッド 解説 対応するPOPコマンド
DownloadMessageAsText(long, Encoding)
DownloadMessageAsTextAsync(long, Encoding)
(指定された番号の/最初の/最後の/すべての)メッセージの本文をstringで取得します。
PopClient.GetMessage()等とPopMessageInfo.ReadAllText(Encoding)の呼び出しの組み合わせと同等です。
RETR
DownloadFirstMessageAsText(Encoding)
DownloadFirstMessageAsTextAsync(Encoding)
DownloadLastMessageAsText(Encoding)
DownloadLastMessageAsTextAsync(Encoding)
DownloadAllMessagesAsText(Encoding)
DownloadAllMessagesAsTextAsync(Encoding)
DownloadMessageAsByteArray(long)
DownloadMessageAsByteArrayAsync(long)
(指定された番号の/最初の/最後の)メッセージの本文をbyte[]で取得します。
PopClient.GetMessage()等とPopMessageInfo.ReadAllBytes()の呼び出しの組み合わせと同等です。
DownloadFirstMessageAsByteArray()
DownloadFirstMessageAsByteArrayAsync()
DownloadLastMessageAsByteArray()
DownloadLastMessageAsByteArrayAsync()
DownloadMessageAsStream(long)
DownloadMessageAsStreamAsync(long)
(指定された番号の/最初の/最後の)メッセージの本文を読み込むためのStreamを取得します。
PopClient.GetMessage()等とPopMessageInfo.OpenRead()の呼び出しの組み合わせと同等です。
DownloadFirstMessageAsStream()
DownloadFirstMessageAsStreamAsync()
DownloadLastMessageAsStream()
DownloadLastMessageAsStreamAsync()
DownloadMessageAs<TResult>(long, Converter<Stream, TResult>)
DownloadMessageAsAsync<TResult>(long, Converter<Stream, TResult>)
(指定された番号の/最初の/最後の/すべての)メッセージの本文を読み込むためのStreamを取得し、指定されたConverter<Stream, TResult>で変換された結果を取得します。
PopClient.GetMessage()等とPopMessageInfo.ReadAs<TResult>(Converter<Stream, TResult>)の呼び出しの組み合わせと同等です。
DownloadFirstMessageAs<TResult>(Converter<Stream, TResult>)
DownloadFirstMessageAsAsync<TResult>(Converter<Stream, TResult>)
DownloadLastMessageAs<TResult>(Converter<Stream, TResult>)
DownloadLastMessageAsAsync<TResult>(Converter<Stream, TResult>)
DownloadAllMessageAs<TResult>(Converter<Stream, TResult>)
DownloadAllMessageAsAsync<TResult>(Converter<Stream, TResult>)
DownloadMessageAs<T, TResult>(long, Func<Stream, T, TResult>, T)
DownloadMessageAsAsync<T, TResult>(long, Func<Stream, T, TResult>, T)
(指定された番号の/最初の/最後の/すべての)メッセージの本文を読み込むためのStreamを取得し、指定されたFunc<Stream, T, TResult>で変換された結果を取得します。
PopClient.GetMessage()等とPopMessageInfo.ReadAs<T, TResult>(Func<Stream, T, TResult>, T)の呼び出しの組み合わせと同等です。
DownloadFirstMessageAs<T, TResult>(Func<Stream, T, TResult>, T)
DownloadFirstMessageAsAsync<T, TResult>(Func<Stream, T, TResult>, T)
DownloadLastMessageAs<T, TResult>(Func<Stream, T, TResult>, T)
DownloadLastMessageAsAsync<T, TResult>(Func<Stream, T, TResult>, T)
DownloadAllMessageAs<T, TResult>(Func<Stream, T, TResult>, T)
DownloadAllMessageAsAsync<T, TResult>(Func<Stream, T, TResult>, T)
DownloadMessageToFile(long, string)
DownloadMessageToFileAsync(long, string)
(指定された番号の/最初の/最後の)メッセージの本文を指定されたファイルに保存します。
PopClient.GetMessage()等とPopMessageInfo.Save(string)の呼び出しの組み合わせと同等です。
DownloadFirstMessageToFile(string)
DownloadFirstMessageToFileAsync(string)
DownloadLastMessageToFile(string)
DownloadLastMessageToFileAsync(string)
DownloadAllMessagesToDirectory(string)
DownloadAllMessagesToDirectoryAsync(string)
すべてのメッセージの本文を指定されたディレクトリに保存します。 個々のメッセージは "(メッセージ番号).eml" のファイル名で保存されます。
PopClient.GetMessage()等とPopMessageInfo.Save(string)の呼び出しの組み合わせと同等です。
DownloadAllMessagesToFiles(Func<PopMessageInfo, string>)
DownloadAllMessagesToFilesAsync(Func<PopMessageInfo, string>)
すべてのメッセージの本文を指定されたデリゲートが返すファイル名で保存します。
PopClient.GetMessage()等とPopMessageInfo.Save(string)の呼び出しの組み合わせと同等です。
PopClientクラスのプロパティ
プロパティ 解説
DeleteAfterRetrieve PopMessageInfoクラスでメッセージ本文を取得する際、正常に取得できたらメッセージを削除マーク済みにするかどうかを取得/設定します。 このプロパティの値は、上記のメソッドの動作を変更します。
  • PopClient.DeleteAfterRetrieveプロパティにtrueが指定されている場合、メッセージの取得に成功した時点で自動的にMarkAsDeleted()メソッドが呼び出され、メッセージは削除マーク済みにされます。
  • すべてのメソッドは、既定ではRETRコマンドを用いてメッセージ本文全体を取得します。
    maxLinesを引数に取るバージョンのオーバーロードを使用することで、TOPコマンドを用いてメッセージ本文のうちヘッダ部分とボディ部分の指定した行数のみを取得することもできます。
  • すべてのメソッドは、取得したメッセージ本文をキャッシュしません。 メソッド呼び出しの度にメッセージ本文のダウンロードを行います。 同じメッセージを何度も処理する必要がある場合は、OpenRead()メソッドで取得したStreamを先頭にシークしなおして使用するか、Save()メソッドでファイルに保存するなどしてください。
  • OpenReadAsync()など名前がAsyncで終わるメソッドは非同期バージョンのメソッドです。 非同期的に動作する以外は同期バージョンと同じです。 非同期バージョンのメソッドを使う際の注意点は§.非同期操作を参照してください。
  • インスタンスを作成したPopClientを切断した後にこれらのメソッド・プロパティを呼び出そうとした場合、PopProtocolViolationExceptionをスローします。
  • OpenText(), ReadAllLines()などのメソッドは、既定ではstringへのデコードにISO-8859-1を使用します。
    Encodingを引数に取るバージョンのオーバーロードを使用することで、指定したエンコーディングでデコードすることもできます。
    これらのメソッドはContent-Typeヘッダのcharsetパラメータや、Content-Transfer-Encodingヘッダなどに指定されている値は考慮せずにデコードを行います。 MIMEメッセージとしてパース・デコードするには、OpenRead()やReadAllBytes()などのメソッドで本文を取得した後、Smdn.Formats.Mimeに渡してデコードするなど、別途適切な処理を施してください。
  • DownloadAllMessagesToFiles()メソッドは保存先のディレクトリを作成しません。 指定されたパスのディレクトリが存在しない場合例外をスローするので、あらかじめディレクトリを作成するか、既存のディレクトリを指定してください。
  • DownloadAllMessagesToDirectory()メソッドは、保存先のディレクトリが存在しない場合、ディレクトリを作成してから保存を行います。

§5.4 メールボックスに対する操作

以下はメールボックス操作に関するメソッドです。

PopClientクラスのメソッド
メソッド 解説 対応するPOPコマンド
CancelDelete()
CancelDeleteAsync()
すべてのメッセージに対して削除マークを元に戻し、削除要求をキャンセルします。 PopMessageInfo.MarkAsDeleted()で行った操作がキャンセルされます。 RSET

§5.5 メールボックスの状態の取得

以下はメールボックスの状態を表すプロパティです。

PopClientクラスのメソッド
メソッド 解説 対応するPOPコマンド
UpdateStatus()
UpdateStatusAsync()
メールボックスの状態を取得してMessageCountおよびTotalSizeプロパティの値を最新の状態に更新します。 STAT
PopClientクラスのプロパティ
プロパティ 解説
MessageCount メールボックスに存在するメッセージの数を取得します。
TotalSize メールボックスに存在するメッセージの総サイズをバイト単位で取得します。
MessageCount, TotalSize
このプロパティは、値が未取得の場合、もしくはPopClient.CancelDelete()やPopMessageInfo.MarkAsDeleted()で状態が更新されている場合、STATコマンドを発行して最新の値を取得します。 また、切断されている状態で取得しようとした場合(PopClient.IsConnectedがfalseの場合)、例外をスローします。
UpdateStatus()メソッドを呼び出して最新の値を取得した直後はMessageCountおよびTotalSizeの値が最新となるため、プロパティを参照してもコマンドは発行されません。

§5.6 メッセージに対する操作

以下はメッセージの操作に関するメソッドです。

PopMessageInfoクラスのメソッド
メソッド 解説 対応するPOPコマンド
MarkAsDeleted()
MarkAsDeletedAsync()
メッセージを削除マーク済みにします。 すでに削除マーク済み(IsMarkedAsDeletedがtrue)の場合は何もしません。 DELE
GetUniqueId()
GetUniqueIdAsync()
メッセージのIDを取得します。 未取得の場合、コマンドを発行してから結果を返します。 取得できない場合は例外をスローします。 UIDL
TryGetUniqueId(out string) メッセージのIDの取得を試みます。 未取得の場合、コマンドを発行してから結果を返します。 取得できない場合は戻り値falseを返し、outパラメータにはnullがセットされます。 UIDL

§5.6.1 GetUniqueId()、TryGetUniqueId()メソッドとUniqueIdプロパティの動作

PopMessageInfoクラスには、メッセージのIDを取得するためのメンバとして以下の三つを用意しています。 いずれもPopClient.GetMessage()等のメソッドでIDを取得するように指定した場合は取得済みのIDを返しますが、取得していない場合はコマンドを発行してから結果を返します。 各メンバの動作の違いは次のとおりです。

メッセージのIDを取得するためのメンバと動作の違い
メンバ IDを取得済みの場合 IDの取得に成功した場合 IDの取得に失敗した場合
UniqueIdプロパティ 取得済みのIDを返す 取得したIDを返す nullを返す
GetUniqueId()メソッド 取得済みのIDを返す 取得したIDを返す PopIncapableExceptionをスローする
TryGetUniqueId()メソッド trueを返す
(outパラメータには取得済みのIDをセット)
trueを返す
(outパラメータには取得したIDをセット)
falseを返す
(outパラメータにはnullをセット)

なお、いずれのメンバも、接続が切断されている場合や、既にメッセージを削除マーク済みにしている場合などにはコマンドを発行する前に例外をスローします。

§5.7 メッセージの状態の取得

以下はメッセージの状態を表すプロパティです。

PopMessageInfoクラスのプロパティ
プロパティ 解説
IsMarkedAsDeleted メッセージが削除マーク済みかどうかを表す値を取得します。
MessageNumber メッセージの番号を取得します。
Length メッセージのサイズをバイト単位で取得します。
UniqueId メッセージのID(UIDLコマンドの結果)を取得します。
  • PopClient.GetMessage()等のメソッドでIDを取得するように指定しなかった場合、UniqueIDプロパティの値を参照する際、コマンドを発行してから結果を返します。
  • インスタンスを作成したPopClientを切断した後にこれらのメソッド・プロパティの値を参照しようとした場合、PopProtocolViolationExceptionをスローします。

§5.8 POP操作を行う静的メソッド

PopClientクラスではいくつかの静的メソッドを用意しています。 これらのメソッドはサーバへの接続・メッセージ本文の取得などの操作・切断および後処理を一度のメソッド呼び出しで行えるようにしたものです。 これらのメソッドでは呼び出しの度にサーバへの接続・切断が行われる点に注意してください。

これらのメソッドは、いずれも共通して以下の引数を取ります。

第一引数 popUrl (Uri)
接続先サーバをPOP URL形式で指定します。 ユーザ名・認証方式を指定する場合は、このURLに含めてください。
第二引数 password (string)
接続時のパスワードを指定します。
第三引数 deleteAfterDownload (bool)
(メッセージ本文の取得を行うメソッドのみ) メッセージ本文が正常に取得できた場合、メッセージを削除するかどうかを指定します。

以下はPopClientクラスに用意されている静的メソッドの一覧です。 具体的な動作については、対応する各インスタンスメンバの解説を参照してください。

PopClientクラスの静的メソッド
メソッド 解説 対応するインスタンスメンバ
GetMessageCount メールボックスに存在するメッセージの数を取得します。 PopClient.MessageCount
DownloadFirstMessageAsText
DownloadLastMessageAsText
DownloadAllMessagesAsText
(最初の/最後の/すべての)メッセージの本文をstringで取得します。 PopClient.DownloadFirstMessageAsText()
PopClient.DownloadLastMessageAsText()
PopClient.DownloadAllMessageAsText()
DownloadFirstMessageAsByteArray
DownloadLastMessageAsByteArray
(最初の/最後の)メッセージの本文をbyte[]で取得します。 PopClient.DownloadFirstMessageAsByteArray()
PopClient.DownloadLastMessageAsByteArray()
DownloadFirstMessageAsStream
DownloadLastMessageAsStream
(最初の/最後の)メッセージの本文を読み込むためのStreamを取得します。 PopClient.DownloadFirstMessageAsStream()
PopClient.DownloadLastMessageAsStream()
DownloadFirstMessageAs<TResult>
DownloadLastMessageAs<TResult>
DownloadAllMessagesAs<TResult>
(最初の/最後の/すべての)メッセージの本文を読み込むためのStreamを取得し、指定されたConverter<Stream, TResult>で変換された結果を取得します。 PopClient.DownloadFirstMessageAs<TResult>()
PopClient.DownloadLastMessageAs<TResult>()
PopClient.DownloadAllMessagesAs<TResult>()
DownloadFirstMessageAs<T, TResult>
DownloadLastMessageAs<T, TResult>
DownloadAllMessagesAs<T, TResult>
(最初の/最後の/すべての)メッセージの本文を読み込むためのStreamを取得し、指定されたFunc<Stream, T, TResult>で変換された結果を取得します。 PopClient.DownloadFirstMessageAs<T, TResult>()
PopClient.DownloadLastMessageAs<T, TResult>()
PopClient.DownloadAllMessagesAs<T, TResult>()
DownloadFirstMessageToFile
DownloadLastMessageToFile
(最初の/最後の)メッセージの本文を指定されたファイルに保存します。 PopClient.DownloadFirstMessageToFile()
PopClient.DownloadLastMessageToFile()
DownloadAllMessagesToDirectory
DownloadAllMessagesToFiles
すべてのメッセージの本文を指定されたディレクトリ、もしくはデリゲートが返すファイル名で保存します。
なお、deleteAfterDownloadにtrueを指定した場合、すべてのメッセージ本文の取得に成功した場合のみメッセージの削除が行われます。 (途中で失敗した場合は、メッセージは削除されません)
PopClient.DownloadAllMessagesToDirectory()
PopClient.DownloadAllMessagesToFiles()

§6 非同期操作

本ライブラリではPopClient.BeginConnect()/EndConnect()、PopClient.ConnectAsync()や、PopMessageInfo.OpenReadAsync()、SaveAsync()など非同期バージョンのメソッドを用意しています。

これらのうち、Asyncで終わる名前のメソッドは.NET Framework 4.0以降でのみ使用できるもので、いずれもTaskまたはTask<T>を返します。

(現時点では非同期操作の実装は不完全です。 特に複数の操作を並列して進行させようとした場合の動作については今後のバージョンで変更する可能性があります。)

§6.1 非同期操作の制限

PopClientおよび関連クラスは並列操作をサポートしません。 常に1クライアントにつき同時に1操作のみ行えます。 この制限は非同期バージョンのメソッドを使った場合も同様です。 非同期バージョンのメソッドは非同期操作のみをサポートします。 同期操作・非同期操作に関わらず、何らかの操作が進行中の場合は同一のクライアントから別の操作を開始することはできません。 操作が進行中の場合、PopClient.IsBusyプロパティはtrueを返します。 この状態で別の操作を開始しようとした場合にはInvalidOperationExceptionがスローされます。

例えば、次のような操作は正しく動作する保証はありません。

メッセージの取得を並列して行う
var client = new PopClient(...);

var m1 = client.GetMessage(1);
var m2 = client.GetMessage(2);

// 2つのメッセージの本文の取得を並列して開始する
var t1 = m1.ReadAllBytesAsync();
var t2 = m2.ReadAllBytesAsync(); // タスクが進行中の場合は他の処理を開始できず、例外をスローする場合がある

Task.WaitAll(t1, t2);
メッセージの取得中に他の処理を行う
var client = new PopClient(...);

var m1 = client.GetMessage(1);
var t1 = m1.ReadAllBytesAsync();

// 本文の取得が開始している状態で別の処理(メッセージの取得)を行う
var m2 = client.GetMessage(2); // タスクが進行中の場合は他の処理を開始できず、例外をスローする場合がある

t1.Wait();

並列操作を行いたい場合は、PopClientのインスタンスを並列数分だけ用意してください。 例えば、次のような操作は正しく動作します。

別々のクライアントを用意してメッセージ本文の取得を並列して行う
var client1 = new PopClient(...);
var client2 = new PopClient(...);

// 別々のクライアントからメッセージを取得し、そのそれぞれで並列して本文を取得する
var m1 = client1.GetMessage(1);
var m2 = client2.GetMessage(2);

var t1 = m1.ReadAllBytesAsync();
var t2 = m2.ReadAllBytesAsync();

Task.WaitAll(t1, t2);

§6.2 非同期操作の中断とタイムアウト

非同期バージョンのメソッドでは、CancellationTokenを指定することにより非同期操作を中断することができるものもあります。 ただしCancellationTokenで中断要求を行なった場合でもプロトコル上の制限などにより即座には中断しない場合もあります。 特にメッセージ本文の取得では、一度取得が開始された場合は本文すべての取得が完了するまで中断することができません。

Timeoutプロパティなどの設定によって強制的にタイムアウトさせることでも操作を中断させることができます。 非同期操作の場合も同期操作と同様にTimeoutプロパティなどの設定に従って操作がタイムアウトします。

なお、操作がタイムアウトしてTimeoutExceptionをスローする場合、PopClientは内部的に接続を切断しPopClient.IsConnectedプロパティはfalseとなります。 タイムアウトした場合は処理を継続できないため、再接続する必要があります。 一方CancellationTokenによって中断を行った場合は接続は維持され、以後も処理を継続することができます。

§6.3 非同期操作の進捗通知

一部の非同期操作メソッドは進捗通知を行わせることができます。 進捗通知は.NET Framework 4.5以降でサポートしていて、IProgress<T>を指定することにより非同期操作の進捗を通知させることができます。 進捗の具体的な取得方法に関してはProgress<T>クラスを参照してください。

PopMessageInfoクラスの非同期メソッドでは、IProgress<PopMessageRetrieveProgressInfo>で進捗の通知を行います。 PopMessageRetrieveProgressInfo構造体からは以下の情報を取得できます。

PopMessageRetrieveProgressInfo構造体のプロパティ
プロパティ 解説
RetrievedOctetCount 現在までに取得が完了したメッセージのオクテット数
RetrievedLineCount 現在までに取得が完了したメッセージの行数
TotalOctetCount 取得されるメッセージの総オクテット数
ProgressPercentage 現在までに取得が完了した割合(%)
百分率に100を掛けて0〜100までの整数で表した値

§6.4 スレッドセーフティ

PopClientクラスおよびPopMessageInfoクラスはスレッドセーフとなっているため、同時に複数のスレッドから参照することができます。 だだし、並列操作はサポートしていないため、同時に複数のスレッドから操作を行う場合、InvalidOperationExceptionがスローされる場合があります。

なお、アプリケーションドメインをまたがる使用については全く考慮していないため、動作および安全性の保証はできません。


§7 例外とトレースログ

§7.1 例外

ライブラリからは主に以下の例外をスローします。 InnerExceptionプロパティに原因となった例外を設定した状態でスローする場合もあります。 また、ここに明記している以外の例外クラス・状況でスローされる場合があります。

スローする例外と状況
名前空間 例外クラス 状況 スローする可能性のあるメソッド
Smdn.Net.Pop3.Protocol PopConnectionException 接続に失敗した場合、送受信中にソケットエラーが発生した場合など PopClient.Connect()
Smdn.Net.Pop3.Protocol PopUpgradeConnectionException SSL/TLSへのアップグレードに失敗した
証明書を検証した結果無効と判断した
PopClient.Connect()
Smdn.Net.Pop3 PopAuthenticationException 認証に失敗した PopClient.Connect()
Smdn.Net.Pop3 PopNoAppropriateAuthMechanismException 現在の設定で試行できる認証メカニズムが無い PopClient.Connect()
Smdn.Net.Pop3 PopErrorResponseException サーバがエラー応答を返した PopClientおよびPopMessageInfoの各メソッド・プロパティ
Smdn.Net.Pop3 PopProtocolViolationException プロトコル上不正な操作を行おうとした PopClientおよびPopMessageInfoの各メソッド・プロパティ
Smdn.Net.Pop3.Client PopMessageDeletedException 削除マーク済み(PopMessageInfo.IsMarkedAsDeletedがtrue)のメッセージに対して操作を行おうとした PopClient.GetMessage()
PopMessageInfoの各メソッド・プロパティ
Smdn.Net.Pop3.Client PopMessageNotFoundException 指定された番号もしくはIDを持つメッセージが存在しない
空のメールボックスに対して番号を指定してメッセージを取得しようとした
PopClient.GetMessage()
PopClient.GetMessages()
System InvalidOperationException 切断された状態(PopClient.IsConnectedがfalse)で操作を行おうとした
操作が進行している状態(PopClient.IsBusyがtrue)で別の操作を開始しようとした
PopClientおよびPopMessageInfoの各メソッド・プロパティ
§.非同期操作の制限
System TimeoutException PopClient.Timeout, SendTimeout, ReceiveTimeoutプロパティで指定されている時間内に操作が完了しなかった PopClientおよびPopMessageInfoの各メソッド・プロパティ
§.非同期操作の中断とタイムアウト
System ArgumentException
および派生クラス
nullや値域外の値など、メソッド呼び出し時の引数が不正な場合 PopClientおよびPopMessageInfoの各メソッド・プロパティ

サーバ/クライアントのバグなどにより上記以外の例外がスローされる可能性もあります。 ログ出力を有効にしている場合、発生した例外はログに記録されます。

それぞれの例外クラスの継承関係は次のとおりです。

  • System.SystemException
    • Smdn.Net.Pop3.PopExcetion
      • Smdn.Net.Pop3.Protocol.PopConnectionException
        • Smdn.Net.Pop3.Protocol.PopUpgradeConnectionException
      • Smdn.Net.Pop3.PopInvalidOperationException
        • Smdn.Net.Pop3.PopErrorResponseException
          • Smdn.Net.Pop3.PopAuthenticationException
        • Smdn.Net.Pop3.PopProtocolViolationException
          • Smdn.Net.Pop3.Client.PopMessageDeletedException
      • Smdn.Net.Pop3.PopNoAppropriateAuthMechanismException
      • Smdn.Net.Pop3.Client.PopMessageNotFoundException

§7.2 ログ

シンボルTRACEを有効にしてビルドした場合、トレースにログを出力します。 ログ出力に使用するTraceSourceの名前と、出力内容は次のとおりです。

ログ出力に使用するTraceSourceの名前と出力内容
TraceSourceの名前 出力内容
"Smdn.Net.Pop3.Client" コマンドの送受信結果とセッション毎の動作ログ
"POP" 送信するコマンドと受信したレスポンスの内容

ログ出力先などの設定を行う場合は、以下の例のようなアプリケーション構成ファイルを作成してください。

アプリケーション構成ファイルの例
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="Smdn.Net.Pop3.Client" switchValue="Verbose">
        <listeners>
          <add name="console" type="System.Diagnostics.ConsoleTraceListener"/>
          <remove name="Default"/>
        </listeners>
      </source>
      <source name="POP" switchValue="Verbose">
        <listeners>
          <add name="log" type="System.Diagnostics.TextWriterTraceListener" initializeData="pop.log"/>
          <remove name="Default"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="switch" value="All"/>
    </switches>
  </system.diagnostics>
</configuration>

なお、SSL/TLSを使用しているかどうかに関わらず、ログにはパスワードを含む内容を平文で出力します。 ログの出力が不要な場合はTRACEを無効にしてリビルドするか、Trace.csなどを削除してください。

§7.2.1 トレース内容のカスタマイズ

トレースにログを出力する際、出力内容はSmdn.Net.MessageAccessProtocols.Diagnosticsの各クラスのインスタンスとしてTraceSource.TraceData()メソッドに渡します。 トレース内容をカスタマイズする場合は、これらのクラスを使うことができます。

カスタマイズしたトレースリスナを使う例
using System;
using System.Diagnostics;
using System.Text;

using Smdn.Net;
using Smdn.Net.MessageAccessProtocols.Diagnostics;
using Smdn.Net.Pop3.Protocol.Client;

public class CustomTraceListener : TraceListener {
  public CustomTraceListener(string name)
    : base(name)
  {
  }

  public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
  {
    if (data is ReceiveTraceData) {
      var received = data as ReceiveTraceData;
      var recv = Encoding.ASCII.GetString(received.Data.Array, received.Data.Offset, received.Data.Count);

      Write("受信内容:\t");
      WriteLine(recv);
    }
    else if (data is SendTraceData) {
      var sent = data as SendTraceData;
      var snt = Encoding.ASCII.GetString(sent.Data.Array, sent.Data.Offset, sent.Data.Count);

      Write("送信内容:\t");
      WriteLine(snt);
    }
    else if (data is MessageTraceData) {
      WriteLine((data as MessageTraceData).Message);
    }
    else if (data is ExceptionTraceData) {
      WriteLine((data as ExceptionTraceData).Exception);
    }
    else {
      base.TraceData(eventCache, source, eventType, id, data);
    }
  }

  public override void Write(string message)
  {
    // TODO
  }

  public override void WriteLine(string message)
  {
    // TODO
  }
}

public static class Test {
  [STAThread]
  static void Main(string[] args) {
    // カスタマイズしたトレースリスナを追加
    PopConnection.TraceSource.Listeners.Add(new CustomTraceListener("log"));
      :
      :
  }
}

§8 サンプルコード

各クライアント実装を用いたサンプルコードを例示します。 証明書の検証を省略している箇所がありますが、実際に使用するコードでは必ず適切な検証を行うように書き換えてください。

§8.1 PopClient

§8.1.1 メッセージをダウンロードしてファイルに保存する

using System;

using Smdn.Net.Pop3;
using Smdn.Net.Pop3.Client;

class SaveToFile {
  static void Main(string[] args)
  {
    using (var client = new PopClient(new Uri("pop://user;AUTH=DIGEST-MD5@localhost/"))) {
      client.Connect("pass");

      // メールボックスにある1件目のメッセージをダウンロードしてファイル"sample.eml"に保存
      client.DownloadFirstMessageToFile("sample.eml");
    }
  }
}

静的メソッドを使って次のようにすることもできます。

using System;

using Smdn.Net.Pop3;
using Smdn.Net.Pop3.Client;

class SaveToFile {
  static void Main(string[] args)
  {
    const bool deleteAfterDownload = false;

    // メールボックスにある1件目のメッセージをダウンロードしてファイル"sample.eml"に保存
    PopClient.DownloadFirstMessageToFile(new Uri("pop://user;AUTH=DIGEST-MD5@localhost/"),
                                         "pass",
                                         deleteAfterDownload,
                                         "sample.eml");
  }
}

§8.1.2 Gmailアカウントのメールボックスから、最新のメッセージをダウンロードする

using System;
using System.Net;
using System.Text;

using Smdn.Net.Pop3.Client;

class GpopRetrieveRecent {
  static void Main(string[] args)
  {
    PopSslConnection.ServerCertificateValidationCallback += delegate {
      // 証明書の検証は省略 (エラーを無視してすべて受け入れる)
      return true;
    };

    using (var client = new PopClient(new Uri("pops://user@pop.gmail.com/"))) {
      client.Connect("pass");

      if (client.MessageCount == 0) {
        // メールボックスにメッセージがない
        Console.WriteLine("no messages");
        return;
      }

      // メールボックスにある最後のメッセージを取得
      var message = client.GetLastMessage();

      // メッセージ本文をダウンロードし、iso-2022-jpでデコードして表示
      Console.WriteLine(message.ReadAllText(Encoding.GetEncoding("iso-2022-jp")));
    }
  }
}

§8.1.3 メールボックスにあるすべてメッセージをダウンロードしてファイルに保存する

using System;

using Smdn.Net.Pop3.Client;

class SaveAndDelete {
  static void Main(string[] args)
  {
    PopSslConnection.ServerCertificateValidationCallback += delegate {
      // 証明書の検証は省略 (エラーを無視してすべて受け入れる)
      return true;
    };

    using (var client = new PopClient(new Uri("pops://user;auth=ntlm@localhost/"))) {
      client.Connect("pass");

      // メッセージ本文を取得したら削除する
      client.DeleteAfterRetrieve = true;

      // メールボックスにあるすべてのメッセージとIDを取得
      foreach (var message in client.GetMessages(true)) {
        // IDをファイル名として保存 (IDがファイル名として妥当かどうかのチェックは省略)
        message.Save(message.UniqueId + ".eml");
      }

      // ログアウトしてメッセージを削除
      client.Logout();
    }
  }
}

上記のコードは、次のコードと等価です。

        :
      client.DeleteAfterRetrieve = false;

      // メールボックスにあるすべてのメッセージとIDを取得
      foreach (var message in client.GetMessages(true)) {
        // IDをファイル名として保存 (IDがファイル名として妥当かどうかのチェックは省略)
        message.Save(message.UniqueId + ".eml");
        // 保存できたら削除マークを付ける
        message.MarkAsDeleted();
      }
        :
        :

さらに、静的メソッドを使うことで次のようにすることもできます。 ただし、この例ではUniqueIdプロパティを参照する度にIDを取得するコマンド(UIDL)が送信される点に注意が必要です。 先の例ではPopClient.GetMessages(true)で全メッセージとそのIDを同時に取得しているため、送信されるコマンドの総数は先の例の方が少なくなります。

using System;

using Smdn.Net.Pop3.Client;

class SaveAndDelete {
  private static string SelectFileNameOf(PopMessageInfo message)
  {
    // IDをファイル名として使用 (IDがファイル名として妥当かどうかのチェックは省略)
    return message.UniqueId + ".eml";
  }

  static void Main(string[] args)
  {
    PopSslConnection.ServerCertificateValidationCallback += delegate {
      // 証明書の検証は省略 (エラーを無視してすべて受け入れる)
      return true;
    };

    // メールボックスにあるすべてのメッセージを取得して指定したファイル名で保存
    PopClient.DownloadAllMessagesToFiles(new Uri("pops://user;auth=ntlm@localhost/"),
                                         "pass",
                                         true,
                                         SelectFileNameOf);
  }
}

§8.1.4 メッセージのIDを使って新着メッセージをチェックする

using System;
using System.Collections.Generic;
using System.Threading;

using Smdn.Net.Pop3.Client;

class CheckRecent {
  static void Main(string[] args)
  {
    // 既知のメッセージID
    var ids = new List<string>();

    using (var client = new PopClient(new Uri("pop://user;auth=ntlm@localhost/"))) {
      client.Profile.UseTlsIfAvailable = false;

      for (;;) {
        client.Connect("pass");

        // メールボックスにあるすべてのメッセージを取得
        foreach (var message in client.GetMessages(true)) {
          // 既知のメッセージIDかどうかチェック
          if (ids.Contains(message.UniqueId)) {
            // 既知の場合は何もしない
          }
          else {
            // 新着の場合は、メッセージ本文のヘッダのみを取得
            foreach (var line in message.ReadLines(0)) {
              // ヘッダのうち"Subject:"で始まる行のみを表示
              if (line.StartsWith("Subject:", StringComparison.OrdinalIgnoreCase))
                Console.WriteLine("{0}: {1}", message.UniqueId, line);
            }

            // 既知のメッセージIDとして追加
            ids.Add(message.UniqueId);
          }
        }

        // いったん切断
        client.Disconnect();

        // 5分後に再接続する
        Thread.Sleep(TimeSpan.FromMinutes(5.0));
      }
    }
  }
}

この例において、初回の接続の時点ではidsは空なので、メールボックスにあるメッセージはすべて新着として扱われます。

また、サーバがUIDLコマンドをサポートしていない場合は、client.GetMessages(true)の時点で例外が発生し、プログラムが停止します。 サーバがUIDLコマンドをサポートしていない可能性がある場合は、PopClient.GetMessages()にはfalseを指定してIDを取得しないようにし、代わりにPopMessageInfo.TryGetUniqueId()メソッドを使ってIDの取得を試みるようにすることができます(ただし、メッセージ一つ一つに対してUIDLコマンドを発行するようになる点には留意が必要です)。

§8.1.5 メッセージをダウンロードしてパース・デコードする

メッセージをダウンロードした後、Smdn.Formats.MimeのMailクラスを使ってパース・デコードする例。

using System;

using Smdn.Net.Pop3.Client;
using Smdn.Formats.Mime;

class DecodeMime {
  static void Main(string[] args)
  {
    using (var client = new PopClient(new Uri("pop://user;auth=ntlm@localhost/"))) {
      client.Profile.UseTlsIfAvailable = false;

      client.Connect("pass");

      // すべてのメッセージを取得
      foreach (var message in client.GetMessages()) {
        // メッセージの本文を取得し、Mail.Loadメソッドでパース・デコードした結果を得る
        var mail = message.ReadAs<Mail>(Mail.Load);

        // デコードしたメッセージの件名を表示
        Console.WriteLine("====[{0}: {1}]{2}",
                          message.MessageNumber,
                          mail.Subject,
                          new string('=', 32));

        // デコードした本文を表示
        if (mail.MimeType.TypeEqualsIgnoreCase("text"))
          Console.WriteLine(mail.ReadContentAsText());
        else
          Console.WriteLine("非テキストメッセージです");
      }
    }
  }
}

§8.2 PopSession

このクラスを直接使用することはできますが推奨はしません。 また、ドキュメントを用意する予定はありません。 内部実装の参照や改変などの参考程度に掲載します。

§8.2.1 メッセージをダウンロード・削除する

Gmailに接続し、最新のメッセージを取得し、表示したあと削除するサンプル。

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

using Smdn.Net.Pop3;
using Smdn.Net.Pop3.Client.Session;
using Smdn.Net.Pop3.Protocol.Client;

class Sample {
  static void Main(string[] args)
  {
    PopSslConnection.ServerCertificateValidationCallback += delegate {
      // 証明書の検証は省略 (エラーを無視してすべて受け入れる)
      return true;
    };

    using (var session = new PopSession("pop.gmail.com", 995, PopSslConnection.CreateSslStream)) {
      var cred = new NetworkCredential("user", "pass");

      // 利用可能ならAPOP、そうでなければUSER/PASSでログイン
      var authenticationResult = session.ApopAvailable
        ? session.Apop(cred)
        : session.Login(cred);

      if (authenticationResult.Failed) {
        Console.Error.WriteLine("authentication failed");
        return;
      }

      // STATコマンドを発行
      PopDropListing dropList;

      if (session.Stat(out dropList).Failed) {
        Console.Error.WriteLine("STAT failed");
        return;
      }

      if (dropList.MessageCount == 0L) {
        // メールボックスにメッセージがない
        Console.Error.WriteLine("no message exists");
        return;
      }

      // RETRコマンドでメッセージ本文を取得・表示
      PopMessage message;

      if (session.Retr(dropList.MessageCount, out message).Failed) {
        Console.Error.WriteLine("RETR failed");
        return;
      }

      var reader = new StreamReader(message.Body, Encoding.GetEncoding("iso-2022-jp"));

      Console.WriteLine(reader.ReadToEnd());

      message.Dispose();

      // DELEコマンドでメッセージを削除マーク済みにする
      if (session.Dele(message.Number).Failed) {
        Console.Error.WriteLine("DELE failed");
        return;
      }

      // QUITコマンドでメッセージを削除してログアウト
      if (session.Quit().Failed) {
        Console.Error.WriteLine("QUIT failed");
        return;
      }
    }
  }
}