using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading; namespace Ant.Service.Utilities { /// /// This class represents a generic Pop3 command and /// encapsulates the major operations when executing a /// Pop3 command against a Pop3 Server. /// /// internal abstract class Pop3Command : IDisposable where T : Pop3Response { public event Action Trace; protected void OnTrace(string message) { if (Trace != null) { Trace(message); } } private const int BufferSize = 1024; private const string MultilineMessageTerminator = "\r\n.\r\n"; private const string MessageTerminator = "."; private ManualResetEvent _manualResetEvent; private byte[] _buffer; private MemoryStream _responseContents; private Pop3State _validExecuteState; public Pop3State ValidExecuteState { get { return _validExecuteState; } } private Stream _networkStream; public Stream NetworkStream { get { return _networkStream; } set { _networkStream = value; } } bool _isMultiline; /// /// Sets a value indicating whether this instance is multiline. /// /// /// true if this instance is multiline; otherwise, false. /// protected bool IsMultiline { get { return _isMultiline; } set { _isMultiline = value; } } /// /// Initializes a new instance of the class. /// /// The stream. /// if set to true [is multiline]. /// State of the valid execute. public Pop3Command(Stream stream, bool isMultiline, Pop3State validExecuteState) { if (stream == null) { throw new ArgumentNullException("stream"); } _manualResetEvent = new ManualResetEvent(false); _buffer = new byte[BufferSize]; _responseContents = new MemoryStream(); _networkStream = stream; _isMultiline = isMultiline; _validExecuteState = validExecuteState; } /// /// Abstract method intended for inheritors to /// build out the byte[] request message for /// the specific command. /// /// The byte[] containing the request message. protected abstract byte[] CreateRequestMessage(); /// /// Sends the specified message. /// /// The message. private void Send(byte[] message) { //EnsureConnection(); try { _networkStream.Write(message, 0, message.Length); } catch (SocketException e) { throw new Pop3Exception("Unable to send the request message: " + Encoding.ASCII.GetString(message), e); } } /// /// Executes this instance. /// /// internal virtual T Execute(Pop3State currentState) { EnsurePop3State(currentState); byte[] message = CreateRequestMessage(); if (message != null) { Send(message); } T response = CreateResponse(GetResponse()); if (response == null) { return null; } OnTrace(response.HostMessage); return response; } /// /// Ensures the state of the POP3. /// /// State of the current. protected void EnsurePop3State(Pop3State currentState) { if (!((currentState & ValidExecuteState) == currentState)) { throw new Pop3Exception(string.Format("This command is being executed" + "in an invalid execution state. Current:{0}, Valid:{1}", currentState, ValidExecuteState)); } } /// /// Creates the response. /// /// The buffer. /// The Pop3Response containing the results of the /// Pop3 command execution. protected virtual T CreateResponse(byte[] buffer) { return Pop3Response.CreateResponse(buffer) as T; } /// /// Gets the response. /// /// private byte[] GetResponse() { //EnsureConnection(); AsyncCallback callback; if (_isMultiline) { callback = new AsyncCallback(GetMultiLineResponseCallback); } else { callback = new AsyncCallback(GetSingleLineResponseCallback); } try { Receive(callback); _manualResetEvent.WaitOne(); return _responseContents.ToArray(); } catch (SocketException e) { throw new Pop3Exception("Unable to get response.", e); } } /// /// Receives the specified callback. /// /// The callback. /// private IAsyncResult Receive(AsyncCallback callback) { return _networkStream.BeginRead(_buffer, 0, _buffer.Length, callback, null); } /// /// Writes the received bytes to buffer. /// /// The bytes received. /// private string WriteReceivedBytesToBuffer(int bytesReceived) { _responseContents.Write(_buffer, 0, bytesReceived); byte[] contents = _responseContents.ToArray(); return Encoding.ASCII.GetString(contents, (contents.Length > 5 ? contents.Length - 5 : 0), 5); } /// /// Gets the single line response callback. /// /// The ar. private void GetSingleLineResponseCallback(IAsyncResult ar) { int bytesReceived = _networkStream.EndRead(ar); string message = WriteReceivedBytesToBuffer(bytesReceived); if (message.EndsWith(Pop3Commands.Crlf)) { _manualResetEvent.Set(); } else { Receive(new AsyncCallback(GetSingleLineResponseCallback)); } } /// /// Gets the multi line response callback. /// /// The ar. private void GetMultiLineResponseCallback(IAsyncResult ar) { int bytesReceived = _networkStream.EndRead(ar); string message = WriteReceivedBytesToBuffer(bytesReceived); if (message.EndsWith(MultilineMessageTerminator) || bytesReceived == 0) //if the POP3 server times out we'll get an error message, then we'll get a following callback w/ 0 bytes. { _manualResetEvent.Set(); } else { Receive(new AsyncCallback(GetMultiLineResponseCallback)); } } /// /// Gets the request message. /// /// The args. /// A byte[] request message to send to the host. protected byte[] GetRequestMessage(params string[] args) { string message = string.Join(string.Empty, args); OnTrace(message); return Encoding.ASCII.GetBytes(message); } /// /// Strips the POP3 host message. /// /// The bytes. /// The header. /// A MemoryStream without the Pop3 server message. protected MemoryStream StripPop3HostMessage(byte[] bytes, string header) { int position = header.Length + 2; MemoryStream stream = new MemoryStream(bytes, position, bytes.Length - position); return stream; } /// /// Gets the response lines. /// /// The stream. /// A string[] of Pop3 response lines. protected string[] GetResponseLines(MemoryStream stream) { List lines = new List(); using (StreamReader reader = new StreamReader(stream)) { try { string line; do { line = reader.ReadLine(); //pop3 protocol states if a line starts w/ a //'.' that line will be byte stuffed w/ a '.' //if it is byte stuffed the remove the byte, //otherwise we have reached the end of the message. if (line.StartsWith(MessageTerminator)) { if (line == MessageTerminator) { break; } line = line.Substring(1); } lines.Add(line); } while (true); } catch (IOException e) { throw new Pop3Exception("Unable to get response lines.", e); } return lines.ToArray(); } } public void Dispose() { if (_responseContents != null) { _responseContents.Dispose(); } } } }