Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1121 → Rev 1122

/TCPproxy/trunk/Network.cs
0,0 → 1,2308
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
 
// FIXME deny write access to all public properties
// FIXME for text/xml get encoding from body if not specified in header
// FIXME option to not store parsed data, just raw packets and reparse on demand
namespace TCPproxy
{
public enum LogLevel
{
Critical,
Error,
Warning,
Important,
Info,
Debug
}
 
public enum SocketState
{
None,
Connecting,
Connected,
ShutdownSend,
ShutdownReceived,
Closed
}
 
public enum TcpMessageDirection
{
Local,
Remote
}
 
public enum HttpVersion
{
V0_9,
V1_0,
V1_1
}
 
public enum HttpEncoding
{
Identify,
Gzip,
Compress,
Deflate,
Unknown
}
 
public class TcpLogEventArgs : EventArgs
{
private readonly LogLevel level;
private readonly string message;
private readonly Exception exception;
 
public LogLevel Level
{
get { return level; }
}
 
public string Message
{
get { return message; }
}
 
public Exception Exception
{
get { return exception; }
}
 
internal TcpLogEventArgs(LogLevel level, string message, Exception exception)
{
this.level = level;
this.message = message;
this.exception = exception;
}
}
 
public class TcpEventArgs : EventArgs
{
internal TcpEventArgs()
{
}
}
 
public class TcpConnectionEventArgs : EventArgs
{
private readonly TcpConnection tcp;
 
public TcpConnection Tcp
{
get { return tcp; }
}
 
internal TcpConnectionEventArgs(TcpConnection tcp)
{
this.tcp = tcp;
}
}
 
public class TcpHttpEventArgs : EventArgs
{
private readonly HttpMessage http;
 
public HttpMessage Http
{
get { return http; }
}
 
internal TcpHttpEventArgs(HttpMessage http)
{
this.http = http;
}
}
 
public delegate void TcpLogEventHandler(object sender, TcpLogEventArgs e);
 
public delegate void TcpEventHandler(object sender, TcpEventArgs e);
 
public delegate void TcpConnectionEventHandler(object sender, TcpConnectionEventArgs e);
 
public delegate void TcpHttpEventHandler(object sender, TcpHttpEventArgs e);
 
public class TcpListener
{
private int listenPort = -1;
private IPAddress resendHost;
private int resendPort = -1;
private Socket socket;
private int tcpId = 0;
private ArrayList tcpConnections = new ArrayList();
 
public TcpListener(int listenPort, IPAddress resendHost, int resendPort)
{
this.listenPort = listenPort;
this.resendHost = resendHost;
this.resendPort = resendPort;
}
 
public void StartListening()
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, listenPort));
socket.Listen(100);
socket.BeginAccept(new AsyncCallback(OnClientConnect), null);
SendLog(null, LogLevel.Important, "Listen on " + socket.LocalEndPoint);
}
 
public void StopListening()
{
try
{
if(socket != null)
{
SendLog(null, LogLevel.Important, "Stop listening " + socket.LocalEndPoint);
socket.Close();
}
}
catch(ObjectDisposedException) // socket is already closed
{
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
public void CancelAll()
{
ArrayList tcpConnectionsCopy;
 
lock(tcpConnections)
{
tcpConnectionsCopy = new ArrayList(tcpConnections);
}
 
foreach(TcpConnection tcp in tcpConnectionsCopy)
{
tcp.Cancel();
}
}
 
protected virtual void OnClientConnect(IAsyncResult asyn)
{
try
{
Socket worker = socket.EndAccept(asyn);
socket.BeginAccept(new AsyncCallback(OnClientConnect), null); // wait for next client
 
TcpConnection tcp = new TcpConnection(string.Format("{0:0000}", tcpId++));
tcp.Close += new TcpEventHandler(TcpConnectionClosed);
OnNewTcp(new TcpConnectionEventArgs(tcp));
 
lock(tcpConnections)
{
tcpConnections.Add(tcp);
}
 
tcp.Continue(resendHost, resendPort, worker);
}
catch(ObjectDisposedException) {} // socket is closed
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
protected virtual void TcpConnectionClosed(object sender, TcpEventArgs e)
{
lock(tcpConnections)
{
tcpConnections.Remove((TcpConnection)sender);
}
}
 
public event TcpConnectionEventHandler NewTcp;
 
protected virtual void OnNewTcp(TcpConnectionEventArgs e)
{
if(NewTcp != null)
{
NewTcp(this, e);
}
}
 
public event TcpLogEventHandler Log;
 
protected virtual void OnLog(TcpLogEventArgs e)
{
if(Log != null)
{
Log(this, e);
}
}
 
protected virtual void SendLog(TcpConnection tcp, LogLevel level, string message)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
OnLog(e);
}
 
protected virtual void SendLog(TcpConnection tcp, LogLevel level, Exception ex)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
OnLog(e);
}
}
 
