Создание Windows-приложений на основе Visual C#


           

region Constants const int MaxReceiveSize


//#define _DEBUG
using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Diagnostics; using System.Collections; using System.Text; using System.Text.RegularExpressions;
namespace Mail.Providers { /// <summary> /// Реализация протокола POP3 /// </summary> /// <example>Пример получения сообщения. /// <code> /// using (Pop3 pop3 = new Pop3("host")) /// { /// pop3.LogIn("Username", "Password"); /// Console.WriteLine("Количество сообщений:" + pop3.NumberOfMessages); /// /// using(Message msg = pop3.GetMessage(1)) // получение первого сообщения /// { /// Console.WriteLine("Тема: " + msg.Subject); /// } /// } /// </code> /// </example> public class Pop3 : Provider { # region Constants const int MaxReceiveSize = 1024; const int POP3DefaultPort = 110;
const int SendTimeout = 60000; // в милисекундах. public int ReceiveTimeout = 2000000; // в милисекундах. public int PollTimeout = 100000; // в микросекундах.
const string CRLF = "\r\n";
const string FiveOctalTerm = "\r\n.\r\n"; const string STAT_OK = "+OK"; const string STAT_ERR = "-ERR";
const char SPACE = ' '; const char CR = '\r'; #endregion
Socket _socket = null;
MaildropStatus _status = null; uint [] _messagelist = null;
bool _authenticated = false;
#region DEBUG functions FileStream GetDebugStream() { return new FileStream(@"C:\trace_response.log", FileMode.Append); } [Conditional("_DEBUG")] void Trace(byte [] str) { FileStream fs = GetDebugStream(); fs.Write(str, 0, str.Length); fs.Close(); }
[Conditional("_DEBUG")] void Trace(string str) { FileStream fs = GetDebugStream(); fs.Write(Encoding.ASCII.GetBytes(str), 0, str.Length); fs.Close(); } #endregion
#region Constructors /// <summary> /// Инициализация класса установленным по умолчанию портом (110) и установка адреса временной папки /// на текущую системную временную папку. /// </summary> /// <param name="server">IP адрес сервера.</param> public Pop3(string server) { _server = server; _port = POP3DefaultPort; TempDirectory = Path.GetTempPath(); }
/// <summary> /// Инициализация класса а установленным по умолчанию портом (110). /// </summary> /// <param name="server">IP адрес сервера.</param> /// <param name="temp">Адрес временной папки.</param> public Pop3(string server, string temp) { _server = server; _port = POP3DefaultPort; TempDirectory = temp; }
/// <summary> /// Инициализация класса . /// </summary> /// <param name="server">IP адрес сервера.</param> /// <param name="port">Номер порта.</param> public Pop3(string server, int port) { _server = server; _port = port; TempDirectory = Path.GetTempPath(); }
/// <summary> /// Инициализация класса . /// </summary> /// <param name="server">IP-адрес сервера.</param> /// <param name="port">Номер порта.</param> /// <param name="temp">Адрес временной папки.</param> public Pop3(string server, int port, string temp) { _server = server; _port = port; TempDirectory = temp; } #endregion
#region Public properties /// <summary> /// Возвращается значение true, если в почтовом ящике есть сообщения. /// </summary> public bool IsMessages { get { return (NumberOfMessages > 0); } }
/// <summary> /// Количество сообщений в ящике. /// </summary> public uint NumberOfMessages { get { // not initialized if (_status == null) { GetStatus(); }
return _status.messages; } } #endregion
#region Method-Property substitution /// <summary> /// Получение количества сообщений. /// </summary> /// <returns></returns> public uint GetNumberOfMessages() { return NumberOfMessages; }
public bool GetIsMessages() { return IsMessages; } #endregion
/// <summary> /// Анализ количества строк, полученных от сервера после отправки команды STAT. /// </summary> /// <example>Команда STAT. В ответ на вызов команды сервер выдает положительный ответ "+OK", /// за которым следует количество сообщений в почтовом ящике и их общий размер в символах. /// Сообщения, которые помечены для удаления, не учитываются в ответе сервера. /// </example> void GetStatus() { CheckConnection();
Send("STAT"); string tmp = Receive();
string [] tokens = tmp.Split(new Char[] {SPACE, CR}, 4);
try { _status = new MaildropStatus( Convert.ToUInt32(tokens[1], 10), Convert.ToUInt32(tokens[2], 10) ); } catch (Exception e) { throw new CoreException("Невозможно проанализировать ответ", e); } } /// <summary> /// Установка соединения с сервером. /// </summary> /// <param name="server">Название сервера.</param> /// <param name="port">Номер порта.</param> void EstablishConnection(string server, int port) { // Получение IP-адреса сервера. IPAddress ipadr = Dns.Resolve(server).AddressList[0]; IPEndPoint ephost = new IPEndPoint(ipadr, port);
// Создание Socket для передачи данных по протоколу TCP. _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
LingerOption linger = new LingerOption(true, 10); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, linger); // Установка времени ожидания. _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SendTimeout); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ReceiveTimeout);
// Соединение с сервером. _socket.Connect(ephost); if (!_socket.Connected) { throw new CoreException("Сервер не найден: " + server); } }
/// <summary> /// Проверка соединения и авторизации пользователя. /// </summary> void CheckConnection() { if (_socket == null || !_socket.Connected) { throw new CoreException("Соединение не установлено."); }
if (!_authenticated) { throw new CoreException("Пользователь не аутентифицирован (Метод LogIn). "); } } /// <summary> /// Отправка команды на сервер. /// </summary> /// <param name="command">Текст команды.</param> void Send(string command) { // Все команды заканчиваются парой CRLF. command += CRLF; // Сервер работает с кодировкой ASCII. Encoding tmp = Encoding.ASCII; Byte [] buf = tmp.GetBytes(command);
int total = buf.Length; while (total > 0) { total -= _socket.Send(buf, command.Length, SocketFlags.None); } } /// <summary> /// Анализ POP3-строки. /// </summary> /// <param name="str">Строка.</param> void AnalyseResponse(string str) { Trace(str); // Debug.WriteLine(str);
if (str.StartsWith(STAT_ERR)) { string msg; int i = str.IndexOf(CRLF); if (i < 0) { msg = "Ответ сервера: " + STAT_ERR; } else { // Если ответ слишком большой, отсекаем его. msg = str.Substring(STAT_ERR.Length + 1, Math.Min(i - STAT_ERR.Length - 1, 79)); }
throw new ResponseException(msg); } }
/// <summary> /// Получение сообщения в POP3-формате. /// </summary> /// <param name="index">Номер сообщения.</param> /// <returns></returns> public StringReader DumpMessage(int index) { CheckConnection();
Send("RETR " + index); return new StringReader(Receive()); } /// <summary> /// Удаление символов конца сообщения. /// </summary> /// <param name="message">Сообщение.</param> /// <returns></returns> string TruncateTail(string message) { if (!message.EndsWith(FiveOctalTerm)) { Debug.WriteLine("Последние 5 символов: {" + message.Substring(message.Length — 5) + "}"); throw new ResponseException("Неправильные символы конца сообщения."); }
return message.Remove(message.Length — FiveOctalTerm.Length, FiveOctalTerm.Length); }
/// <summary> /// Получение существующих номеров сообщений. /// </summary> /// <returns>Массив с существующими индексами сообщений.</returns> /// <example>Команда LIST. Сервер выдает информацию о всех сообщениях, находящихся в почтовом ящике. /// Сообщения, помеченные для удаления, не перечисляются. /// </example> public uint[] ListMessages() { CheckConnection();
if (_messagelist == null) { Send("LIST"); string tmp = Receive(); tmp = TruncateTail(tmp); int start = tmp.IndexOf(CRLF); if (start > 0) { start += CRLF.Length; ArrayList l = new ArrayList(); Regex r = new Regex(@"\r\n"); string [] list = r.Split(tmp.Substring(start)); if (list.Length > 0) { foreach (string s in list) { string [] f = s.Split(new char [] {' '}, 2); l.Add(Convert.ToUInt32(f[0], 10)); } }
if (l.Count > 0) { _messagelist = (uint [])l.ToArray(typeof(uint)); } else _messagelist = new uint[0]; } else _messagelist = new uint[0]; }
return _messagelist; }
/// <summary> /// Отправляет команду NOOP на сервер. /// </summary> /// <remarks> /// <para>Используется для поддержания сеанса с сервером.</para> /// </remarks> /// <example>Команда NOOP. POP3-сервер ничего не делает и всегда отвечает положительно. /// </example> public void SendNoop() { CheckConnection();
Send("NOOP"); string tmp = Receive(); }
/// <summary> /// Возвращает уникальный идентификатор сообщения. /// </summary> /// <remarks> /// <para> /// Если сообщение помечено на удаление, оно не учитывается. /// </para> /// </remarks> /// <param name="index">Номер сообщения.</param> /// <returns>Уникальный идентификатор пользователя.</returns> public string GetMessageUniqueID(uint index) { CheckConnection();
Send("UIDL " + index); string tmp = Receive();
string [] f = tmp.Split(new char [] {' ', '\r', '\n'}, 4); return f[2]; }
/// <summary> /// Получение заголовка сообщения. /// </summary> /// <param name="index">Сообщение.</param> /// <param name="liens">Количество первых строк.</param> /// <returns>Сообщение с анализированными заголовками.</returns> /// <example>Команда TOP. Если ответ сервера положительный, /// он передает заголовки сообщения и указанное количество строк из тела сообщения. /// </example> public Message GetMessageHeader(uint index, int top) { CheckConnection();
Send("TOP " + index + " " + top); string message = Receive();
message = Utils.RemoveByteStuffedSequence(message); return new Message(this, TruncateTail(message), index); }
/// <summary>Удаление сообщения. /// </summary> /// <param name="index">Номер сообщения.</param> /// <example>Команда DELETE. POP3-сервер помечает указанное сообщение как удаленное, /// но не удаляет его, пока сессия не перейдет в режим UPDATE. /// </example> public override void DeleteMessage(uint index) { CheckConnection();
Send("DELE " + index); string tmp = Receive(); }
/// <summary> /// Получение сообщения. /// </summary> /// <param name="index">Номер сообщения.</param> /// <returns>Сообщение.</returns> /// <example>Команда RETR. После положительного ответа сервер передает содержание сообщения. /// </example> public override Message GetMessage(uint index) { CheckConnection();
Send("RETR " + index); string message = ReceiveMessage();
message = Utils.RemoveByteStuffedSequence(message); return new Message(this, TruncateTail(message), index); }
public void OnRecievedData( IAsyncResult ar ) { }
/// <summary> /// Получение ответа сервера без проверки подлинности. /// </summary> /// <returns>Ответ сервера.</returns> StringBuilder UnsafeReceive() { StringBuilder tmp = new StringBuilder(); Encoding cenc = Encoding.ASCII; IAsyncResult asynResult; byte[] buf = new byte[1024]; int recv = 0; do { asynResult = _socket.BeginReceive(buf, 0, buf.Length, SocketFlags.None, null, null); if (asynResult.AsyncWaitHandle.WaitOne()) { recv = _socket.EndReceive(asynResult); string t = cenc.GetString(buf, 0, recv); tmp.Append(t); if (t.LastIndexOf(FiveOctalTerm) > 0) break; } } while(_socket.Poll(PollTimeout, SelectMode.SelectRead));
return tmp; } /// <summary> /// Получение ответа сервера без проверки подлинности. /// </summary> /// <returns>Ответ сервера.</returns> StringBuilder UnsafeReceiveMessage() { StringBuilder tmp = new StringBuilder(); Encoding cenc = Encoding.ASCII; IAsyncResult asynResult; byte[] buf = new byte[1024]; int recv = 0; do { asynResult = _socket.BeginReceive(buf, 0, buf.Length, SocketFlags.None, null, null); if (asynResult.AsyncWaitHandle.WaitOne()) { recv = _socket.EndReceive(asynResult); string t = cenc.GetString(buf, 0, recv); tmp.Append(t); //if (t.LastIndexOf(FiveOctalTerm) > 0) // break; } } while(!tmp.ToString().EndsWith(FiveOctalTerm));
return tmp; } /// <summary> /// Возвращение ответа сервера. /// </summary> /// <returns>Ответ сервера.</returns> string Receive() { StringBuilder tmp = UnsafeReceive(); string str = tmp.ToString(); AnalyseResponse(str);
return str; } /// <summary> /// Возвращение сообщения в виде строки. /// </summary> /// <returns></returns> string ReceiveMessage() { StringBuilder tmp = UnsafeReceiveMessage(); string str = tmp.ToString(); AnalyseResponse(str);
return str; } /// <summary> /// Аутентификация пользователя. /// </summary> /// <param name="username">Имя пользователя.</param> /// <param name="password">Пароль.</param> /// <example>После установки соединения сервер находится в режиме авторизации пользователя. /// Пользователь должен идентифицировать себя на сервере, используя команды USER и PASS. /// Сначала надо отправить команду USER, после которой в качестве аргумента следует имя пользователя. /// Если сервер отвечает положительно, то теперь необходимо отправить команду PASS, за которой следует пароль. /// <code> /// Client: USER username /// Server: +OK username /// Client: PASS mypass /// Server: +OK username /// </code> /// </example> void AuthenticateYourSelf(string username, string password) { Send("USER " + username); Receive(); Send("PASS " + password); Receive();
_authenticated = true; }
/// <summary> /// Соединение с сервером и аутентификация пользователя. /// </summary> /// <param name="username">Имя пользователя.</param> /// <param name="password">Пароль.</param> public override void LogIn(string username, string password) { try { if (_socket != null) { Quit(); ResetVariables(); } // Установка соеденения. EstablishConnection(_server, _port); Receive(); // Получение приветствия от сервера. AuthenticateYourSelf(username, password); } catch (ShutdownException e) { throw new CoreException("Невозможно завершить предыдущий сеанс.", e); } catch (Exception e) { throw new CoreException("Вход невозможен", e); } }
/// <summary> /// Закрытие транзакции на сервере. /// </summary> public override void Quit() { try { CheckConnection(); // Сервер завершает POP3-сессию и переходит в режим UPDATE. Send("QUIT"); // Ответ нас не интересует } catch (Exception e) { throw new ShutdownException("Невозможно покинуть транзакцию", e); }
CloseSocket(); }
/// <summary> /// Свойство закрытия соединения. /// </summary> void CloseSocket() { try { _socket.Shutdown(SocketShutdown.Both); _socket.Close(); // Свойство 'Connected' установлено в false, когда соединение закрыто. if (_socket.Connected) { throw new CoreException("При закрытии socket возникло исключение: " + Convert.ToString(System.Runtime.InteropServices.Marshal.GetLastWin32Error())); }
_socket = null; } catch (SocketException e) { throw new CoreException("Невозможно закрыть socket", e); } } /// <summary> /// Сброс переменных. /// </summary> void ResetVariables() { _authenticated = false; _status = null; _messagelist = null; }
/// <summary> /// Закрытие сеанса. /// </summary> public override void Dispose() { try { Quit(); ResetVariables(); } catch // Обработчик всех возникших исключений. { Debug.WriteLine("Невозможно закрыть socket"); }
GC.SuppressFinalize(this); } } }
Листинг 3.11.
Закрыть окно
Содержание  Назад  Вперед