SMTP on C# (3)

この記事は古いです。ここも参照してください



ログイン機能を付け足しました。loginとplainだけですが。CRAM-MD5はどうしてもうまくいかないのでコメントアウトしてあります。
文字列のエンコードがぐちゃぐちゃなのであとでなおします。(いちおう文字化けはしないはず。)
コメントアウトが沢山でごめんなさい。一旦コンパイルして逆コンパイルすれば取り除けますが(笑)

using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Security.Cryptography;

namespace Mail
{
    public class Mail
    {
        static void Main(string[] args)
        {
            Message mes = new Message();
            mes.Body = "ああああああああ";
            mes.From = new Address("from@msn.com",null);
            mes.To = new List<Address>();
            mes.To.Add(new Address("to@msn.com","あおんと"));


            SMTP smtp = new SMTP("mail.asahi-net.or.jp", 25);
            smtp.Auth(SMTP.AuthTypes.Plain, "user", "password");
            smtp.From("from@from.com");
            smtp.Recipient("aont@hatena.com");
            smtp.Send(mes);
            smtp.Quit();
            Console.ReadKey(true);
        }

        /* =========================================================== */


        static string ToBase64(byte[] bytes)
        {
            return Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);
        }
        static string FromBase64(string b64)
        {
            return Encoding.Unicode.GetString(System.Convert.FromBase64String(b64));
        }
        static string ToBase64(string @string)
        {
            return Convert.ToBase64String(
                JIS_Enc.GetBytes(
                    @string
                ), Base64FormattingOptions.None
            );
        }
        static string RandomB64(int n)
        {
            Random rnd = new Random();
            byte[] bytes = new byte[n];
            for (int i = 0; i < n; i++)
            {
                bytes[i] = (byte)rnd.Next(256);
            }
            return ToBase64(bytes);
        }
        /*
        static string MD5(string s)
        {
            byte[] data = System.Text.Encoding.Unicode.GetBytes(s);

            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
            byte[] bs = md5.ComputeHash(data);
            StringBuilder result = new StringBuilder();
            foreach (byte b in bs)
            {
                result.Append(b.ToString("x2"));
            }
            return result.ToString();

        }
         */

        static Encoding JIS_Enc = Encoding.GetEncoding(50220);
        static string ToBase64_JIS(string @string)
        {
            byte[] bytes = JIS_Enc.GetBytes(@string);

            return "=?ISO-2022-JP?B?" + ToBase64(bytes) + "?=";
        }

        /* =========================================================== */


        public class SMTP
        {
            NetworkStream Stream;
            TcpClient Client;
            int Port;
            string Server;
            StreamReader Reader;
            //List<string> messages = null;

            public SMTP(string Server, int Port)
            {
                this.Server = Server;
                this.Port = Port;
                Connect();
            }

            public SMTP(string Server)
            {
                this.Server = Server;
                this.Port = 25;
                Connect();
            }

            void Connect()
            {
                Client = new TcpClient(Server, Port);
                Stream = Client.GetStream();
                Stream.ReadTimeout = 5;
                Reader = new StreamReader(Stream);
                ReadEx("220");
                WriteLine("EHLO ", Server);
                ReadEx("250");

            }
            //IEnumerable<string>
            string ReadEx(params string[] codes)
            {
                string read = null;
                string read_toend = "";
                do
                {
                    read = Read();
                    read_toend += read + "\r\n";
                    //write(read);
                    //yield return read;
                    //Console.WriteLine(read);
                    bool error = false;
                    foreach (string code in codes)
                    {
                        if (error = read.StartsWith(code))
                            break;
                    }
                    if (!error)
                    {
                        throw new Exception(read);
                    }
                } while (read[3] == '-');
                return read_toend;
            }

            string[] _AuthTypes;

            string Read()
            {
                string read = Reader.ReadLine();
                if (read.StartsWith("250-AUTH "))
                {
                    _AuthTypes = read.Substring(9).Split(' ', '\0');
                }
                return read;
            }