public class TcpConnection
{
private string id;
private DateTime startTimestamp = DateTime.MinValue;
private DateTime localEndTimestamp = DateTime.MinValue;
private DateTime remoteEndTimestamp = DateTime.MinValue;
private IPEndPoint localPoint;
private IPEndPoint remotePoint;
private LinkedList messages = new LinkedList();
private SocketState localState = SocketState.None;
private SocketState remoteState = SocketState.None;
private SocketWorker worker;
private LinkedList https = new LinkedList();
private HttpParser httpParser;
 
public string Id
{
get { return id; }
}
 
public DateTime StartTimestamp
{
get { return startTimestamp; }
}
 
public DateTime LocalEndTimestamp
{
get { return localEndTimestamp; }
}
 
public DateTime RemoteEndTimestamp
{
get { return remoteEndTimestamp; }
}
 
public IPEndPoint LocalPoint
{
get { return localPoint; }
}
 
public IPEndPoint RemotePoint
{
get { return remotePoint; }
}
 
public LinkedList Messages
{
get { return messages; } // FIXME return read-only object
}
 
public SocketState LocalState
{
get { return localState; }
}
 
public SocketState RemoteState
{
get { return remoteState; }
}
 
private int CountBytes(TcpMessageDirection direction)
{
int count = 0;
 
foreach(TcpMessage message in messages)
{
if(message.Direction == direction)
count += message.Length;
}
return count;
}
 
public int SentBytes
{
get { return CountBytes(TcpMessageDirection.Local); }
}
 
public int ReceivedBytes
{
get { return CountBytes(TcpMessageDirection.Remote); }
}
 
internal TcpConnection(string id)
{
this.id = id;
this.httpParser = HttpParser.Parse(this);
}
 
internal void Continue(IPAddress resendHost, int resendPort, Socket localSocket)
{
SendLog(LogLevel.Important, "Client connected from " + ((IPEndPoint)localSocket.RemoteEndPoint).ToString());
SetLocalState(SocketState.Connected);
this.worker = new SocketWorker(resendHost, resendPort, localSocket, this);
}
 
public void Cancel()
{
worker.Cancel();
}
 
protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes)
{
return Append(direction, newBytes, newBytes.Length);
}
 
protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes, int length)
{
if(newBytes == null) return null;
 
TcpMessage message;
 
lock(this)
{
message = new TcpMessage();
message.Direction = direction;
messages.Add(message);
message.Append(newBytes, length);
}
 
httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
 
return message;
}
 
internal void AddHttpMessage(HttpMessage http)
{
lock(this)
{
https.Add(http);
TcpHttpEventArgs e = new TcpHttpEventArgs(http);
OnNewHttp(e);
}
}
 
 
public override string ToString() // FIXME delete the method
{
return id + " " + startTimestamp.ToString("HH:mm:ss.ffff");
}
 
 
protected void SetLocalPoint(IPEndPoint localPoint)
{
this.localPoint = localPoint;
OnUpdate(new TcpEventArgs());
}
 
protected void SetRemotePoint(IPEndPoint remotePoint)
{
this.remotePoint = remotePoint;
OnUpdate(new TcpEventArgs());
}
 
protected void SetLocalState(SocketState localState)
{
if(this.localState == SocketState.None && localState == SocketState.Connecting)
{
startTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.None && localState == SocketState.Connected)
{
startTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.None && localState == SocketState.Closed)
{
}
else if(this.localState == SocketState.Connecting && localState == SocketState.Connected)
{
}
else if(this.localState == SocketState.Connecting && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownSend)
{
}
else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownReceived)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Connected && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownSend && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownSend && localState == SocketState.ShutdownReceived)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.ShutdownSend)
{
}
else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Closed && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else
{
throw new Exception("Wrong local socket state change: from " + this.localState + " to " + localState);
}
this.localState = localState;
if(this.localState == SocketState.Closed) httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
}
 
protected void SetRemoteState(SocketState remoteState)
{
if(this.remoteState == SocketState.None && remoteState == SocketState.Connecting)
{
}
else if(this.remoteState == SocketState.None && remoteState == SocketState.Connected)
{
}
else if(this.remoteState == SocketState.None && remoteState == SocketState.Closed)
{
}
else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Connected)
{
}
else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownSend)
{
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownReceived)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.ShutdownReceived)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.ShutdownSend)
{
}
else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Closed && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else
{
throw new Exception("Wrong remote socket state change: from " + this.remoteState + " to " + remoteState);
}
this.remoteState = remoteState;
if(this.remoteState == SocketState.Closed) httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
}
 
public event TcpEventHandler Update;
 
protected virtual void OnUpdate(TcpEventArgs e)
{
if(Update != null)
{
Update(this, e);
}
}
 
public event TcpHttpEventHandler NewHttp;
 
protected virtual void OnNewHttp(TcpHttpEventArgs e)
{
if(NewHttp != null)
{
NewHttp(this, e);
}
}
 
public event TcpEventHandler Close;
 
protected virtual void OnClose(TcpEventArgs e)
{
if(Close != null)
{
Close(this, e);
}
}
 
public event TcpLogEventHandler Log;
 
protected virtual void OnLog(TcpLogEventArgs e)
{
if(Log != null)
{
Log(this, e);
}
}
 
protected virtual void SendLog(LogLevel level, string message)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
OnLog(e);
}
 
protected virtual void SendLog(LogLevel level, Exception ex)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
OnLog(e);
}
 
