POP3クライアント
ネットの掲示板や会議室で時々、POP3クライアントを作りたいというのを見かけます。BCLではSMTPはあるのに何故かPOP3は無いんですよね。で、考えてみたら、私も作り方知らなかったり。(^^; 折角なので電子メールプロトコルの本ひっくり返しながら、てきとーに実装してみました。ちょっと長いですが、以下、PO3クライアントのサンプル。
using System; using System.Collections; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Diagnostics; using System.Security.Cryptography; public class Pop3Mail { // 状態 const string OK = "+OK"; const string ERR = "-ERR"; // コマンド const string USER = "USER {0}"; const string PASS = "PASS {0}"; const string APOP = "APOP {0} {1}"; const string QUIT = "QUIT"; const string STAT = "STAT"; const string NOOP = "NOOP"; const string LIST = "LIST"; const string LIST_N = "LIST {0}"; const string RETR = "RETR {0}"; const string DELE = "DELE {0}"; const string RSET = "RSET"; const int DEFAULT_PORT = 110; private string server; private int port; private string msg; private TcpClient tcp; // デフォルトはJISとする private Encoding enc = Encoding.GetEncoding(50220); public Pop3Mail(string server) : this(server, DEFAULT_PORT) {} public Pop3Mail(string server, int port) { Server = server; Port = port; } public string Server { get { return server; } set { server = value; } } public int Port { get { return port; } set { port = value; } } public string ErrorMessge { get { return msg; } } public Encoding Encode { get { return enc; } set { enc = value; } } public bool Connect(string user, string pass) { // デフォルトはAPOPを使わない return Connect(user, pass, false); } public bool Connect(string user, string pass, bool apop) { try { tcp = new TcpClient(Server, Port); } catch (Exception e) { msg = e.Message; return false; } msg = ReceiveLine(tcp.GetStream()); Trace.WriteLine(msg); string challenge = null; if (apop) { // APOP Match m = Regex.Match(msg, @"(<[^>]+>)"); if (!m.Success) { msg = String.Format("{0}はAPOPに対応していません。", Server); return false; } challenge = m.Groups[1].Value; } return Login(user, pass, challenge); } // 認証 private bool Login(string user, string pass, string challenge) { string cmd; Stream s = tcp.GetStream(); if (challenge == null) { // ユーザ・パスワード cmd = String.Format(USER, user); Trace.WriteLine(cmd); Send(s, cmd); // USER応答 msg = ReceiveLine(s); Trace.WriteLine(msg); // PASS cmd = String.Format(PASS, pass); } else { // チャレンジ・アンド・レスポンス MD5 md5 = MD5.Create(); md5.ComputeHash(Encoding.ASCII.GetBytes(challenge+pass)); StringBuilder digest = new StringBuilder(); foreach (byte b in md5.Hash) digest.AppendFormat("{0:x2}", b); cmd = String.Format(APOP, user, digest.ToString()); } // 認証 Trace.WriteLine(cmd); Send(s, cmd); // 認証応答 msg = ReceiveLine(s); Trace.WriteLine(msg); if (msg.StartsWith(ERR)) return false; return true; } public bool Quit() { Stream s = tcp.GetStream(); // Quit Send(s, QUIT); Trace.WriteLine(QUIT); msg = ReceiveLine(s); Trace.WriteLine(msg); if (msg.StartsWith(ERR)) return false; tcp.Close(); tcp = null; return true; } public string Stat() { Stream s = tcp.GetStream(); // Stat Send(s, STAT); Trace.WriteLine(STAT); msg = ReceiveLine(s); Trace.WriteLine(msg); return msg; } public bool Noop() { Stream s = tcp.GetStream(); // Noop Send(s, NOOP); Trace.WriteLine(NOOP); msg = ReceiveLine(s); Trace.WriteLine(msg); return msg.StartsWith(OK); } public string List() { Stream s = tcp.GetStream(); Trace.WriteLine(LIST); Send(s, LIST); return ReceiveLines(s); } public string List(int no) { Stream s = tcp.GetStream(); string cmd = String.Format(LIST_N, no); Trace.WriteLine(cmd); Send(s, cmd); msg = ReceiveLine(s); if (msg.StartsWith(ERR)) { return null; } // 番号 バイト数を戻す return msg.Substring(OK.Length + 1); } public string Retr(int no) { Stream s = tcp.GetStream(); string cmd = String.Format(RETR, no); Trace.WriteLine(cmd); Send(s, cmd); return ReceiveMessage(s, Encode); } public bool Dele(int no) { Stream s = tcp.GetStream(); string cmd = String.Format(DELE, no); Trace.WriteLine(cmd); Send(s, cmd); msg = ReceiveLine(s); Trace.WriteLine(msg); return msg.StartsWith(OK); } public bool Rset() { Stream s = tcp.GetStream(); Trace.WriteLine(RSET); Send(s, RSET); msg = ReceiveLine(s); Trace.WriteLine(msg); return msg.StartsWith(OK); } private void Send(Stream s, string cmd) { byte data = Encoding.ASCII.GetBytes(cmd + "\r\n"); s.Write(data, 0, data.Length); } private string ReceiveLine(Stream s) { return new StreamReader(s).ReadLine(); } private string ReceiveLines(Stream s) { StreamReader sr = new StreamReader(s); StringBuilder sb = new StringBuilder(); string line; line = sr.ReadLine(); if (line == null) return null; // 最初の応答を保存 msg = line; do { Trace.WriteLine(line); // .のみの行が来たら終了 if (Regex.IsMatch(line, @"^\.$")) break; // "番号 バイト数"の並び if (Regex.IsMatch(line, @"^\d+\s\d+")) sb.Append(line + ","); } while ( (line = sr.ReadLine()) != null); line = sb.ToString(); if (line.EndsWith(",")) return line.TrimEnd(',').Split(','); return null; } private string ReceiveMessage(Stream s, Encoding enc) { StreamReader sr = new StreamReader(s, enc); StringBuilder sb = new StringBuilder(); string line; line = sr.ReadLine(); if (line == null || Regex.IsMatch(line, @"^\.$")) return null; // 最初の応答を保存 msg = line; while ( (line = sr.ReadLine()) != null) { Trace.WriteLine(line); // .のみの行が来たら終了 if (Regex.IsMatch(line, @"^\.$")) break; if (line.StartsWith("..")) { // 先頭が..の場合、.を1つ削除する line = line.Substring(1); } sb.Append(line + "\n"); } line = sb.ToString(); if (line.EndsWith("\n")) return line.TrimEnd('\n').Split('\n'); return null; } } // メール取得サンプル class Program { static void Main(string args) { if (args.Length < 4) { Console.WriteLine("usage: program.exe server user pass no"); return; } string server = args[0]; // POPサーバー string user = args[1]; // ユーザ string pass = args[2]; // パスワード int no = int.Parse(args[3]);// メッセージ番号 Pop3Mail pop = new Pop3Mail(server); // POPサーバーに接続 pop.Connect(user, pass, true); // メッセージを取得 string[] list = pop.Retr(no); // 表示 foreach (string s in list) Console.WriteLine(s); // セッションの終了 pop.Quit(); } }
途中で力尽きたのでMIMEには未対応です。一応動いているみたいですが、きっと怪しいです。(^^;
勉強用に作ったので、そのまま使うのはやめておいた方が良いです・・・