            void Write(byte[] bytearray)
            {
                Stream.Write(bytearray, 0, bytearray.Length);
            }
            void Write(byte[] bytearray, int offset, int size)
            {
                Stream.Write(bytearray, offset, size);
            }
            void Write(string write)
            {
                //Console.Write(write);//////////////////////////////////////////////////////////////////////////
                byte[] bytearray = JIS_Enc.GetBytes(write);
                Write(bytearray);
            }
            void WriteLine(string write)
            {
                Write(write);
                Stream.Write(new byte[] { 13, 10 }, 0, 2);
                //Console.WriteLine();//////////////////////////////////////////////////////////////////////////////////
            }
            void Write(params string[] write_array)
            {
                foreach (string write in write_array)
                {
                    Write(write);
                }
            }
            void WriteLine(params string[] write_array)
            {
                foreach (string write in write_array)
                {
                    Write(write);
                }
                Stream.Write(new byte[] { 13, 10 }, 0, 2);
            }

            public static class AuthTypes
            {
                public const string
                    Plain = "PLAIN",
                    Login = "LOGIN";
                    //Cram_MD5 = "CRAM-MD5";
            }
            public void Auth(string type, string user, string password)
            {
                type = type.ToUpper();
                AuthCheck(type);
                switch (type)
                {
                    case "PLAIN":
                        WriteLine("AUTH PLAIN " + ToBase64(user + '\0' + user + '\0' + password));
                        break;
                    case "LOGIN":
                        WriteLine("AUTH LOGIN");
                        ReadEx("334");
                        WriteLine(ToBase64(user));
                        ReadEx("334");
                        WriteLine(ToBase64(password));
                        break;
                    /*
                    case "CRAM-MD5":
                        WriteLine("AUTH CRAM-MD5");
                        string md5_key = System.Text.Encoding.ASCII.GetString(System.Convert.FromBase64String(Read().Substring(4)));
                        Console.WriteLine(md5_key);
                        Console.ReadKey(true);
                        Console.WriteLine(MD5(md5_key + password));
                        Console.ReadKey(true);
                        WriteLine(ToBase64(user + '\0' + MD5(md5_key + password)));/////////////////////////
                        break;
                    */
                }
                ReadEx("235");
            }

            private void AuthCheck(string type)
            {
                foreach (string _type in _AuthTypes)
                {
                    if (_type == type)
                    {
                        return;
                    }
                }
                throw new Exception("Authtype:" + type + " Invalid.");
                //return;
            }

            public void From(string from)
            {
                //throw new Exception("AAAAAAAAAAAAAAAAAAa");
                WriteLine("MAIL FROM:<", from, ">");
                ReadEx("250");
            }

            public void Recipient(string rcpt)
            {
                WriteLine("RCPT TO:<", rcpt, ">");
                ReadEx("250", "251");
            }
            public void Recipient(params string[] rcpts)
            {
                foreach (string rcpt in rcpts)
                {
                    Recipient(rcpt);
                }
            }
            public void Send(Message message, Encoding encoding)
            {
                Send(message.ToString(), encoding);
            }
            public void Send(Message message)
            {
                Send(message.ToString(), JIS_Enc);
            }
            public void Send(string message, Encoding encoding)
            {
                WriteLine("DATA");
                ReadEx("250", "354");
                Write(message);
                WriteLine("\r\n.");
                ReadEx("250");
            }
            public void Send(string message)
            {
                Send(message, JIS_Enc);
            }


            public void Send_Eml(string emlfile)
            {
                WriteLine("DATA");
                ReadEx("250", "354");

                FileStream fs = new FileStream(emlfile, FileMode.Open);
                long length = fs.Length;
                byte[] data;
                if (length > int.MaxValue)
                {
                    throw new Exception("too large file");
                }
                else
                {
                    data = new byte[length];
                    fs.Read(data, 0, (int)length);
                    fs.Dispose();
                }
                Write(data);

                WriteLine("\r\n.");
                ReadEx("250");
            }