protected class SocketWorker
{
private enum SendCommandType
{
Send,
Shutdown,
Reset
}
 
private class SendCommand
{
public byte[] buffer = null;
public int length = 0;
public SendCommandType cmdType = SendCommandType.Send;
}
 
private static int BUF_SIZE = 2048;
 
private TcpConnection tcp;
private Socket localSocket;
private Socket remoteSocket;
private byte[] localDataBuffer;
private byte[] remoteDataBuffer;
private AsyncCallback receiveLocalMethod;
private AsyncCallback receiveRemoteMethod;
private Queue localSendQueue = new Queue();
private Queue remoteSendQueue = new Queue();
private AutoResetEvent localSendEvent = new AutoResetEvent(false);
private AutoResetEvent remoteSendEvent = new AutoResetEvent(false);
private bool localSocketSendShutdown = false;
private bool localSocketReceiveShutdown = false;
private bool remoteSocketSendShutdown = false;
private bool remoteSocketReceiveShutdown = false;
private ManualResetEvent remoteSocketEvent = new ManualResetEvent(false);
private Thread localSendThread;
private Thread remoteSendThread;
 
public SocketWorker(IPAddress resendHost, int resendPort, Socket localSocket, TcpConnection tcp)
{
try
{
tcp.SendLog(LogLevel.Debug, string.Format("Local socket: {0}:{1} <-> {2}:{3}",
((IPEndPoint)localSocket.LocalEndPoint).Address,
((IPEndPoint)localSocket.LocalEndPoint).Port,
((IPEndPoint)localSocket.RemoteEndPoint).Address,
((IPEndPoint)localSocket.RemoteEndPoint).Port));
 
this.localSocket = localSocket;
this.tcp = tcp;
receiveLocalMethod = new AsyncCallback(OnLocalReceived);
receiveRemoteMethod = new AsyncCallback(OnRemoteReceived);
 
tcp.SetLocalPoint((IPEndPoint)localSocket.RemoteEndPoint);
this.localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
 
localSendThread = new Thread(new ThreadStart(LocalSendProc));
localSendThread.Name = "SocketWorker.LocalSendProc";
localSendThread.Start();
 
ContinueLocalReceive();
 
if(resendHost == null)
{
remoteSocket = null;
}
else
{
tcp.SetRemoteState(SocketState.Connecting);
 
IPEndPoint point = new IPEndPoint(resendHost, resendPort);
remoteSocket = new Socket(point.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
remoteSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
remoteSocket.Connect(point);
tcp.SetRemoteState(SocketState.Connected);
tcp.SetRemotePoint((IPEndPoint)remoteSocket.RemoteEndPoint);
 
remoteSendThread = new Thread(new ThreadStart(RemoteSendProc));
remoteSendThread.Name = "SocketWorker.RemoteSendProc";
remoteSendThread.Start();
 
ContinueRemoteReceive();
remoteSocketEvent.Set(); // remote socket ready to send data
tcp.SendLog(LogLevel.Info, "Connected to server " + tcp.RemotePoint.ToString());
 
tcp.SendLog(LogLevel.Debug, string.Format("Remote socket: {0}:{1} <-> {2}:{3}",
((IPEndPoint)remoteSocket.LocalEndPoint).Address,
((IPEndPoint)remoteSocket.LocalEndPoint).Port,
((IPEndPoint)remoteSocket.RemoteEndPoint).Address,
((IPEndPoint)remoteSocket.RemoteEndPoint).Port));
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void ContinueLocalReceive()
{
try
{
localDataBuffer = new byte[BUF_SIZE];
localSocket.BeginReceive(localDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveLocalMethod, this);
}
catch(ObjectDisposedException ex) // the socket is closed
{
tcp.SendLog(LogLevel.Info, ex);
Cancel();
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void ContinueRemoteReceive()
{
try
{
remoteDataBuffer = new byte[BUF_SIZE];
remoteSocket.BeginReceive(remoteDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveRemoteMethod, this);
}
catch(ObjectDisposedException ex) // the socket is closed
{
tcp.SendLog(LogLevel.Info, ex);
Cancel();
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void CheckLocalSocket()
{
lock(localSocket)
{
try
{
if(localSocketReceiveShutdown && localSocketSendShutdown)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
 
private void CheckRemoteSocket()
{
lock(remoteSocket)
{
try
{
if(remoteSocketReceiveShutdown && remoteSocketSendShutdown)
{
if(remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
 
private void OnLocalReceived(IAsyncResult asyn)
{
try
{
int bytesReceived = 0;
bool reset = false;
 
try
{
bytesReceived = localSocket.EndReceive(asyn);
}
catch(ObjectDisposedException)
{
reset = true;
}
catch(SocketException ex)
{
if(ex.ErrorCode == 10054)
reset = true;
else
throw ex;
}
 
if(reset)
{
tcp.SendLog(LogLevel.Info, "Got reset from local end");
 
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Reset;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
}
else if(bytesReceived <= 0)
{
tcp.SendLog(LogLevel.Info, "Got showdown from local end");
 
localSocket.Shutdown(SocketShutdown.Receive);
tcp.SetLocalState(SocketState.ShutdownReceived);
localSocketReceiveShutdown = true;
CheckLocalSocket();
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Shutdown;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
}
else
{
tcp.SendLog(LogLevel.Debug, string.Format("Local received {0} bytes", bytesReceived));
 
SendCommand cmd = new SendCommand();
cmd.buffer = localDataBuffer;
cmd.length = bytesReceived;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
ContinueLocalReceive();
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void LocalSendProc()
{
try
{
while(true)
{
SendCommand cmd;
 
if(localSendQueue.Count == 0)
{
localSendEvent.WaitOne();
}
 
lock(localSendQueue)
{
localSendEvent.Reset();
cmd = (SendCommand)localSendQueue.Dequeue();
}
 
if(cmd.cmdType == SendCommandType.Reset) // reset marker
{
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send reset to remote end"));
lock(remoteSocket)
{
if(!remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
 
break; // no more send allowed
}
else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
{
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to remote end"));
remoteSocket.Shutdown(SocketShutdown.Send);
tcp.SetRemoteState(SocketState.ShutdownSend);
remoteSocketSendShutdown = true;
CheckRemoteSocket();
 
break; // no more send allowed
}
else
{
// store received bytes
tcp.Append(TcpMessageDirection.Local, cmd.buffer, cmd.length);
 
// forward it
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to remote end", cmd.length));
remoteSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
}
}
}
catch(ThreadAbortException)
{
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void OnRemoteReceived(IAsyncResult asyn)
{
try
{
int bytesReceived = 0;
bool reset = false;
 
try
{
bytesReceived = remoteSocket.EndReceive(asyn);
}
catch(ObjectDisposedException)
{
reset = true;
}
catch(SocketException ex)
{
if(ex.ErrorCode == 10054)
reset = true;
else
throw ex;
}
 
if(reset)
{
tcp.SendLog(LogLevel.Info, "Got reset from remote end");
 
lock(remoteSocket)
{
if(remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Reset;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
}
else if(bytesReceived <= 0)
{
tcp.SendLog(LogLevel.Info, "Got showdown from remote end");
 
remoteSocket.Shutdown(SocketShutdown.Receive);
tcp.SetRemoteState(SocketState.ShutdownReceived);
remoteSocketReceiveShutdown = true;
CheckRemoteSocket();
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Shutdown;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
}
else
{
tcp.SendLog(LogLevel.Debug, string.Format("Remote received {0} bytes", bytesReceived));
 
SendCommand cmd = new SendCommand();
cmd.buffer = remoteDataBuffer;
cmd.length = bytesReceived;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
ContinueRemoteReceive();
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void RemoteSendProc()
{
try
{
while(true)
{
SendCommand cmd;
 
if(remoteSendQueue.Count == 0)
{
remoteSendEvent.WaitOne();
}
 
lock(remoteSendQueue)
{
remoteSendEvent.Reset();
cmd = (SendCommand)remoteSendQueue.Dequeue();
}
 
if(cmd.cmdType == SendCommandType.Reset) // reset marker
{
tcp.SendLog(LogLevel.Debug, string.Format("Send reset to local end"));
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
 
break; // no more send allowed
}
else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
{
tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to local end"));
localSocket.Shutdown(SocketShutdown.Send);
tcp.SetLocalState(SocketState.ShutdownSend);
localSocketSendShutdown = true;
CheckLocalSocket();
 
break; // no more send allowed
}
else
{
// store received bytes
tcp.Append(TcpMessageDirection.Remote, cmd.buffer, cmd.length);
 
// forward it
tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to local end", cmd.length));
localSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
}
}
}
catch(ThreadAbortException)
{
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
public void Cancel()
{
tcp.SendLog(LogLevel.Important, "Connection canceled");
 
try
{
if(localSendThread != null && localSendThread.IsAlive) localSendThread.Abort();
if(remoteSendThread != null && remoteSendThread.IsAlive) remoteSendThread.Abort();
 
// close sockets
try
{
if(localSocket != null)
{
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
}
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
tcp.SetLocalState(SocketState.Closed);
 
try
{
if(remoteSocket != null)
{
lock(remoteSocket)
{
if(remoteSocket.Connected) remoteSocket.Close();
}
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
tcp.SetRemoteState(SocketState.Closed);
 
// return
tcp.OnClose(new TcpEventArgs());
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
}
 
internal class HttpParser
{
private enum HttpCharType
{
None,
Control,
Digit,
UpAlpha,
LoAlpha,
NonChar,
Separator,
CrLf,
}
 
private static HttpCharType[] charTypes = null;
private static bool[] tokenChars = null;
private static char[] charValues = {
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\0'
};
 
private static void InitTables()
{
if(charTypes != null) return;
 
// main table
charTypes = new HttpCharType[256];
 
for(int i = 0; i < charTypes.Length; i++) charTypes[i] = HttpCharType.None;
 
for(int i = 0; i <= 31; i++) charTypes[i] = HttpCharType.Control;
charTypes[127] = HttpCharType.Control; // <del>
 
for(int i = 48; i <= 57; i++) charTypes[i] = HttpCharType.Digit;
 
for(int i = 65; i <= 90; i++) charTypes[i] = HttpCharType.UpAlpha;
for(int i = 97; i <= 122; i++) charTypes[i] = HttpCharType.LoAlpha;
 
for(int i = 128; i < charTypes.Length; i++) charTypes[i] = HttpCharType.NonChar;
 
charTypes[ 40] = HttpCharType.Separator; // (
charTypes[ 41] = HttpCharType.Separator; // )
charTypes[ 60] = HttpCharType.Separator; // <
charTypes[ 62] = HttpCharType.Separator; // >
charTypes[ 64] = HttpCharType.Separator; // @
charTypes[ 44] = HttpCharType.Separator; // ,
charTypes[ 59] = HttpCharType.Separator; // ;
charTypes[ 58] = HttpCharType.Separator; // :
charTypes[ 92] = HttpCharType.Separator; // \
charTypes[ 34] = HttpCharType.Separator; // "
charTypes[ 47] = HttpCharType.Separator; // /
charTypes[ 91] = HttpCharType.Separator; // [
charTypes[ 93] = HttpCharType.Separator; // ]
charTypes[ 63] = HttpCharType.Separator; // ?
charTypes[ 61] = HttpCharType.Separator; // =
charTypes[123] = HttpCharType.Separator; // {
charTypes[125] = HttpCharType.Separator; // }
charTypes[ 32] = HttpCharType.Separator; // <space>
charTypes[ 9] = HttpCharType.Separator; // <tab>
 
charTypes[ 13] = HttpCharType.CrLf; // <CR>
charTypes[ 10] = HttpCharType.CrLf; // <LF>
 
// token table
tokenChars = new bool[256];
for(int i = 0; i < tokenChars.Length; i++)
{
tokenChars[i] = !(charTypes[i] == HttpCharType.NonChar
|| charTypes[i] == HttpCharType.Control || charTypes[i] == HttpCharType.Separator
|| charTypes[i] == HttpCharType.CrLf);
}
}
 
private class ParsePosition
{
private TcpConnection messages;
private IEnumerator messagesEnum;
private TcpMessage tcp = null;
private int tcpPos;
private int tcpLen;
private bool tcpEnd = false;
private AutoResetEvent newMessageEvent;
private AutoResetEvent nextMessageEvent;
 
public ParsePosition(TcpConnection messages, AutoResetEvent nextMessageEvent)
{
this.messages = messages;
this.messagesEnum = messages.Messages.GetEnumerator();
this.newMessageEvent = new AutoResetEvent(false);
this.nextMessageEvent = nextMessageEvent;
}
 
public AutoResetEvent NewMessageEvent
{
get { return newMessageEvent; }
}
 
public bool IsEnd
{
get { return tcpEnd; }
}
 
public TcpMessage CurrentMessage
{
get { return tcp; }
}
 
private bool MoveNext()
{
for(bool moved = false; !moved; )
{
lock(messages)
{
newMessageEvent.Reset();
moved = messagesEnum.MoveNext();
}
 
if(moved) break;
 
if(!newMessageEvent.WaitOne())
throw new Exception("Cannot get next TCP message");
 
lock(messages)
{
if(messages.LocalState == SocketState.Closed && messages.RemoteState == SocketState.Closed)
return false;
 
moved = messagesEnum.MoveNext();
}
}
 
return true;
}
 
private void NextTcp()
{
if(tcpEnd) return;
 
TcpMessage newTcp = null;
 
do
{
if(!MoveNext())
{
tcpEnd = true;
break;
}
newTcp = (TcpMessage)messagesEnum.Current;
}
while(tcp != null && tcp.Direction != newTcp.Direction);
 
if(!tcpEnd)
{
tcp = newTcp;
tcpLen = tcp.Length;
tcpPos = 0;
if(nextMessageEvent != null) nextMessageEvent.Set();
}
}
 
public byte CurrentOctet()
{
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
}
 
public byte NextOctet()
{
tcpPos++;
 
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
}
 
public void SetDirection(TcpMessageDirection direction)
{
do
{
if(!MoveNext())
{
tcp = null;
tcpEnd = true;
break;
}
tcp = (TcpMessage)messagesEnum.Current;
}
while(tcp.Direction != direction);
 
if(!tcpEnd)
{
tcpLen = tcp.Length;
tcpPos = 0;
}
}
}
 
private TcpConnection messages;
private AutoResetEvent requestEvent = new AutoResetEvent(false); // new request found
private AutoResetEvent nextMessageEvent = new AutoResetEvent(false); // request goes to next TCP message
private LinkedList https = new LinkedList();
private ParsePosition requestPos;
private ParsePosition responsePos;
private AutoResetEvent newMessageEvent = new AutoResetEvent(false); // new TCP message available
private Thread runThread;
 
public static HttpParser Parse(TcpConnection messages)
{
HttpParser parser = new HttpParser(messages);
parser.runThread = new Thread(new ThreadStart(parser.Run));
parser.RunThread.Name = "HttpParser.Run";
parser.runThread.Start();
 
return parser;
}
 
public void NewMessageArived()
{
requestPos.NewMessageEvent.Set();
responsePos.NewMessageEvent.Set();
}
 
public Thread RunThread
{
get { return runThread; }
}
 
private HttpParser(TcpConnection messages)
{
this.messages = messages;
InitTables();
}
 
/// <summary>
/// Try to recognize the stored TCP packets as sequence of HTTP messages (request-response)
/// </summary>
private void Run()
{
Thread responseThread = null;
 
try
{
requestPos = new ParsePosition(messages, nextMessageEvent);
responsePos = new ParsePosition(messages, null);
 
responseThread = new Thread(new ThreadStart(MatchResponses));
responseThread.Name = "HttpParser.MatchResponses";
responseThread.Start();
 
// find requests
while(!requestPos.IsEnd)
{
HttpMessage http = new HttpMessage();
lock(https)
{
https.Add(http);
requestEvent.Set(); // new request available
}
 
messages.AddHttpMessage(http);
SkipEmptyLines(requestPos);
 
ParseRequestLine(requestPos, http);
http.UpdateHttpMessage();
 
ParseHeaders(requestPos, http, true);
SetRequestProperties(http);
http.UpdateHttpMessage();
 
bool fullLength = ParseBody(requestPos, http, true);
if("text" == http.RequestContentType && "xml" == http.RequestContentSubtype)
{
http.RequestXml = new XmlMessage(http.RequestText);
}
http.UpdateHttpMessage();
 
if(fullLength) requestPos.NextOctet();
http.RequestComplete = true;
http.UpdateHttpMessage();
 
SkipEmptyLines(requestPos);
}
 
responseThread.Join();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
if(responseThread != null) responseThread.Abort();
}
}
 
private void MatchResponses()
{
try
{
IEnumerator httpEnum = https.GetEnumerator();
 
if(!nextMessageEvent.WaitOne()) throw new Exception("Cannot get first message of request");
 
responsePos.SetDirection(requestPos.CurrentMessage.Direction == TcpMessageDirection.Local
? TcpMessageDirection.Remote : TcpMessageDirection.Local);
 
while(!responsePos.IsEnd)
{
bool moved;
 
lock(https)
{
requestEvent.Reset();
moved = httpEnum.MoveNext();
}
 
if(!moved)
{
if(!requestEvent.WaitOne())
throw new Exception("Cannot get next request");
 
lock(https)
{
if(!httpEnum.MoveNext())
throw new Exception("Tried to find response by no HTTP message available");
}
}
 
HttpMessage http = (HttpMessage)httpEnum.Current;
 
ParseResponseLine(responsePos, http);
http.UpdateHttpMessage();
 
ParseHeaders(responsePos, http, false);
SetResponseProperties(http);
http.UpdateHttpMessage();
 
bool fullLength = ParseBody(responsePos, http, false);
if("text" == http.ResponseContentType && "xml" == http.ResponseContentSubtype)
{
http.ResponseXml = new XmlMessage(http.ResponseText);
}
http.UpdateHttpMessage();
 
if(fullLength) responsePos.NextOctet();
http.ResponseComplete = true;
http.UpdateHttpMessage();
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
private string GetToken(ParsePosition pos, int limit)
{
StringBuilder res = new StringBuilder(100);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && tokenChars[b]; b = pos.NextOctet())
{
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
 
return res.ToString();
}
 
private string GetUntilSpace(ParsePosition pos, int limit)
{
StringBuilder res = new StringBuilder(1024);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && b != 32 && b != 13 && b != 10; b = pos.NextOctet())
{ // <space> <cr> <lf>
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
 
return res.ToString();
}
 
private string GetUntilEoL(ParsePosition pos, int limit)
{
StringBuilder res = new StringBuilder(1024);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && b != 13; b = pos.NextOctet())
{ // <cr>
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
 
return res.ToString();
}
 
private void ExpectSpace(ParsePosition pos)
{
if(pos.IsEnd || pos.CurrentOctet() != 32)
throw new HttpParseException("Space expected");
 
pos.NextOctet();
}
 
private void ExpectCRLF(ParsePosition pos)
{
if(pos.IsEnd || pos.CurrentOctet() != 13)
throw new HttpParseException("Carriage return expected");
if(pos.IsEnd || pos.NextOctet() != 10)
throw new HttpParseException("Linefeed expected");
 
pos.NextOctet();
}
 
private void SkipEmptyLines(ParsePosition pos)
{
while(pos.CurrentOctet() == 13)
ExpectCRLF(pos);
}
 
private void ParseRequestLine(ParsePosition pos, HttpMessage http)
{
// method
http.RequestMethod = GetToken(pos, 1024);
if(http.RequestMethod == null || http.RequestMethod.Length == 0)
throw new HttpParseException("Request method name expected");
ExpectSpace(pos);
 
// URI
http.RequestUri = GetUntilSpace(pos, 1024);
if(http.RequestUri == null || http.RequestUri.Length == 0)
throw new HttpParseException("Request URI expected");
 
if(pos.IsEnd)
throw new HttpParseException("Unexpected end of message");
 
// EoL or version
byte b = pos.CurrentOctet();
if(b == 13)
{
if(pos.IsEnd || pos.NextOctet() != 10)
{
throw new HttpParseException("Linefeed expected");
}
else
{
if(!pos.IsEnd) ExpectCRLF(pos);
http.RequestVersion = HttpVersion.V0_9;
return;
}
}
else if(b != 32)
{
throw new HttpParseException("HTTP version expected");
}
pos.NextOctet();
 
// check version
string versionStr = GetUntilEoL(pos, 20);
if(pos.IsEnd || versionStr == null || versionStr.Length == 0)
throw new HttpParseException("HTTP version expected");
 
if(versionStr == "HTTP/1.0")
{
http.RequestVersion = HttpVersion.V1_0;
}
else if(versionStr == "HTTP/1.1")
{
http.RequestVersion = HttpVersion.V1_1;
}
else
{
throw new HttpParseException("Unknown HTTP version: " + versionStr);
}
 
ExpectCRLF(pos);
}
 
private void ParseHeaders(ParsePosition pos, HttpMessage http, bool request)
{
if(pos.IsEnd) return; // end of TCP messages
 
while(true)
{
if(pos.IsEnd)
throw new HttpParseException("Unexpected end of message");
 
if(pos.CurrentOctet() == 13)
{
if(pos.IsEnd || pos.NextOctet() != 10)
{
throw new HttpParseException("Linefeed expected");
}
else
{
pos.NextOctet(); // end of header, move to body
return;
}
}
if(pos.IsEnd) return; // end of TCP messages
 
string name = GetToken(pos, 0);
if(name == null || name.Length == 0)
throw new HttpParseException("Request header name expected");
 
if(pos.IsEnd || pos.CurrentOctet() != 58) // :
throw new HttpParseException("Request header value expected");
 
pos.NextOctet();
string s = TrimHeaderValue(GetUntilEoL(pos, 0));
 
ExpectCRLF(pos);
 
if(request)
http.AddRequestHeader(name, s);
else
http.AddResponseHeader(name, s);
}
}
 
enum HeaderValueState
{
Space,
Token,
Quoted
}
 
private string TrimHeaderValue(string s)
{
if(s == null) return null;
 
HeaderValueState state = HeaderValueState.Space;
StringBuilder buf = new StringBuilder();
 
for(int i = 0, l = s.Length; i < l; i++)
{
char c = s[i];
switch(state)
{
case HeaderValueState.Space:
if(c != ' ' && c != '\t')
{
if(c == '"')
{
if(buf.Length > 0) buf.Append(' ');
buf.Append(c);
state = HeaderValueState.Quoted;
}
else
{
if(buf.Length > 0) buf.Append(' ');
buf.Append(c);
state = HeaderValueState.Token;
}
}
break;
 
case HeaderValueState.Token:
if(c == ' ' || c == '\t')
{
state = HeaderValueState.Space;
}
else if(c == '"')
{
buf.Append(c);
state = HeaderValueState.Quoted;
}
else
{
buf.Append(c);
}
break;
 
case HeaderValueState.Quoted:
if(c == '"')
{
buf.Append(c);
i++;
if(i < l)
{
c = s[i];
if(c == ' ' || c == '\t')
{
state = HeaderValueState.Space;
}
else if(c == '"')
{
buf.Append(c);
state = HeaderValueState.Quoted;
}
else
{
buf.Append(c);
state = HeaderValueState.Token;
}
}
}
else
{
buf.Append(c);
}
break;
}
}
 
return buf.ToString();
}
 
private HttpEncoding ParseEncoding(string encoding)
{
if(encoding == null || encoding == "identity")
return HttpEncoding.Identify;
else if(encoding == "gzip")
return HttpEncoding.Gzip;
else if(encoding == "compress")
return HttpEncoding.Compress;
else if(encoding == "deflate")
return HttpEncoding.Deflate;
else
return HttpEncoding.Unknown;
}
 
private void SetRequestProperties(HttpMessage http)
{
// length
string contentLength = (string)http.RequestHeadersHash["Content-Length"];
if(contentLength != null)
{
http.RequestLength = int.Parse(contentLength);
}
 
// encoding
http.RequestEncoding = ParseEncoding((string)http.RequestHeadersHash["Content-Encoding"]);
 
// type & charset
string contentType = (string)http.RequestHeadersHash["Content-Type"];
if(contentType != null)
{
Match match = Regex.Match(contentType, @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
if(match.Success)
{
http.RequestContentType = match.Groups[1].Captures[0].Value;
http.RequestContentSubtype = match.Groups[2].Captures[0].Value;
if(match.Groups.Count >= 6) http.RequestCharset = match.Groups[5].Captures[0].Value.Trim('"');
}
}
 
// soap action
string soapAction = (string)http.RequestHeadersHash["soapaction"];
if(soapAction != null)
{
http.SoapAction = soapAction.Trim('"');
}
}
 
private bool ParseBody(ParsePosition pos, HttpMessage http, bool request)
{
if(request && http.RequestMethod != "POST") return false;
 
// FIXME parse and save on-the-fly, dont wait util end of message
 
byte[] bin = new byte[8*1024];
int len = 0;
int limit = (request ? http.RequestLength : http.ResponseLength);
HttpEncoding encoding = (request ? http.RequestEncoding : http.ResponseEncoding);
string contentType = (request ? http.RequestContentType : http.ResponseContentType);
string contentSubtype = (request ? http.RequestContentSubtype : http.ResponseContentSubtype);
string charset = (request ? http.RequestCharset : http.ResponseCharset);
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd; b = pos.NextOctet())
{
if(len >= bin.Length)
{
byte[] newBin = new byte[bin.Length*2];
Array.Copy(bin, newBin, len);
bin = newBin;
}
 
bin[len++] = b;
if(limit > 0 && limit <= len) // full length
{
break;
}
}
 
string text = null;
if(encoding == HttpEncoding.Identify && contentType == "text")
{
try
{
Encoding enc = Encoding.GetEncoding(charset == null ? (contentSubtype == "xml" ? "UTF-8" : "ASCII") : charset);
text = enc.GetString(bin, 0, len);
}
catch(NotSupportedException)
{
Console.WriteLine("Unsupported encoding: " + charset);
}
}
 
if(request)
{
http.RequestLength = len;
http.RequestBody = bin;
http.RequestText = text;
}
else
{
http.ResponseLength = len;
http.ResponseBody = bin;
http.ResponseText = text;
}
 
return (limit > 0 && limit <= len); // full length reached, need to go to next octet
}
 
private void ParseResponseLine(ParsePosition pos, HttpMessage http)
{
// version
string versionStr = GetUntilSpace(pos, 20);
if(pos.IsEnd || versionStr == null || versionStr.Length == 0)
throw new HttpParseException("HTTP version expected");
 
if(versionStr == "HTTP/1.0")
{
http.ResponseVersion = HttpVersion.V1_0;
}
else if(versionStr == "HTTP/1.1")
{
http.ResponseVersion = HttpVersion.V1_1;
}
else
{
throw new HttpParseException("Unknown HTTP version: " + versionStr);
}
ExpectSpace(pos);
 
// status code
string code = GetToken(pos, 3);
if(code == null || code.Length != 3)
throw new HttpParseException("Status code expected");
 
try
{
int c = int.Parse(code);
if(c < 100 || c >= 1000) throw new HttpParseException("Status code expected");
http.ResponseStatusCode = c;
}
catch(FormatException)
{
throw new HttpParseException("Status code expected");
}
ExpectSpace(pos);
 
// status message
http.ResponseStatusMessage = GetUntilEoL(pos, 0);
 
if(pos.IsEnd)
throw new HttpParseException("Unexpected end of message");
 
ExpectCRLF(pos);
}
 
private void SetResponseProperties(HttpMessage http)
{
// length
HttpHeader contentLength = (HttpHeader)http.ResponseHeadersHash["Content-Length"];
if(contentLength != null)
{
http.ResponseLength = int.Parse(contentLength.Values[0]);
}
 
// encoding
HttpHeader contentEncoding = (HttpHeader)http.ResponseHeadersHash["Content-Encoding"];
http.ResponseEncoding = ParseEncoding((contentEncoding == null) ? null : contentEncoding.Values[0]);
 
// type & charset
HttpHeader contentType = (HttpHeader)http.ResponseHeadersHash["Content-Type"];
if(contentType != null)
{
Match match = Regex.Match(contentType.Values[0], @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
if(match.Success)
{
http.ResponseContentType = match.Groups[1].Captures[0].Value;
http.ResponseContentSubtype = match.Groups[2].Captures[0].Value;
if(match.Groups.Count >= 6 && match.Groups[5].Captures.Count > 0)
http.ResponseCharset = match.Groups[5].Captures[0].Value.Trim('"');
}
}
}
}
 
public class HttpHeader
{
private string name;
private string[] headerValues;
 
public string Name
{
get { return name; }
}
 
public string[] Values
{
get { return headerValues; }
}
 
internal HttpHeader()
{
}
 
internal HttpHeader(string name, string headerValue)
{
this.name = name;
AddValue(headerValue);
}
 
internal void AddValue(string value)
{
if(headerValues == null)
{
headerValues = new string[1];
}
else
{
string[] newValues = new string[headerValues.Length + 1];
Array.Copy(headerValues, 0, newValues, 0, headerValues.Length);
headerValues = newValues;
}
 
headerValues[headerValues.Length-1] = value;
}
}
 
public class HttpMessage
{
private bool requestComplete = false;
private HttpVersion requestVersion;
private string requestMethod;
private string requestUri;
private LinkedList requestHeaders = new LinkedList();
private Hashtable requestHeadersHash = new Hashtable();
private int requestLength = -1; // -1 == unknown
private HttpEncoding requestEncoding = HttpEncoding.Identify;
private string requestContentType;
private string requestContentSubtype;
private string requestCharset;
private string soapAction;
private byte[] requestBody;
private string requestText;
private XmlMessage requestXml;
 
private bool responseComplete = false;
private HttpVersion responseVersion;
private int responseStatusCode;
private string responseStatusMessage;
private LinkedList responseHeaders = new LinkedList();
private Hashtable responseHeadersHash = new Hashtable();
private int responseLength = -1; // -1 == unknown
private HttpEncoding responseEncoding = HttpEncoding.Identify;
private string responseContentType;
private string responseContentSubtype;
private string responseCharset;
private byte[] responseBody;
private string responseText;
private XmlMessage responseXml;
 
public bool RequestComplete
{
get { return requestComplete; }
set { requestComplete = value; }
}
 
public HttpVersion RequestVersion
{
get { return requestVersion; }
set { requestVersion = value; }
}
 
public string RequestMethod
{
get { return requestMethod; }
set { requestMethod = value; }
}
 
public string RequestUri
{
get { return requestUri; }
set { requestUri = value; }
}
 
public LinkedList RequestHeaders
{
get { return requestHeaders; }
}
 
public IDictionary RequestHeadersHash
{
get { return requestHeadersHash; }
}
 
public int RequestLength
{
get { return requestLength; }
set { requestLength = value; }
}
 
public HttpEncoding RequestEncoding
{
get { return requestEncoding; }
set { requestEncoding = value; }
}
 
public string RequestContentType
{
get { return requestContentType; }
set { requestContentType = value; }
}
 
public string RequestContentSubtype
{
get { return requestContentSubtype; }
set { requestContentSubtype = value; }
}
 
public string RequestCharset
{
get { return requestCharset; }
set { requestCharset = value; }
}
 
public string SoapAction
{
get { return soapAction; }
set { soapAction = value; }
}
 
public byte[] RequestBody
{
get { return requestBody; }
set { requestBody = value; }
}
 
public string RequestText
{
get { return requestText; }
set { requestText = value; }
}
 
public XmlMessage RequestXml
{
get { return requestXml; }
set { requestXml = value; }
}
 
public bool ResponseComplete
{
get { return responseComplete; }
set { responseComplete = value; }
}
 
public HttpVersion ResponseVersion
{
get { return responseVersion; }
set { responseVersion = value; }
}
 
public int ResponseStatusCode
{
get { return responseStatusCode; }
set { responseStatusCode = value; }
}
 
public string ResponseStatusMessage
{
get { return responseStatusMessage; }
set { responseStatusMessage = value; }
}
 
public LinkedList ResponseHeaders
{
get { return responseHeaders; }
}
 
public IDictionary ResponseHeadersHash
{
get { return responseHeadersHash; }
}
 
public int ResponseLength
{
get { return responseLength; }
set { responseLength = value; }
}
 
public HttpEncoding ResponseEncoding
{
get { return responseEncoding; }
set { responseEncoding = value; }
}
 
public string ResponseContentType
{
get { return responseContentType; }
set { responseContentType = value; }
}
 
public string ResponseContentSubtype
{
get { return responseContentSubtype; }
set { responseContentSubtype = value; }
}
 
public string ResponseCharset
{
get { return responseCharset; }
set { responseCharset = value; }
}
 
public byte[] ResponseBody
{
get { return responseBody; }
set { responseBody = value; }
}
 
public string ResponseText
{
get { return responseText; }
set { responseText = value; }
}
 
public XmlMessage ResponseXml
{
get { return responseXml; }
set { responseXml = value; }
}
 
public void AddRequestHeader(string name, string headerValue)
{
requestHeaders.Add(new HttpHeader(name, headerValue));
requestHeadersHash.Add(name, headerValue);
}
 
public void AddResponseHeader(string name, string headerValue)
{
HttpHeader header = (HttpHeader)responseHeadersHash[name];
if(header == null)
{
header = new HttpHeader(name, headerValue);
responseHeaders.Add(header);
responseHeadersHash.Add(name, header);
}
else
{
header.AddValue(headerValue);
}
}
 
public override string ToString() // FIXME delete the method
{
return (soapAction != null ? soapAction
: (requestMethod == null ? "" : requestMethod) + " " + (requestUri == null ? "" : requestUri));
}
 
public event TcpEventHandler Update;
 
protected virtual void OnUpdate(TcpEventArgs e)
{
if(Update != null)
{
Update(this, e);
}
}
 
internal void UpdateHttpMessage()
{
OnUpdate(new TcpEventArgs());
}
}
 
internal class HttpParseException : Exception
{
public HttpParseException() : base()
{
}
 
public HttpParseException(string message) : base(message)
{
}
 
public HttpParseException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
 
public HttpParseException(string message, Exception innerException) : base(message, innerException)
{
}
}
 
public class XmlMessage
{
private XmlDocument xml;
private XmlException parseException;
 
public XmlDocument Xml
{
get { return xml; }
}
 
public XmlException ParseException
{
get { return parseException; }
}
 
internal XmlMessage(string text)
{
try
{
this.xml = new XmlDocument();
this.xml.LoadXml(text);
}
catch(XmlException ex)
{
parseException = ex;
}
}
}
 
public class TcpMessage
{
private TcpMessageDirection direction;
private byte[] bytes;
private int length = 0;
private DateTime timestamp;
 
public TcpMessageDirection Direction
{
get { return direction; }
set { direction = value; }
}
 
public int Length
{
get { return length; }
}
 
public byte[] Bytes
{
get
{
return bytes;
}
set
{
length = 0;
Append(value);
}
}
 
public DateTime Timestamp
{
get { return timestamp; }
}
 
internal TcpMessage()
{
this.timestamp = DateTime.Now;
this.bytes = new byte[1024];
}
 
internal TcpMessage(byte[] bytes, int length)
{
this.timestamp = DateTime.Now;
this.bytes = new byte[length];
this.length = length;
Array.Copy(this.bytes, bytes, length);
}
 
internal TcpMessage Append(byte[] newBytes)
{
if(newBytes == null) return this;
 
return Append(newBytes, newBytes.Length);
}
 
internal TcpMessage Append(byte[] newBytes, int length)
{
if(newBytes == null) return this;
 
lock(this)
{
// grow array
if(this.length + length > bytes.Length)
{
int newLength = bytes.Length;
while(this.length + length > newLength) newLength *= 2;
byte[] newArray = new byte[newLength];
 
Array.Copy(bytes, newArray, this.length);
bytes = newArray;
}
 
// store received bytes
Array.Copy(newBytes, 0, bytes, this.length, length);
this.length += length;
 
return this;
}
}
}
 
}