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, |
ShutdownReceive, |
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.ShutdownReceive) |
{ |
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.ShutdownReceive) |
{ |
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now; |
} |
else if(this.localState == SocketState.ShutdownReceive && localState == SocketState.ShutdownSend) |
{ |
} |
else if(this.localState == SocketState.ShutdownReceive && 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.ShutdownReceive) |
{ |
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.ShutdownReceive) |
{ |
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now; |
} |
else if(this.remoteState == SocketState.ShutdownReceive && remoteState == SocketState.ShutdownSend) |
{ |
} |
else if(this.remoteState == SocketState.ShutdownReceive && 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.ShutdownReceive); |
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.ShutdownReceive); |
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; |
} |
} |
} |
|
} |