            public void Quit()
            {

                WriteLine("QUIT");
                ReadEx("221");

                Dispose();
            }
            public void Dispose()
            {
                Reader.Dispose();
                Stream.Dispose();
            }
        }

        /* ============================================================= */



        public class Message
        {
            string boundary = RandomB64(16);
            public Address From = null;
            public List<Address> To = null;
            public Address ReplyTo = null;
            public string Subject = null;
            public string Body = null;
            public List<File> Files = null;
            void AddAddress(ref string s, string type, Address address)
            {
                if (address != null)
                {
                    s += type + ": ";
                    s += address;
                    s += "\r\n";
                }
            }

            void AddAddress(ref string s, string type, List<Address> addresses)
            {
                if (addresses != null)
                {
                    bool first = true;
                    s += type + ": ";
                    foreach (Address address in addresses)
                    {
                        if (!first)
                        {
                            s += ",";
                            first = false;
                        }
                        s += address;
                    }
                    s += "\r\n";
                }
            }


            public override string ToString()
            {
                string mail = "";

                AddAddress(ref mail, "From", From);
                AddAddress(ref mail, "To", To);
                AddAddress(ref mail, "Reply-To", ReplyTo);

                if (Subject != null)
                    mail += "Subject: " + ToBase64_JIS(Subject) + "\r\n";

                mail += "MIME-Version: 1.0\r\n";
                mail += "X-Mail-Agent: Aont Mailer\r\n";

                if (Files == null)
                {
                    mail += "Content-Type: text/plain; charset=ISO-2022-JP\r\n";
                    //mail += "Content-Transfer-Encoding: base64\r\n";
                    mail += "\r\n";

                    mail += Body;
                    //mail += ToBase64(jisEnc.GetBytes(Body));
                }
                else
                {
                    mail += "Content-Type: multipart/mixed; boundary=\"" + boundary + "\"\r\n\r\n";
                    mail += "--" + boundary + "\r\n";
                    mail += "Content-Type: text/plain; charset=ISO-2022-JP\r\n";
                    //mail += "Content-Transfer-Encoding: base64\r\n";
                    mail += "\r\n";
                    mail += Body;
                    mail += "\r\n\r\n";

                    foreach (File file in Files)
                    {

                        mail += "--" + boundary + "\r\n";
                        mail += file;
                        mail += "\r\n";
                    }
                    mail += "--" + boundary + "--";
                }
                return mail;
            }

        }

        /* ========================================================= */

        public class Address
        {
            public string address;
            public string name;

            public override string ToString()
            {
                if (name != null && name != "")
                    return ToBase64_JIS(name) + "<" + address + ">";
                else
                    return address;
            }

            public Address(string address, string name)
            {
                this.address = address;
                this.name = name;
            }
        }


        /* ========================================================= */
        public class File
        {
            public string name;
            string _address;
            public string address
            {
                get { return _address; }
                set { _address = value; data = null; }
            }
            public byte[] data;
            public File(string name, string address)
            {
                this.name = name;
                this.address = address;
            }
            public File(string name, byte[] data)
            {
                this.name = name;
                this.data = data;
            }
            public override string ToString()
            {
                if (data == null)
                {
                    FileStream fs = new FileStream(address, FileMode.Open);
                    long length = fs.Length;
                    //byte[] data;
                    if (length > int.MaxValue)
                    {
                        fs.Dispose();
                        throw new Exception("too large file");
                    }
                    else
                    {
                        data = new byte[length];
                        fs.Read(data, 0, (int)length);
                        fs.Dispose();
                    }
                }

                string b64 = "Content-type: unknown;\r\n";
                b64 += " name=" + ToBase64_JIS(name) + "\r\n";
                b64 += "Content-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n";
                b64 += " filename=" + ToBase64_JIS(name) + "\r\n\r\n";
                {
                    b64 += ToBase64(data);
                }

                return b64;
            }
        }
    }
}


IEnumerableにはめられた…IEnumerableを戻り値とする関数のなかで例外発生させたり、標準出力しても反応しない…まじうざかった。しょうがないから改行つきのテキストを返すようにした。