POPでメールを受信する手順
サンプルソースコードに含まれるPopClient
クラスは、POPを使用してメールサーバからメールを受信するクラスです。以下、主要部分について解説します。
1. POPサーバとの接続
POPサーバは通常TCPの110番ポートで待ち受けしています。そこにTcpClient
で接続します。この処理はPopClient
のコンストラクタで行われています。
public PopClient(string hostname, int port) { // サーバと接続 this.tcp = new TcpClient(hostname, port); this.reader = new StreamReader(this.tcp.GetStream(), Encoding.ASCII); // オープニング受信 string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "接続時に POP サーバが \"" + s + "\" を返しました。"); } }
Public Sub New(ByVal hostname As String, ByVal port As Integer) ' サーバと接続 Me.tcp = New TcpClient(hostname, port) Me.reader = New StreamReader(Me.tcp.GetStream(), Encoding.ASCII) ' オープニング受信 Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "接続時に POP サーバが """ & s & """ を返しました。") End If End Sub
上記のReadLine()
メソッドは行末までの1行を読み込むprivate
メソッドです。POPでは8bit ASCIIでやり取りが行われます(メールがどんな文字コードで書かれているかではなく、POP自体のやり取りのことです)。そのため、Encoding.ASCII
を指定したStreamReader
をあらかじめ作成しておいて、テキストの読み込みに使用しています。なお、POPでの行末は必ずCR LF(C#での"\r\n"
)と決められています。また、ReadLine()
メソッドは動作確認用に受信したテキストをコンソールに書き出すようにもなっています。
POPサーバに接続すると最初にオープニングのテキストが1行送られてきます。オープニングのテキストの最初は"+OK"で始まります。最短の場合はこの3文字と改行だけの場合もありますが、多くの場合は"+OK POP3 server ready."のようなテキストが返されます。"+OK"より後ろの部分のテキストには特に意味はありません。POPサーバの名称やバージョンが記述されていることが多いようです。POPサーバに問題があるために使用できない場合などは、受信テキストの先頭が"+OK"以外("-ERR")になります。そのため、上記のように先頭が"+OK"でない場合は例外を投げるようにしています。
なお、サンプルソースコードではs.StartsWith("+OK")
としていますが、必ずしも大文字であるとは限りません。ですからs.ToUpper().StartsWith("+OK")
のような判定を行った方が汎用性が増します。以下のログインやリストの取得などPOPサーバから返されるリザルト文字列はすべて同様のことが言えますのでご注意ください。
2. ログイン
POPサーバを使用するには、通常ユーザー名、パスワードを使用してログインする必要があります。Login()
メソッドにてログイン処理を行っています。
public void Login(string username, string password) { // ユーザー名送信 SendLine("USER " + username); string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "USER 送信時に POP サーバが \"" + s + "\" を返しました。"); } // パスワード送信 SendLine("PASS " + password); s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "PASS 送信時に POP サーバが \"" + s + "\" を返しました。"); } }
Public Sub Login(ByVal username As String, ByVal password As String) ' ユーザー名送信 SendLine("USER " & username) Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "USER 送信時に POP サーバが """ & s & """ を返しました。") End If ' パスワード送信 SendLine("PASS " & password) s = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "PASS 送信時に POP サーバが """ & s & """ を返しました。") End If End Sub
SendLine()
メソッドは引数のテキストに改行(CR LF。C#での"\r\n"
)を付加して送信するprivate
メソッドです。送信時も8bit ASCIIにするため、Encoding.ASCII.GetBytes()
を使用して変換した後に送信するようにしています。また、SendLine()
メソッドは動作確認用に、送信するテキストをコンソールに書き出すようにもなっています。
ログインするには、まずUSERコマンドでユーザー名を送信します。このコマンドは最初に"USER"、半角スペース、続けてユーザー名、最後に改行(CR LF)という書式になっています。「ユーザー名」の前は半角スペースを空けてください。
例えば、ユーザー名がabc@example.comの場合は次のように送信します。
ユーザー名が正常な場合には"+OK"で始まるテキストが返されます。オープニングのテキストと同じように"+OK"の後には"+OK username is ok."のように任意のテキストが付いてくる場合もあります。ユーザー名が正常ではないと判断された場合は"+OK"以外("-ERR")が返されます。
次にPASSコマンドでパスワードを送信します。内容はサンプルコードを参照してください。
このように、POPはすべてテキストのやり取りだけで構成された、とても単純で扱いやすいプロトコルです。ほとんどのやり取りは上記のUSERコマンドやPASSコマンドのように、コマンドのテキストを送信すると、それに対する返事のテキストが返ってくるという形になっています。
3. リストの取得
POPにはメールを取得したり削除したりするコマンドがあります。POPサーバに複数のメールがたまっている場合は、どのメールを取得したり削除したりするのかを番号で指定します。この番号はあらかじめLISTコマンドで取得しておく必要があります。
public ArrayList GetList() { // LIST 送信 SendLine("LIST"); string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "LIST 送信時に POP サーバが \"" + s + "\" を返しました。"); } // サーバにたまっているメールの数を取得 ArrayList list = new ArrayList(); while (true) { s = ReadLine(); if (s == ".") { // 終端に到達 break; } // メール番号部分のみを取り出し格納 int p = s.IndexOf(' '); if (p > 0) { s = s.Substring(0, p); } list.Add(s); } return list; }
Public Function GetList() As ArrayList ' LIST 送信 SendLine("LIST") Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "LIST 送信時に POP サーバが """ & s & """ を返しました。") End If ' サーバにたまっているメールの数を取得 Dim list As ArrayList = New ArrayList Do While True s = ReadLine() If s = "." Then ' 終端に到達 Exit Do End If ' メール番号部分のみを取り出し格納 Dim p As Integer = s.IndexOf(" "c) If p > 0 Then s = s.Substring(0, p) End If list.Add(s) Loop Return list End Function
番号のリストを取得するには、まずLISTコマンドを送信します。LISTコマンドには引数はないため、単に"LIST"と改行のみを送信するだけです。問題がなければPOPサーバは次のような一連のテキストを返します。
他のコマンドと同様に"+OK"で始まる1行のテキストを返します。通常は"+OK"の後に半角スペースで区切ってPOPサーバにたまっているメールの数が記述されています。この例では、メールの数は3通だったことが分かります。何か問題があってリストを返せない場合は"-ERR"になります。
"+OK"行の下に続いているのがメールのリストで、POPサーバにたまっているメールの数と同じ行数あります。それぞれの行頭にメールの番号があり、半角スペースで区切ってオプションのテキストが続きます(半角スペース以降がない場合もあります)。リストの最後は"."(ピリオド)のみの行となります。
メールの番号は現在のセッション中だけ有効となります。上記の例では1番、201番、202番のメールがあることになりますが、現在のセッションを切断し、再び接続したときに同じ番号のメールがあるかどうかは分かりませんし、たとえあったとしても同じメールであるとは限りません。ですから、POPサーバに接続するたびにLISTコマンドで番号を取得する必要があります。
今回のサンプルコードでは、取得した番号のリストをArrayList
に格納して返すようにしています。
4. メールの取得
さて、メールの番号が分かればメール本体を取得することができます。メール本体を取得するにはRETRコマンドを使用します。
public string GetMail(string num) { // RETR 送信 SendLine("RETR " + num); string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "RETR 送信時に POP サーバが \"" + s + "\" を返しました。"); } // メール取得 StringBuilder sb = new StringBuilder(); while (true) { s = ReadLine(); if (s == ".") { // "." のみの場合はメールの終端を表す break; } sb.Append(s); sb.Append("\r\n"); } return sb.ToString(); }
Public Function GetMail(ByVal num As String) As String ' RETR 送信 SendLine("RETR " & num) Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "RETR 送信時に POP サーバが """ & s & """ を返しました。") End If ' メール取得 Dim sb As StringBuilder = New StringBuilder Do While True s = ReadLine() If s = "." Then ' "." のみの場合はメールの終端を表す Exit Do End If sb.Append(s) sb.Append(vbcrlf) Loop Return sb.ToString() End Function
RETRコマンドは「"RETR"+半角スペース+LISTコマンドで取り出したメールの番号のうちの1つ+改行」というテキストを送信します。POPサーバから返ってくるテキストは"+OK"の行、メール本体、"."のみの行となります。
メール本体はSMTPで配送されているそのままの形です。この形式は通常のテキスト形式のメールとほとんど同じです。唯一の注意点は行頭に"."(ピリオド)がある場合は".."(ピリオド2つ)となっています。メール本体の詳細は後述します。
5. メールの削除
POPではメールを取得しても自動的に削除されることはありません。メールを削除する場合はDELEコマンドで明示的に削除する必要があります。
public void DeleteMail(string num) { // DELE 送信 SendLine("DELE " + num); string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "DELE 送信時に POP サーバが \"" + s + "\" を返しました。"); } }
Public Sub DeleteMail(ByVal num As String) ' DELE 送信 SendLine("DELE " & num) Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "DELE 送信時に POP サーバが """ & s & """ を返しました。") End If End Sub
メールを削除するときは「"DELE"+半角スペース+LISTコマンドで取り出したメールの番号のうちの1つ+改行」というテキストを送信します。なお、削除したメールを元に戻すことはできませんのでご注意ください。
6. クローズ
POPとの接続を切断する際にはQUITコマンドを使用します。DELEコマンドによるメールの削除などはQUITコマンドを発行して初めて有効になるとRFCでは既定されています。ですので、切断時には必ずQUITコマンドを発行するようにすべきです(初稿では「いきなりTCPのセッションを切断しても問題は出ないと思う」と記述していましたが誤りでした。訂正させて頂きます)。
public void Close() { // QUIT 送信 SendLine("QUIT"); string s = ReadLine(); if (!s.StartsWith("+OK")) { throw new PopClientException( "QUIT 送信時に POP サーバが \"" + s + "\" を返しました。"); } ((IDisposable)this.reader).Dispose(); this.reader = null; ((IDisposable)this.tcp).Dispose(); this.tcp = null; }
Public Sub Close() ' QUIT 送信 SendLine("QUIT") Dim s As String = ReadLine() If Not s.StartsWith("+OK") Then Throw New PopClientException( _ "QUIT 送信時に POP サーバが """ & s & """ を返しました。") End If CType(Me.reader, IDisposable).Dispose() Me.reader = Nothing CType(Me.tcp, IDisposable).Dispose() Me.tcp = Nothing End Sub