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();
}
}
}
}