Subversion Repositories general

Compare Revisions

No changes between revisions

Ignore whitespace Rev 1091 → Rev 1092

/TCPproxy/trunk/tcpproxy.build
0,0 → 1,24
<?xml version="1.0" ?>
<project name="TCPproxy" default="build">
<property name="debug" value="true"/>
<property name="build.dir" value="bin"/>
 
<target name="clean" description="cleans build directory">
<delete dir="${build.dir}" verbose="true" if="${directory::exists(build.dir)}" />
</target>
 
<target name="build">
<mkdir dir="${build.dir}"/>
<csc target="winexe" output="bin/tcpproxy.exe" debug="${debug}">
<sources>
<include name="src/*.cs" />
</sources>
</csc>
</target>
 
<target name="run" depends="build">
<exec program="tcpproxy.exe" basedir="${build.dir}" />
</target>
 
<target name="all" depends="clean,build" />
</project>
/TCPproxy/trunk/src/LinkedList.cs
0,0 → 1,92
using System;
using System.Collections;
 
namespace TCPproxy
{
public class LinkedList
{
private class ListNode
{
public object data;
public ListNode next;
}
 
private class LinkedEnumerator : IEnumerator
{
private LinkedList list;
private ListNode current;
 
public object Current
{
get
{
if(current == null) throw new InvalidOperationException("No current element");
 
return current.data;
}
}
 
public bool MoveNext()
{
if(current == null)
{
current = list.first;
return (current != null);
}
else
{
if(current.next == null)
{
return false;
}
else
{
current = current.next;
return true;
}
}
}
 
public void Reset()
{
current = null;
}
 
public LinkedEnumerator(LinkedList list)
{
this.list = list;
}
}
 
private ListNode first;
private ListNode last;
private int count = 0;
 
public int Count
{
get { return count; }
}
 
public IEnumerator GetEnumerator()
{
return new LinkedEnumerator(this);
}
 
public void Add(object obj)
{
ListNode node = new ListNode();
node.data = obj;
 
if(last == null)
{
first = node;
}
else
{
last.next = node;
}
last = node;
count++;
}
}
}
/TCPproxy/trunk/src/MainForm.cs
0,0 → 1,2342
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
 
// FIXME:
// - add soap validation
// - icons for all items in the tree
// - update text view, not override whole text
// - use pool of threads?
// - do not store intermediate info, just row packets and the parsed fragments to display
// - make the text fragment store switchable
namespace TCPproxy
{
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.IContainer components;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button startButton;
private System.Windows.Forms.MenuItem saveXmlMenuItem;
private System.Windows.Forms.ListBox logBox;
private System.Windows.Forms.MenuItem tcpShowByTimeMenuItem;
private System.Windows.Forms.SaveFileDialog saveLogDialog;
private System.Windows.Forms.MenuItem clearMenuItem;
private System.Windows.Forms.StatusBarPanel connectionStatusBar;
private System.Windows.Forms.Label levelLabel;
private TCPproxy.ViewControl messagesBox;
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.Button saveButton;
private System.Windows.Forms.MenuItem saveHttpMenuItem;
private System.Windows.Forms.TextBox resendPortBox;
private System.Windows.Forms.MenuItem selectAllmenuItem;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.MenuItem autoExpandMenuItem;
private System.Windows.Forms.MenuItem copyMenuItem;
private System.Windows.Forms.Splitter splitter2;
private System.Windows.Forms.ContextMenu viewContextMenu;
private System.Windows.Forms.TreeView messageView;
private System.Windows.Forms.Splitter splitter1;
private System.Windows.Forms.MenuItem saveTcpMenuItem;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.MenuItem closeConnectionMenuItem;
private System.Windows.Forms.Panel panel4;
private System.Windows.Forms.ImageList saveButtonImageList;
private System.Windows.Forms.StatusBar statusBar;
private System.Windows.Forms.MenuItem saveLogMenuItem;
private System.Windows.Forms.ContextMenu saveLogMenu;
private System.Windows.Forms.Button stopButton;
private System.Windows.Forms.ContextMenu messagesContextMenu;
private System.Windows.Forms.MenuItem tcpShowByDirectionMenuItem;
private System.Windows.Forms.TextBox listenPortBox;
private System.Windows.Forms.ComboBox levelBox;
private System.Windows.Forms.Button clearButton;
private System.Windows.Forms.ImageList treeImageList;
private System.Windows.Forms.MenuItem wordWrapMenuItem;
private System.Windows.Forms.TextBox resendHostBox;
#region private fields
 
private TcpListener tcpListener = null;
private LogMessages logMessages = null;
private Hashtable treeNodes = new Hashtable();
 
private TcpShowMode tcpShowMode = TcpShowMode.ByDirection;
private bool autoExpand = true;
 
#endregion private fields
 
#region web forms fields
 
 
#endregion web forms fields
 
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.resendHostBox = new System.Windows.Forms.TextBox();
this.wordWrapMenuItem = new System.Windows.Forms.MenuItem();
this.treeImageList = new System.Windows.Forms.ImageList(this.components);
this.clearButton = new System.Windows.Forms.Button();
this.levelBox = new System.Windows.Forms.ComboBox();
this.listenPortBox = new System.Windows.Forms.TextBox();
this.tcpShowByDirectionMenuItem = new System.Windows.Forms.MenuItem();
this.messagesContextMenu = new System.Windows.Forms.ContextMenu();
this.stopButton = new System.Windows.Forms.Button();
this.saveLogMenu = new System.Windows.Forms.ContextMenu();
this.saveLogMenuItem = new System.Windows.Forms.MenuItem();
this.statusBar = new System.Windows.Forms.StatusBar();
this.saveButtonImageList = new System.Windows.Forms.ImageList(this.components);
this.panel4 = new System.Windows.Forms.Panel();
this.closeConnectionMenuItem = new System.Windows.Forms.MenuItem();
this.panel1 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel();
this.saveTcpMenuItem = new System.Windows.Forms.MenuItem();
this.splitter1 = new System.Windows.Forms.Splitter();
this.messageView = new System.Windows.Forms.TreeView();
this.viewContextMenu = new System.Windows.Forms.ContextMenu();
this.splitter2 = new System.Windows.Forms.Splitter();
this.copyMenuItem = new System.Windows.Forms.MenuItem();
this.autoExpandMenuItem = new System.Windows.Forms.MenuItem();
this.label1 = new System.Windows.Forms.Label();
this.selectAllmenuItem = new System.Windows.Forms.MenuItem();
this.resendPortBox = new System.Windows.Forms.TextBox();
this.saveHttpMenuItem = new System.Windows.Forms.MenuItem();
this.saveButton = new System.Windows.Forms.Button();
this.panel3 = new System.Windows.Forms.Panel();
this.messagesBox = new TCPproxy.ViewControl();
this.levelLabel = new System.Windows.Forms.Label();
this.connectionStatusBar = new System.Windows.Forms.StatusBarPanel();
this.clearMenuItem = new System.Windows.Forms.MenuItem();
this.saveLogDialog = new System.Windows.Forms.SaveFileDialog();
this.tcpShowByTimeMenuItem = new System.Windows.Forms.MenuItem();
this.logBox = new System.Windows.Forms.ListBox();
this.saveXmlMenuItem = new System.Windows.Forms.MenuItem();
this.startButton = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.panel4.SuspendLayout();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
this.panel3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.connectionStatusBar)).BeginInit();
this.SuspendLayout();
//
// resendHostBox
//
this.resendHostBox.Location = new System.Drawing.Point(88, 48);
this.resendHostBox.Name = "resendHostBox";
this.resendHostBox.TabIndex = 3;
this.resendHostBox.Text = "";
//
// wordWrapMenuItem
//
this.wordWrapMenuItem.Index = 3;
this.wordWrapMenuItem.Text = "Word &Wrap";
this.wordWrapMenuItem.Click += new System.EventHandler(this.wordWrapMenuItem_Click);
//
// treeImageList
//
this.treeImageList.ImageSize = new System.Drawing.Size(16, 16);
this.treeImageList.TransparentColor = System.Drawing.Color.Transparent;
//
// clearButton
//
this.clearButton.Location = new System.Drawing.Point(288, 16);
this.clearButton.Name = "clearButton";
this.clearButton.TabIndex = 6;
this.clearButton.Text = "Clear";
this.clearButton.Click += new System.EventHandler(this.clearButton_Click);
//
// levelBox
//
this.levelBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.levelBox.Items.AddRange(new object[] {
"All",
"Info",
"Important"});
this.levelBox.Location = new System.Drawing.Point(472, 16);
this.levelBox.Name = "levelBox";
this.levelBox.Size = new System.Drawing.Size(121, 21);
this.levelBox.TabIndex = 8;
this.levelBox.SelectedIndexChanged += new System.EventHandler(this.levelBox_SelectedIndexChanged);
//
// listenPortBox
//
this.listenPortBox.Location = new System.Drawing.Point(88, 16);
this.listenPortBox.Name = "listenPortBox";
this.listenPortBox.TabIndex = 2;
this.listenPortBox.Text = "";
//
// tcpShowByDirectionMenuItem
//
this.tcpShowByDirectionMenuItem.Checked = true;
this.tcpShowByDirectionMenuItem.Index = 1;
this.tcpShowByDirectionMenuItem.Text = "TCP Show by &Direction";
this.tcpShowByDirectionMenuItem.Click += new System.EventHandler(this.tcpShowByDirectionMenuItem_Click);
//
// messagesContextMenu
//
this.messagesContextMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.selectAllmenuItem,
this.copyMenuItem,
this.clearMenuItem,
this.wordWrapMenuItem});
this.messagesContextMenu.Popup += new System.EventHandler(this.messagesContextMenu_Popup);
//
// stopButton
//
this.stopButton.Location = new System.Drawing.Point(192, 16);
this.stopButton.Name = "stopButton";
this.stopButton.TabIndex = 5;
this.stopButton.Text = "Stop";
this.stopButton.Visible = false;
this.stopButton.Click += new System.EventHandler(this.stopButton_Click);
//
// saveLogMenu
//
this.saveLogMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.saveLogMenuItem,
this.saveTcpMenuItem,
this.saveHttpMenuItem,
this.saveXmlMenuItem});
//
// saveLogMenuItem
//
this.saveLogMenuItem.Index = 0;
this.saveLogMenuItem.Text = "Save Log";
this.saveLogMenuItem.Click += new System.EventHandler(this.saveLogMenuItem_Click);
//
// statusBar
//
this.statusBar.Location = new System.Drawing.Point(0, 619);
this.statusBar.Name = "statusBar";
this.statusBar.Panels.AddRange(new System.Windows.Forms.StatusBarPanel[] {
this.connectionStatusBar});
this.statusBar.ShowPanels = true;
this.statusBar.Size = new System.Drawing.Size(780, 22);
this.statusBar.TabIndex = 0;
//
// saveButtonImageList
//
this.saveButtonImageList.ImageSize = new System.Drawing.Size(16, 16);
this.saveButtonImageList.TransparentColor = System.Drawing.Color.Transparent;
//
// panel4
//
this.panel4.Controls.Add(this.messagesBox);
this.panel4.Controls.Add(this.splitter2);
this.panel4.Controls.Add(this.logBox);
this.panel4.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel4.Location = new System.Drawing.Point(163, 0);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(617, 539);
this.panel4.TabIndex = 13;
//
// closeConnectionMenuItem
//
this.closeConnectionMenuItem.Index = 0;
this.closeConnectionMenuItem.Text = "&Close connection";
this.closeConnectionMenuItem.Click += new System.EventHandler(this.closeConnectionMenuItem_Click);
//
// panel1
//
this.panel1.Controls.Add(this.panel3);
this.panel1.Controls.Add(this.panel2);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(780, 619);
this.panel1.TabIndex = 1;
//
// panel2
//
this.panel2.Controls.Add(this.levelLabel);
this.panel2.Controls.Add(this.levelBox);
this.panel2.Controls.Add(this.saveButton);
this.panel2.Controls.Add(this.clearButton);
this.panel2.Controls.Add(this.stopButton);
this.panel2.Controls.Add(this.startButton);
this.panel2.Controls.Add(this.label2);
this.panel2.Controls.Add(this.label1);
this.panel2.Controls.Add(this.resendPortBox);
this.panel2.Controls.Add(this.resendHostBox);
this.panel2.Controls.Add(this.listenPortBox);
this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel2.Location = new System.Drawing.Point(0, 539);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(780, 80);
this.panel2.TabIndex = 3;
//
// saveTcpMenuItem
//
this.saveTcpMenuItem.Index = 1;
this.saveTcpMenuItem.Text = "Save TCP";
this.saveTcpMenuItem.Click += new System.EventHandler(this.saveTcpMenuItem_Click);
//
// splitter1
//
this.splitter1.Location = new System.Drawing.Point(160, 0);
this.splitter1.Name = "splitter1";
this.splitter1.Size = new System.Drawing.Size(3, 539);
this.splitter1.TabIndex = 12;
this.splitter1.TabStop = false;
//
// messageView
//
this.messageView.ContextMenu = this.viewContextMenu;
this.messageView.Dock = System.Windows.Forms.DockStyle.Left;
this.messageView.HideSelection = false;
this.messageView.ImageList = this.treeImageList;
this.messageView.Location = new System.Drawing.Point(0, 0);
this.messageView.Name = "messageView";
this.messageView.Size = new System.Drawing.Size(160, 539);
this.messageView.TabIndex = 11;
this.messageView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.messageView_AfterSelect);
this.messageView.BeforeSelect += new System.Windows.Forms.TreeViewCancelEventHandler(this.messageView_BeforeSelect);
//
// viewContextMenu
//
this.viewContextMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.closeConnectionMenuItem,
this.tcpShowByDirectionMenuItem,
this.tcpShowByTimeMenuItem,
this.autoExpandMenuItem});
this.viewContextMenu.Popup += new System.EventHandler(this.listContextMenu_Popup);
//
// splitter2
//
this.splitter2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.splitter2.Location = new System.Drawing.Point(0, 468);
this.splitter2.Name = "splitter2";
this.splitter2.Size = new System.Drawing.Size(617, 3);
this.splitter2.TabIndex = 9;
this.splitter2.TabStop = false;
//
// copyMenuItem
//
this.copyMenuItem.Index = 1;
this.copyMenuItem.Shortcut = System.Windows.Forms.Shortcut.CtrlC;
this.copyMenuItem.Text = "&Copy";
this.copyMenuItem.Click += new System.EventHandler(this.copyMenuItem_Click);
//
// autoExpandMenuItem
//
this.autoExpandMenuItem.Index = 3;
this.autoExpandMenuItem.Text = "Auto &Expand";
this.autoExpandMenuItem.Click += new System.EventHandler(this.autoExpandMenuItem_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(8, 20);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(73, 16);
this.label1.TabIndex = 3;
this.label1.Text = "Listen on port";
//
// selectAllmenuItem
//
this.selectAllmenuItem.Index = 0;
this.selectAllmenuItem.Shortcut = System.Windows.Forms.Shortcut.CtrlA;
this.selectAllmenuItem.Text = "Select &All";
this.selectAllmenuItem.Click += new System.EventHandler(this.selectAllMenuItem_Click);
//
// resendPortBox
//
this.resendPortBox.Location = new System.Drawing.Point(192, 48);
this.resendPortBox.Name = "resendPortBox";
this.resendPortBox.Size = new System.Drawing.Size(72, 20);
this.resendPortBox.TabIndex = 4;
this.resendPortBox.Text = "";
//
// saveHttpMenuItem
//
this.saveHttpMenuItem.Index = 2;
this.saveHttpMenuItem.Text = "Save HTTP";
this.saveHttpMenuItem.Click += new System.EventHandler(this.saveHttpMenuItem_Click);
//
// saveButton
//
this.saveButton.ImageAlign = System.Drawing.ContentAlignment.MiddleRight;
this.saveButton.ImageList = this.saveButtonImageList;
this.saveButton.Location = new System.Drawing.Point(288, 48);
this.saveButton.Name = "saveButton";
this.saveButton.TabIndex = 7;
this.saveButton.Text = "Save Log";
this.saveButton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.saveButton.Click += new System.EventHandler(this.saveButton_Click);
//
// panel3
//
this.panel3.Controls.Add(this.panel4);
this.panel3.Controls.Add(this.splitter1);
this.panel3.Controls.Add(this.messageView);
this.panel3.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel3.Location = new System.Drawing.Point(0, 0);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(780, 539);
this.panel3.TabIndex = 5;
//
// messagesBox
//
this.messagesBox.ContextMenu = this.messagesContextMenu;
this.messagesBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.messagesBox.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.messagesBox.Location = new System.Drawing.Point(0, 0);
this.messagesBox.Name = "messagesBox";
this.messagesBox.Size = new System.Drawing.Size(617, 468);
this.messagesBox.TabIndex = 7;
this.messagesBox.WordWrap = true;
//
// levelLabel
//
this.levelLabel.AutoSize = true;
this.levelLabel.Location = new System.Drawing.Point(384, 20);
this.levelLabel.Name = "levelLabel";
this.levelLabel.Size = new System.Drawing.Size(86, 16);
this.levelLabel.TabIndex = 9;
this.levelLabel.Text = "Messages Level";
//
// connectionStatusBar
//
this.connectionStatusBar.AutoSize = System.Windows.Forms.StatusBarPanelAutoSize.Spring;
this.connectionStatusBar.Width = 764;
//
// clearMenuItem
//
this.clearMenuItem.Index = 2;
this.clearMenuItem.Shortcut = System.Windows.Forms.Shortcut.CtrlL;
this.clearMenuItem.Text = "C&lear";
this.clearMenuItem.Click += new System.EventHandler(this.clearButton_Click);
//
// saveLogDialog
//
this.saveLogDialog.DefaultExt = "txt";
this.saveLogDialog.Filter = "Text Files|*.txt|All Files|*.*";
this.saveLogDialog.Title = "Save Log";
//
// tcpShowByTimeMenuItem
//
this.tcpShowByTimeMenuItem.Index = 2;
this.tcpShowByTimeMenuItem.Text = "TCP Show by &Time";
this.tcpShowByTimeMenuItem.Click += new System.EventHandler(this.tcpShowByTimeMenuItem_Click);
//
// logBox
//
this.logBox.Dock = System.Windows.Forms.DockStyle.Bottom;
this.logBox.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.logBox.HorizontalScrollbar = true;
this.logBox.ItemHeight = 16;
this.logBox.Location = new System.Drawing.Point(0, 471);
this.logBox.Name = "logBox";
this.logBox.ScrollAlwaysVisible = true;
this.logBox.Size = new System.Drawing.Size(617, 68);
this.logBox.TabIndex = 8;
//
// saveXmlMenuItem
//
this.saveXmlMenuItem.Index = 3;
this.saveXmlMenuItem.Text = "Save XML";
this.saveXmlMenuItem.Click += new System.EventHandler(this.saveXmlMenuItem_Click);
//
// startButton
//
this.startButton.Location = new System.Drawing.Point(192, 16);
this.startButton.Name = "startButton";
this.startButton.TabIndex = 5;
this.startButton.Text = "Start";
this.startButton.Click += new System.EventHandler(this.startButton_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(8, 52);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(56, 16);
this.label2.TabIndex = 4;
this.label2.Text = "Resend to";
//
// MainForm
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(780, 641);
this.Controls.Add(this.panel1);
this.Controls.Add(this.statusBar);
this.MinimumSize = new System.Drawing.Size(400, 200);
this.Name = "MainForm";
this.Text = "TCPproxy";
this.panel4.ResumeLayout(false);
this.panel1.ResumeLayout(false);
this.panel2.ResumeLayout(false);
this.panel3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.connectionStatusBar)).EndInit();
this.ResumeLayout(false);
}
#endregion
 
#region windows forms methods
public MainForm()
{
InitializeComponent();
logMessages = new LogMessages(logBox);
LoadFromRegistry();
if(levelBox.SelectedIndex < 0) levelBox.SelectedIndex = 0;
}
 
protected override void Dispose( bool disposing )
{
if(tcpListener != null)
{
tcpListener.StopListening(); // stop listening
tcpListener.CancelAll(); // cancel all open connections
}
 
SaveToRegistry(); // save settings
 
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
 
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
 
private void LoadFromRegistry()
{
Microsoft.Win32.RegistryKey subkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
@"Software\Echelon\TCPproxy");
 
if(subkey != null)
{
listenPortBox.Text = (string)subkey.GetValue("Listen Port", "");
resendHostBox.Text = (string)subkey.GetValue("Resend Host", "");
resendPortBox.Text = (string)subkey.GetValue("Resend Port", "");
levelBox.SelectedIndex = (int)subkey.GetValue("Message Level", 0);
autoExpand = (int)subkey.GetValue("Auto Expand", 1) == 1;
messagesBox.WordWrap = (int)subkey.GetValue("Word Wrap", 1) == 1;
 
object tcpShowModeStr = (object)subkey.GetValue("Tcp Show Mode", TcpShowMode.ByDirection);
tcpShowMode = (tcpShowModeStr as string) == "ByDirection"
? TcpShowMode.ByDirection : TcpShowMode.ByTime;
}
}
 
private void SaveToRegistry()
{
Microsoft.Win32.RegistryKey subkey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(
@"Software\Echelon\TCPproxy");
 
subkey.SetValue("Listen Port", listenPortBox.Text);
subkey.SetValue("Resend Host", resendHostBox.Text);
subkey.SetValue("Resend Port", resendPortBox.Text);
subkey.SetValue("Message Level", levelBox.SelectedIndex);
subkey.SetValue("Tcp Show Mode", tcpShowMode);
subkey.SetValue("Auto Expand", autoExpand ? 1 : 0);
subkey.SetValue("Word Wrap", messagesBox.WordWrap ? 1 : 0);
}
 
private void startButton_Click(object sender, System.EventArgs e)
{
int listenPort = -1;
IPAddress resendHost;
int resendPort = -1;
ArrayList tcpConnections = new ArrayList();
 
// parse listen port
try
{
listenPort = int.Parse(listenPortBox.Text);
}
catch(FormatException)
{
MessageBox.Show("Listen port must be an integer number");
return;
}
 
// get resend host
if(resendHostBox.Text == "")
{
resendHost = null;
}
else
{
Regex ipRegex = new Regex(@"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$");
if(ipRegex.Match(resendHostBox.Text).Success)
{
try
{
resendHost = IPAddress.Parse(resendHostBox.Text);
}
catch(FormatException)
{
MessageBox.Show("Wrong IP address of the resend host");
return;
}
}
else
{
IPHostEntry hostInfo = Dns.GetHostByName(resendHostBox.Text);
 
if(hostInfo.AddressList.Length > 0)
{
resendHost = hostInfo.AddressList[0];
}
else
{
MessageBox.Show("Cannot find IP address of the resend host");
return;
}
}
 
// parse resend port
try
{
resendPort = int.Parse(resendPortBox.Text);
}
catch(FormatException)
{
MessageBox.Show("Resend port must be an integer number");
return;
}
}
 
// listen to the port
try
{
Start(listenPort, resendHost, resendPort);
}
catch(Exception ex)
{
MessageBox.Show("Cannot start listening: " + ex.Message, "TCPproxy",
MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
return;
}
 
startButton.Visible = false;
stopButton.Visible = true;
listenPortBox.Enabled = false;
resendHostBox.Enabled = false;
resendPortBox.Enabled = false;
}
 
private void stopButton_Click(object sender, System.EventArgs e)
{
if(tcpListener != null) tcpListener.StopListening();
 
startButton.Visible = true;
stopButton.Visible = false;
listenPortBox.Enabled = true;
resendHostBox.Enabled = true;
resendPortBox.Enabled = true;
}
 
private void clearButton_Click(object sender, System.EventArgs e)
{
treeNodes.Clear();
messageView.Nodes.Clear();
messagesBox.Clear();
logMessages.Clear();
}
 
private void saveButton_Click(object sender, System.EventArgs e)
{
saveLogMenu.Show(saveButton, new Point(saveButton.Width, 0));
}
 
private void messagesContextMenu_Popup(object sender, System.EventArgs e)
{
wordWrapMenuItem.Checked = messagesBox.WordWrap;
}
 
private void selectAllMenuItem_Click(object sender, System.EventArgs e)
{
messagesBox.SelectAll();
}
 
private void copyMenuItem_Click(object sender, System.EventArgs e)
{
string sel = messagesBox.SelectedText;
if(sel != null) Clipboard.SetDataObject(sel);
}
 
private void wordWrapMenuItem_Click(object sender, System.EventArgs e)
{
messagesBox.WordWrap = !messagesBox.WordWrap;
}
 
private void autoExpandMenuItem_Click(object sender, System.EventArgs e)
{
autoExpand = !autoExpand;
}
 
private void messageView_BeforeSelect(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
{
if(messageView.SelectedNode == null) return;
 
object tag = messageView.SelectedNode.Tag;
if(tag is TreeNodeData)
{
TreeNodeData data = (TreeNodeData)tag;
data.SaveViewState();
}
}
 
private void messageView_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
if(messageView.SelectedNode == null) return;
 
object tag = messageView.SelectedNode.Tag;
if(tag is TreeNodeData)
{
TreeNodeData data = (TreeNodeData)tag;
data.Show();
}
}
 
private void closeConnectionMenuItem_Click(object sender, System.EventArgs e)
{
if(messageView.SelectedNode == null) return;
 
object tag = messageView.SelectedNode.Tag;
if(tag is TcpNodeData)
CloseTcpConnection(((TcpNodeData)tag).Tcp);
else if(tag is TcpNodeData)
CloseTcpConnection(((TcpNodeData)messageView.SelectedNode.Parent.Tag).Tcp);
else if(tag is XmlNodeData)
CloseTcpConnection(((TcpNodeData)messageView.SelectedNode.Parent.Parent.Tag).Tcp);
}
 
private void tcpShowByDirectionMenuItem_Click(object sender, System.EventArgs e)
{
tcpShowMode = TcpShowMode.ByDirection;
 
if(messageView.SelectedNode == null) return;
 
object tag = messageView.SelectedNode.Tag;
if(tag is TcpNodeData)
UpdateTcpNodeInternal(((TcpNodeData)tag).Tcp);
}
 
private void tcpShowByTimeMenuItem_Click(object sender, System.EventArgs e)
{
tcpShowMode = TcpShowMode.ByTime;
 
if(messageView.SelectedNode == null) return;
 
object tag = messageView.SelectedNode.Tag;
if(tag is TcpNodeData)
UpdateTcpNodeInternal(((TcpNodeData)tag).Tcp);
}
 
private void listContextMenu_Popup(object sender, System.EventArgs e)
{
if(tcpShowMode == TcpShowMode.ByDirection)
{
tcpShowByDirectionMenuItem.Checked = true;
tcpShowByTimeMenuItem.Checked = false;
}
else
{
tcpShowByDirectionMenuItem.Checked = false;
tcpShowByTimeMenuItem.Checked = true;
}
 
autoExpandMenuItem.Checked = autoExpand;
}
 
private void saveLogMenuItem_Click(object sender, System.EventArgs e)
{
if(saveLogDialog.ShowDialog() == DialogResult.OK)
{
SaveLog(saveLogDialog.FileName);
}
}
 
private void saveTcpMenuItem_Click(object sender, System.EventArgs e)
{
if(saveLogDialog.ShowDialog() == DialogResult.OK)
{
SaveTcp(saveLogDialog.FileName);
}
}
 
private void saveHttpMenuItem_Click(object sender, System.EventArgs e)
{
if(saveLogDialog.ShowDialog() == DialogResult.OK)
{
SaveHttp(saveLogDialog.FileName);
}
}
 
private void saveXmlMenuItem_Click(object sender, System.EventArgs e)
{
if(saveLogDialog.ShowDialog() == DialogResult.OK)
{
SaveXml(saveLogDialog.FileName);
}
}
 
private void levelBox_SelectedIndexChanged(object sender, System.EventArgs e)
{
LogLevel level = LogLevel.Debug;
 
if(levelBox.SelectedIndex == 1)
level = LogLevel.Info;
else if(levelBox.SelectedIndex == 2)
level = LogLevel.Important;
 
logMessages.Level = level;
}
 
#endregion windows forms methods
 
#region core methods
private void Start(int listenPort, IPAddress resendHost, int resendPort)
{
if(tcpListener != null) tcpListener.StopListening();
 
tcpListener = new TcpListener(listenPort, resendHost, resendPort);
tcpListener.Log += new TcpLogEventHandler(TcpConnectionLog);
tcpListener.NewTcp += new TcpConnectionEventHandler(AddTcpConnetion);
tcpListener.StartListening();
}
 
private void CloseTcpConnection(TcpConnection tcp)
{
if(tcp == null) return;
 
tcp.Cancel();
}
 
private void SaveLog(string fileName)
{
StreamWriter writer = new StreamWriter(fileName);
 
foreach(LogMessage message in logMessages.Messages)
{
writer.WriteLine(message);
}
 
writer.Close();
}
 
private void SaveTcp(string fileName)
{
StreamWriter writer = new StreamWriter(fileName);
 
foreach(TreeNode tcpNode in messageView.Nodes)
{
TcpNodeData data = (TcpNodeData)tcpNode.Tag;
data.WriteLog(writer);
}
 
writer.Close();
}
 
private void SaveHttp(string fileName)
{
StreamWriter writer = new StreamWriter(fileName);
 
foreach(TreeNode tcpNode in messageView.Nodes)
{
foreach(TreeNode httpNode in tcpNode.Nodes)
{
HttpNodeData data = (HttpNodeData)httpNode.Tag;
data.WriteLog(writer);
}
}
 
writer.Close();
}
 
private void SaveXml(string fileName)
{
StreamWriter writer = new StreamWriter(fileName);
 
foreach(TreeNode tcpNode in messageView.Nodes)
{
foreach(TreeNode httpNode in tcpNode.Nodes)
{
foreach(TreeNode xmlNode in httpNode.Nodes)
{
XmlNodeData data = (XmlNodeData)xmlNode.Tag;
data.WriteLog(writer);
}
}
}
 
writer.Close();
}
 
#endregion core methods
 
#region network events handlers
private void TcpConnectionLog(object sender, TcpLogEventArgs e)
{
TcpConnection tcp = sender as TcpConnection;
LogMessage message = new LogMessage(tcp, e.Level, e.Message, e.Exception);
 
try
{
this.BeginInvoke(new AddLogMessageHandler(AddLogMessageInternal), new object[] { message } );
}
catch(InvalidOperationException ex)
{
if(!this.Disposing && !this.IsDisposed)
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
private void AddTcpConnetion(object sender, TcpConnectionEventArgs e)
{
e.Tcp.Log += new TcpLogEventHandler(TcpConnectionLog);
e.Tcp.Update += new TcpEventHandler(UpdateTcpNode);
e.Tcp.Close += new TcpEventHandler(UpdateTcpNode);
e.Tcp.NewHttp += new TcpHttpEventHandler(AddHttpMessageNode);
 
try
{
this.BeginInvoke(new AddTcpNodeHandler(AddTcpNodeInternal), new object[] { e.Tcp } );
}
catch(InvalidOperationException ex)
{
if(!this.Disposing && !this.IsDisposed)
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
private void UpdateTcpNode(object sender, TcpEventArgs e)
{
try
{
this.BeginInvoke(new UpdateTcpNodeHandler(UpdateTcpNodeInternal), new object[] { (TcpConnection)sender } );
}
catch(InvalidOperationException ex)
{
if(!this.Disposing && !this.IsDisposed)
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
private void AddHttpMessageNode(object sender, TcpHttpEventArgs e)
{
e.Http.Update += new TcpEventHandler(UpdateHttpNode);
 
try
{
this.BeginInvoke(new AddHttpNodeHandler(AddHttpNodeInternal),
new object[] { (TcpConnection)sender, e.Http } );
}
catch(InvalidOperationException ex)
{
if(!this.Disposing && !this.IsDisposed)
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
private void UpdateHttpNode(object sender, TcpEventArgs e)
{
try
{
this.BeginInvoke(new UpdateHttpNodeHandler(UpdateHttpNodeInternal), new object[] { (HttpMessage)sender } );
}
catch(InvalidOperationException ex)
{
if(!this.Disposing && !this.IsDisposed)
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
#endregion network events handlers
 
#region handlers for async GUI events
private delegate void AddLogMessageHandler(LogMessage message);
private delegate void AddTcpNodeHandler(TcpConnection tcp);
private delegate void UpdateTcpNodeHandler(TcpConnection tcp);
private delegate void AddHttpNodeHandler(TcpConnection tcp, HttpMessage http);
private delegate void UpdateHttpNodeHandler(HttpMessage http);
 
private void AddLogMessageInternal(LogMessage message)
{
logMessages.Add(message);
 
if(message.Exception != null)
{
Console.WriteLine(message.Exception.Message + " (" + message.Exception.GetType().Name
+ ")\n" + message.Exception.StackTrace);
}
}
 
private void AddTcpNodeInternal(TcpConnection tcp)
{
TreeNode treeNode = new TreeNode(tcp.ToString());
TcpNodeData data = new TcpNodeData(this, treeNode);
 
data.Tcp = tcp;
treeNode.Tag = data;
treeNode.ImageIndex = 1;
treeNode.SelectedImageIndex = 1;
treeNodes[tcp] = data;
 
messageView.Nodes.Add(treeNode);
treeNode.EnsureVisible();
}
 
private void UpdateTcpNodeInternal(TcpConnection tcp)
{
TcpNodeData data = treeNodes[tcp] as TcpNodeData;
if(data == null) throw new ArgumentException("Unknown TCP node");
 
string title = tcp.ToString();
if(title != data.Node.Text) data.Node.Text = title;
if(tcp.LocalState == SocketState.Closed && tcp.RemoteState == SocketState.Closed)
{
data.Node.ImageIndex = 2;
data.Node.SelectedImageIndex = 2;
}
 
if(messageView.SelectedNode == null || data != messageView.SelectedNode.Tag)
data.ViewExpired = true; // got update for invisible TCP node, update it later
else
data.Show();
}
 
private void AddHttpNodeInternal(TcpConnection tcp, HttpMessage http)
{
TreeNode treeNode = new TreeNode(http.ToString());
HttpNodeData data = new HttpNodeData(this, treeNode);
 
data.Http = http;
treeNode.Tag = data;
treeNodes[http] = data;
 
TcpNodeData tcpData = treeNodes[tcp] as TcpNodeData;
if(tcpData == null) throw new ArgumentException("No node found for TCP message");
 
tcpData.Node.Nodes.Add(treeNode);
if(autoExpand) tcpData.Node.Expand();
tcpData.Node.EnsureVisible();
}
 
private void UpdateHttpNodeInternal(HttpMessage http)
{
HttpNodeData httpData = treeNodes[http] as HttpNodeData;
if(httpData == null) throw new ArgumentException("No node found for HTTP message");
 
string title = http.ToString();
if(httpData.Node.Text != title) httpData.Node.Text = title;
 
if(!httpData.RequestXmlShown && http.RequestXml != null)
{
httpData.RequestXmlShown = true;
AddXmlNode(httpData.Node, "Request XML", http.RequestXml);
if(autoExpand) httpData.Node.Expand();
}
 
if(!httpData.ResponseXmlShown && http.ResponseXml != null)
{
httpData.ResponseXmlShown = true;
AddXmlNode(httpData.Node, "Response XML", http.ResponseXml);
if(autoExpand) httpData.Node.Expand();
}
 
// update text view
if(messageView.SelectedNode == null || httpData != messageView.SelectedNode.Tag)
httpData.ViewExpired = true;
else
httpData.Show();
}
 
private void AddXmlNode(TreeNode parent, string title, XmlMessage xml)
{
TreeNode treeNode = new TreeNode(title);
XmlNodeData data = new XmlNodeData(this, treeNode);
 
data.Xml = xml;
treeNode.Tag = data;
treeNodes[xml] = data;
 
parent.Nodes.Add(treeNode);
}
 
#endregion handlers for async GUI events
 
#region node display classes
private abstract class TreeNodeData
{
protected TreeNode node;
protected object viewState;
protected bool viewExpired;
protected MainForm owner;
protected bool shown = false;
 
public TreeNode Node
{
get { return node; }
}
 
public TreeNodeData(MainForm owner, TreeNode node)
{
this.owner = owner;
this.node = node;
}
 
public bool ViewExpired
{
get { return viewExpired; }
set { viewExpired = value; }
}
 
public void SaveViewState()
{
viewState = owner.messagesBox.SaveState(false);
}
 
protected virtual bool ForceInitView()
{
return false;
}
 
protected abstract void InitView();
protected abstract void UpdateView();
 
public void Show()
{
if(!shown || viewState == null || ForceInitView())
{
InitView();
shown = true;
if(viewState == null) viewState = owner.messagesBox.SaveState(false);
}
else
{
owner.messagesBox.RestoreState(viewState, true);
if(viewExpired) UpdateView();
}
}
 
public abstract void WriteLog(StreamWriter writer);
}
 
private class TcpNodeData : TreeNodeData
{
private TcpConnection tcp;
 
private TcpShowMode lastShowMode;
private object localStateMarker = null;
private object remoteStateMarker = null;
private object startMarker = null;
private object localEndMarker = null;
private object remoteEndMarker = null;
private object clientMarker = null;
private object serverMarker = null;
private object sentMarker = null;
private object receivedMarker = null;
private object textMarker1 = null;
private object textMarker2 = null;
private IEnumerator messagesEnum = null;
 
public TcpConnection Tcp
{
get { return tcp; }
set { tcp = value; }
}
 
public TcpNodeData(MainForm owner, TreeNode node) : base(owner, node)
{
}
 
 
protected override bool ForceInitView()
{
return (lastShowMode != owner.tcpShowMode);
}
 
protected override void InitView()
{
lastShowMode = owner.tcpShowMode;
 
try
{
owner.messagesBox.BeginUpdate();
 
lock(tcp)
{
owner.messagesBox.Clear();
 
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("ID: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
owner.messagesBox.AppendText(tcp.Id,
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("State: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
localStateMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.LocalState.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(localStateMarker);
owner.messagesBox.AppendText(null,
Color.DarkRed, Color.Transparent, false, false, 1, 12);
remoteStateMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.RemoteState.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(remoteStateMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Start: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
startMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.StartTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(startMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Local End: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
localEndMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.LocalEndTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(localEndMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Remote End: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
remoteEndMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.RemoteEndTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(remoteEndMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Client: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
clientMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText((tcp.LocalPoint == null) ? "" : tcp.LocalPoint.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(clientMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Server: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
serverMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText((tcp.RemotePoint == null) ? "" : tcp.RemotePoint.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(serverMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Sent: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
sentMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.SentBytes.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(sentMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Received: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
receivedMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(tcp.ReceivedBytes.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.EndMark(receivedMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendNewLine();
 
messagesEnum = tcp.Messages.GetEnumerator();
 
textMarker1 = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(textMarker1);
 
owner.messagesBox.AppendNewLine();
textMarker2 = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(textMarker2);
 
ShowMessages();
}
}
finally
{
owner.messagesBox.EndUpdate();
}
}
 
protected override void UpdateView()
{
try
{
owner.messagesBox.BeginUpdate();
 
lock(tcp)
{
owner.messagesBox.ChangeText(localStateMarker, tcp.LocalState.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.ChangeText(remoteStateMarker, tcp.RemoteState.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
 
owner.messagesBox.ChangeText(startMarker, tcp.StartTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.ChangeText(localEndMarker, tcp.LocalEndTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.ChangeText(remoteEndMarker, tcp.RemoteEndTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
 
owner.messagesBox.ChangeText(clientMarker, (tcp.LocalPoint == null) ? "" : tcp.LocalPoint.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.ChangeText(serverMarker, (tcp.RemotePoint == null) ? "" : tcp.RemotePoint.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
 
owner.messagesBox.ChangeText(sentMarker, tcp.SentBytes.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
owner.messagesBox.ChangeText(receivedMarker, tcp.ReceivedBytes.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 12);
 
ShowMessages();
}
}
finally
{
owner.messagesBox.EndUpdate();
}
}
 
private void ShowMessages()
{
while(messagesEnum.MoveNext())
{
TcpMessage message = (TcpMessage)messagesEnum.Current;
object marker = (owner.tcpShowMode == TcpShowMode.ByTime
|| message.Direction == TcpMessageDirection.Local) ? textMarker1 : textMarker2;
 
if(owner.tcpShowMode == TcpShowMode.ByTime)
{
owner.messagesBox.InsertText(marker,
message.Timestamp.ToString("HH:mm:ss.ffff") + " (" + message.Length + ")",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
owner.messagesBox.InsertNewLine(marker);
}
 
string str = Utils.BytesToString(message.Bytes, message.Length);
ArrayList lines = Utils.SplitLine(str);
for(int i = 0; i < lines.Count; i++)
{
owner.messagesBox.InsertText(marker, (string)lines[i],
message.Direction == TcpMessageDirection.Local ? Color.Green : Color.Blue,
Color.LightGray, false, false, 0, 0);
 
if(owner.tcpShowMode == TcpShowMode.ByTime || i != lines.Count - 1)
owner.messagesBox.InsertNewLine(marker);
}
}
}
 
public override void WriteLog(StreamWriter writer)
{
writer.WriteLine(
"Start: " + tcp.StartTimestamp.ToString("HH:mm:ss.ffff")
+ "\r\nLocal End: " + tcp.LocalEndTimestamp.ToString("HH:mm:ss.ffff")
+ "\r\nRemote End: " + tcp.RemoteEndTimestamp.ToString("HH:mm:ss.ffff")
+ "\r\nClient: " + ((tcp.LocalPoint == null) ? "" : tcp.LocalPoint.ToString())
+ "\r\nServer: " + ((tcp.RemotePoint == null) ? "" : tcp.RemotePoint.ToString())
+ "\r\nSent: " + tcp.SentBytes
+ "\r\nReceived: " + tcp.ReceivedBytes);
 
foreach(TcpMessage message in tcp.Messages)
{
string str = Utils.BytesToString(message.Bytes, message.Length);
if(!str.EndsWith("\n")) str += "\r\n";
 
writer.WriteLine();
if(message.Direction == TcpMessageDirection.Local)
writer.WriteLine(">>> {0:HH:mm:ss.ffff} >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", message.Timestamp);
else
writer.WriteLine("<<< {0:HH:mm:ss.ffff} <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", message.Timestamp);
writer.Write(str);
}
writer.WriteLine("===============================================================");
writer.WriteLine();
}
}
 
private class HttpNodeData : TreeNodeData
{
private HttpMessage http;
private bool requestXmlShown = false;
private bool responseXmlShown = false;
 
private object stateMarker = null;
private object requestMethodMarker = null;
private object requestUriMarker = null;
private object requestVersionMarker = null;
private object requestLengthMarker = null;
private object requestEncodingMarker = null;
private object requestContentTypeMarker = null;
private object requestCharsetMarker = null;
private object requestHeadersMarker = null;
private object responseVersionMarker = null;
private object responseStatusMarker = null;
private object responseLengthMarker = null;
private object responseEncodingMarker = null;
private object responseContentTypeMarker = null;
private object responseCharsetMarker = null;
private object responseHeadersMarker = null;
private object requestBodyMarker = null;
private object responseBodyMarker = null;
private IEnumerator requestHeadersEnum = null;
private IEnumerator responseHeadersEnum = null;
 
public HttpMessage Http
{
get { return http; }
set { http = value; }
}
 
public bool RequestXmlShown
{
get { return requestXmlShown; }
set { requestXmlShown = value; }
}
 
public bool ResponseXmlShown
{
get { return responseXmlShown; }
set { responseXmlShown = value; }
}
 
public HttpNodeData(MainForm owner, TreeNode node) : base(owner, node)
{
}
 
private string EncodingToString(HttpEncoding encoding)
{
switch(encoding)
{
case HttpEncoding.Identify: return "identify";
case HttpEncoding.Gzip: return "gzip";
case HttpEncoding.Compress: return "compress";
case HttpEncoding.Deflate: return "deflate";
default: return "<unknown>";
}
}
 
protected override void InitView()
{
try
{
lock(http)
{
owner.messagesBox.Clear();
 
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Complete: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
stateMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestComplete && http.ResponseComplete ? "YES" : "NO",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(stateMarker);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendNewLine();
 
// request info
owner.messagesBox.AppendText("Request Method: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestMethodMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestMethod,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestMethodMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request URI: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestUriMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestUri,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestUriMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Version: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestVersionMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestVersion == HttpVersion.V0_9 ? "HTTP/0.9"
: http.RequestVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestVersionMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Content Length: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestLengthMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestLength < 0 ? "<unknown>" : http.RequestLength.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestLengthMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Content Encoding: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestEncodingMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(EncodingToString(http.RequestEncoding),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestEncodingMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Content Type: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestContentTypeMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestContentType == null ? "<unknown>"
: http.RequestContentType + "/" + http.RequestContentSubtype,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestContentTypeMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Content Charset: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestCharsetMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestCharset == null ? "<unknown>" : http.RequestCharset,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestCharsetMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Request Headers:",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
owner.messagesBox.AppendNewLine();
requestHeadersMarker = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(requestHeadersMarker);
 
requestHeadersEnum = http.RequestHeaders.GetEnumerator();
ShowHeaders(requestHeadersEnum, requestHeadersMarker);
 
// response info
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText("Response Version: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseVersionMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseVersion == HttpVersion.V0_9
? "HTTP/0.9" : http.ResponseVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseVersionMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Status: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseStatusMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseStatusCode + " " + http.ResponseStatusMessage,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseStatusMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Content Length: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseLengthMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseLength < 0 ? "<unknown>" : http.ResponseLength.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseLengthMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Content Encoding: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseEncodingMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(EncodingToString(http.ResponseEncoding),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseEncodingMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Content Type: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseContentTypeMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseContentType == null ? "<unknown>"
: http.ResponseContentType + "/" + http.ResponseContentSubtype,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseContentTypeMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Content Charset: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseCharsetMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseCharset == null ? "<unknown>" : http.ResponseCharset,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseCharsetMarker);
owner.messagesBox.AppendNewLine();
 
owner.messagesBox.AppendText("Response Headers:",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
owner.messagesBox.AppendNewLine();
responseHeadersMarker = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(responseHeadersMarker);
 
responseHeadersEnum = http.ResponseHeaders.GetEnumerator();
ShowHeaders(responseHeadersEnum, responseHeadersMarker);
 
owner.messagesBox.AppendNewLine();
requestBodyMarker = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(requestBodyMarker);
ShowBody(requestBodyMarker, http.RequestBody, http.RequestLength, http.RequestText, Color.Green);
 
owner.messagesBox.AppendNewLine();
responseBodyMarker = owner.messagesBox.BeginMark();
owner.messagesBox.EndMark(responseBodyMarker);
ShowBody(responseBodyMarker, http.ResponseBody, http.ResponseLength, http.ResponseText, Color.Blue);
}
}
finally
{
owner.messagesBox.EndUpdate();
}
}
 
protected override void UpdateView()
{
try
{
owner.messagesBox.BeginUpdate();
 
lock(http)
{
owner.messagesBox.ChangeText(stateMarker, http.RequestComplete && http.ResponseComplete ? "YES" : "NO",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestMethodMarker, http.RequestMethod,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestUriMarker, http.RequestUri,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestVersionMarker, http.RequestVersion == HttpVersion.V0_9 ? "HTTP/0.9"
: http.RequestVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestLengthMarker, http.RequestLength < 0
? "<unknown>" : http.RequestLength.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestEncodingMarker, EncodingToString(http.RequestEncoding),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestContentTypeMarker, http.RequestContentType == null ? "<unknown>"
: http.RequestContentType + "/" + http.RequestContentSubtype,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(requestCharsetMarker, http.RequestCharset == null
? "<unknown>" : http.RequestCharset,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
 
ShowHeaders(requestHeadersEnum, requestHeadersMarker);
 
owner.messagesBox.ChangeText(responseVersionMarker, http.ResponseVersion == HttpVersion.V0_9
? "HTTP/0.9" : http.ResponseVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1",
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(responseStatusMarker, http.ResponseStatusCode + " " + http.ResponseStatusMessage,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(responseLengthMarker, http.ResponseLength < 0
? "<unknown>" : http.ResponseLength.ToString(),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(responseEncodingMarker, EncodingToString(http.ResponseEncoding),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(responseContentTypeMarker, http.ResponseContentType == null ? "<unknown>"
: http.ResponseContentType + "/" + http.ResponseContentSubtype,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.ChangeText(responseCharsetMarker, http.ResponseCharset == null
? "<unknown>" : http.ResponseCharset,
Color.DarkRed, Color.LightGray, false, false, 0, 27);
 
ShowHeaders(responseHeadersEnum, responseHeadersMarker);
 
owner.messagesBox.DeleteText(requestBodyMarker);
ShowBody(requestBodyMarker, http.RequestBody, http.RequestLength, http.RequestText, Color.Green);
owner.messagesBox.DeleteText(responseBodyMarker);
ShowBody(responseBodyMarker, http.ResponseBody, http.ResponseLength, http.ResponseText, Color.Blue);
}
}
finally
{
owner.messagesBox.EndUpdate();
}
}
 
private void ShowHeaders(IEnumerator headers, object marker)
{
while(headers.MoveNext())
{
HttpHeader h = (HttpHeader)headers.Current;
 
bool first = true;
foreach(string val in h.Values)
{
if(first)
{
owner.messagesBox.InsertText(marker, h.Name + ":",
Color.DarkRed, Color.Transparent, false, false, 4, 4);
owner.messagesBox.InsertText(marker, null,
Color.DarkRed, Color.Transparent, false, false, 6 - 4 - 1, 4);
first = false;
}
else
{
owner.messagesBox.InsertText(marker, null,
Color.DarkRed, Color.Transparent, false, false, 6 + h.Name.Length, 6 + h.Name.Length);
}
owner.messagesBox.InsertText(marker, val,
Color.DarkRed, Color.LightGray, false, false, 0, 6 + h.Name.Length);
owner.messagesBox.InsertNewLine(marker);
}
}
}
 
private void ShowBody(object marker, byte[] body, int len, string text, Color color)
{
if(text != null)
{
ArrayList lines = Utils.SplitLine(text);
for(int i = 0; i < lines.Count; i++)
{
owner.messagesBox.InsertText(marker, (string)lines[i], color, Color.LightGray, false, false, 0, 0);
owner.messagesBox.InsertNewLine(marker);
}
}
else if(body != null)
{
ArrayList lines = Utils.SplitLine(Utils.BytesToString(body, len));
for(int i = 0; i < lines.Count; i++)
{
owner.messagesBox.InsertText(marker, (string)lines[i], color, Color.LightGray, false, false, 0, 0);
owner.messagesBox.InsertNewLine(marker);
}
}
}
 
public override void WriteLog(StreamWriter writer)
{
// request info
writer.WriteLine(
"Complete: " + (http.RequestComplete && http.ResponseComplete ? "YES" : "NO") + "\r\n"
+ "\r\nRequest Method: " + http.RequestMethod
+ "\r\nRequest URI: " + http.RequestUri
+ "\r\nRequest Version: " + (http.RequestVersion == HttpVersion.V0_9 ? "HTTP/0.9"
: http.RequestVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1")
+ "\r\nRequest Content Length: " + (http.RequestLength < 0 ? "<unknown>" : http.RequestLength.ToString())
+ "\r\nRequest Content Encoding: " + EncodingToString(http.RequestEncoding)
+ "\r\nRequest Content Type: " + (http.RequestContentType == null ? "<unknown>"
: http.RequestContentType + "/" + http.RequestContentSubtype)
+ "\r\nRequest Content Charset: " + (http.RequestCharset == null ? "<unknown>" : http.RequestCharset)
+ "\r\nRequest Headers:");
 
foreach(HttpHeader h in http.RequestHeaders)
{
int indent = 0;
foreach(string val in h.Values)
{
if(indent == 0)
{
writer.WriteLine(" {0}: {1}", h.Name, val);
indent = 6 + h.Name.Length;
}
else
{
writer.WriteLine("{0," + indent + "}{1}", " ", val);
}
}
}
 
// response info
writer.WriteLine(
"\r\nResponse Version: " + (http.ResponseVersion == HttpVersion.V0_9
? "HTTP/0.9" : http.ResponseVersion == HttpVersion.V1_0 ? "HTTP/1.0" : "HTTP/1.1")
+ "\r\nResponse Status: " + http.ResponseStatusCode + " " + http.ResponseStatusMessage
+ "\r\nResponse Content Length: " + (http.ResponseLength < 0
? "<unknown>" : http.ResponseLength.ToString())
+ "\r\nResponse Content Encoding: " + EncodingToString(http.ResponseEncoding)
+ "\r\nResponse Content Type: " + (http.ResponseContentType == null ? "<unknown>"
: http.ResponseContentType + "/" + http.ResponseContentSubtype)
+ "\r\nResponse Content Charset: " + (http.ResponseCharset == null ? "<unknown>" : http.ResponseCharset)
+ "\r\nResponse Headers:");
 
foreach(HttpHeader h in http.ResponseHeaders)
{
int indent = 0;
foreach(string val in h.Values)
{
if(indent == 0)
{
writer.WriteLine(" {0}: {1}", h.Name, val);
indent = 6 + h.Name.Length;
}
else
{
writer.WriteLine("{0," + indent + "}{1}", " ", val);
}
}
}
writer.WriteLine();
 
// request body
if(http.RequestText != null)
{
writer.WriteLine(http.RequestText);
}
else if(http.RequestBody != null)
{
writer.WriteLine(Utils.BytesToString(http.RequestBody, http.RequestLength));
}
 
// response body
if(http.ResponseText != null)
{
writer.WriteLine(http.ResponseText);
}
else if(http.ResponseBody != null)
{
writer.WriteLine(Utils.BytesToString(http.ResponseBody, http.ResponseLength));
}
 
writer.WriteLine("===============================================================");
writer.WriteLine();
}
}
 
private class XmlNodeData : TreeNodeData
{
private XmlMessage xml;
 
public XmlMessage Xml
{
get { return xml; }
set { xml = value; }
}
 
public XmlNodeData(MainForm owner, TreeNode node) : base(owner, node)
{
}
 
protected override void InitView()
{
try
{
// update main screen if necessary
owner.messagesBox.BeginUpdate();
 
lock(xml)
{
owner.messagesBox.Clear();
if(xml.ParseException != null)
{
owner.messagesBox.AppendText("Cannot parse XML:",
Color.Red, Color.Transparent, false, false, 0, 0);
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText(xml.ParseException.Message,
Color.Red, Color.Transparent, false, false, 2, 2);
owner.messagesBox.AppendNewLine();
}
else if(xml.Xml != null)
{
ShowXmlNode(xml.Xml, 0);
}
}
}
finally
{
owner.messagesBox.EndUpdate();
}
}
 
protected override void UpdateView()
{
}
 
private void ShowXmlNode(XmlNode node, int indent)
{
if(node.NodeType == XmlNodeType.Document)
{
foreach(XmlNode subnode in node.ChildNodes)
{
ShowXmlNode(subnode, indent);
}
}
else if(node.NodeType == XmlNodeType.XmlDeclaration)
{
owner.messagesBox.AppendText("<?" + node.Name + " " + node.Value + " ?>",
Color.Blue, Color.Transparent, false, false, indent, indent+2);
owner.messagesBox.AppendNewLine();
}
else if(node.NodeType == XmlNodeType.Comment)
{
owner.messagesBox.AppendText("<!--", Color.Gray, Color.Transparent, false, false, indent, indent + 2);
ShowNormalizedXmlValue(node.Value, Color.Gray, Color.Thistle, false, false, indent);
owner.messagesBox.AppendText("-->", Color.Gray, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendNewLine();
}
else if(node.NodeType == XmlNodeType.Element)
{
owner.messagesBox.AppendText("<", Color.Blue, Color.Transparent, false, false, indent, indent + 2);
owner.messagesBox.AppendText(node.Name, Color.DarkRed, Color.Transparent, false, false, 0, indent + 2);
 
foreach(XmlAttribute attr in node.Attributes)
{
bool xmlAttr = attr.Name.StartsWith("xml");
owner.messagesBox.AppendText(attr.Name, xmlAttr ? Color.Red : Color.DarkRed, Color.Transparent, false, false, 1, indent + 2);
owner.messagesBox.AppendText("=\"", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendText(attr.Value, xmlAttr ? Color.Red : Color.DarkRed, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendText("\"", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
}
 
if(!node.HasChildNodes)
{
owner.messagesBox.AppendText(" />", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendNewLine();
}
else
{
owner.messagesBox.AppendText(">", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
 
bool elementExists = false;
bool lastText = true;
foreach(XmlNode subnode in node.ChildNodes)
{
if(subnode.NodeType == XmlNodeType.Text)
{
if(elementExists) owner.messagesBox.AppendNewLine();
ShowNormalizedXmlValue(subnode.Value, Color.Black, Color.LightGray, false, false, indent);
lastText = true;
}
else
{
if(lastText) owner.messagesBox.AppendNewLine();
ShowXmlNode(subnode, indent + 2);
elementExists = true;
lastText = false;
}
}
 
if(elementExists) owner.messagesBox.AppendText(null, Color.Black, Color.Transparent, false, false, indent, indent + 2);
owner.messagesBox.AppendText("</", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendText(node.Name, Color.DarkRed, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendText(">", Color.Blue, Color.Transparent, false, false, 0, indent + 2);
owner.messagesBox.AppendNewLine();
}
}
}
 
private void ShowNormalizedXmlValue(string s, Color color, Color backColor, bool italic, bool bold, int indent)
{
int begin;
int end;
 
bool newLineFound = false;
foreach(char c in s)
{
if(c == '\n')
{
newLineFound = true;
break;
}
}
 
if(newLineFound)
{
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText(null, color, Color.Transparent, italic, bold, indent, indent);
owner.messagesBox.AppendText(null, color, backColor, italic, bold, 2, 2);
}
 
// trim
for(begin = 0; begin < s.Length; begin++)
{
char c = s[begin];
if(c != '\n' && c != '\r' && c != '\t' && c != ' ') break;
}
for(end = s.Length-1; end >= 0; end--)
{
char c = s[end];
if(c != '\n' && c != '\r' && c != '\t' && c != ' ') break;
}
 
StringBuilder b = new StringBuilder(s.Length);
for(int i = begin; i <= end; i++)
{
char c = s[i];
 
switch(c)
{
case '\n':
if(b.Length > 0)
{
owner.messagesBox.AppendText(b.ToString(), color, backColor, italic, bold, 0, indent + 2);
b.Length = 0;
}
 
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText(null, color, Color.Transparent, italic, bold, indent, indent);
owner.messagesBox.AppendText(null, color, backColor, italic, bold, 2, 2);
break;
 
case '\r':
break;
 
case '\t':
b.Append(" ");
break;
 
default:
b.Append(c);
break;
}
}
 
if(b.Length > 0)
owner.messagesBox.AppendText(b.ToString(), color, backColor, italic, bold, 0, indent + 2);
 
if(newLineFound)
{
owner.messagesBox.AppendNewLine();
owner.messagesBox.AppendText(null, color, Color.Transparent, italic, bold, indent, indent + 2);
}
}
 
public override void WriteLog(StreamWriter writer)
{
WriteXmlNode(writer, xml.Xml, "");
writer.WriteLine("===============================================================");
writer.WriteLine();
}
 
private void WriteXmlNode(StreamWriter writer, XmlNode node, string indent)
{
if(node.NodeType == XmlNodeType.Document)
{
foreach(XmlNode subnode in node.ChildNodes)
{
WriteXmlNode(writer, subnode, indent);
}
}
else if(node.NodeType == XmlNodeType.XmlDeclaration)
{
writer.WriteLine(indent + "<?" + node.Name + " " + node.Value + " ?>");
}
else if(node.NodeType == XmlNodeType.Comment)
{
writer.Write(indent + "<!--");
WriteNormalizedXmlValue(writer, node.Value, indent);
writer.WriteLine("-->");
}
else if(node.NodeType == XmlNodeType.Element)
{
writer.Write(indent + "<" + node.Name);
 
foreach(XmlAttribute attr in node.Attributes)
{
bool xmlAttr = attr.Name.StartsWith("xml");
writer.Write(" " + attr.Name + "=\"" + attr.Value + "\"");
}
 
if(!node.HasChildNodes)
{
writer.WriteLine(" />");
}
else
{
writer.Write(">");
 
bool elementExists = false;
bool lastText = true;
foreach(XmlNode subnode in node.ChildNodes)
{
if(subnode.NodeType == XmlNodeType.Text)
{
if(elementExists) writer.WriteLine();
WriteNormalizedXmlValue(writer, subnode.Value, indent);
lastText = true;
}
else
{
if(lastText) writer.WriteLine();
WriteXmlNode(writer, subnode, indent + " ");
elementExists = true;
lastText = false;
}
}
 
if(elementExists) writer.Write(indent);
writer.WriteLine("</" + node.Name + ">");
}
}
}
 
private void WriteNormalizedXmlValue(StreamWriter writer, string s, string indent)
{
int begin;
int end;
 
bool newLineFound = false;
foreach(char c in s)
{
if(c == '\n')
{
newLineFound = true;
break;
}
}
 
if(newLineFound)
{
writer.WriteLine();
writer.Write(indent + " ");
}
 
// trim
for(begin = 0; begin < s.Length; begin++)
{
char c = s[begin];
if(c != '\n' && c != '\r' && c != '\t' && c != ' ') break;
}
for(end = s.Length-1; end >= 0; end--)
{
char c = s[end];
if(c != '\n' && c != '\r' && c != '\t' && c != ' ') break;
}
 
StringBuilder b = new StringBuilder(s.Length);
for(int i = begin; i <= end; i++)
{
char c = s[i];
 
switch(c)
{
case '\n':
if(b.Length > 0)
{
writer.Write(b.ToString());
b.Length = 0;
}
 
writer.WriteLine();
writer.Write(indent + " ");
break;
 
case '\r':
break;
 
case '\t':
b.Append(" ");
break;
 
case '&':
b.Append("&amp;");
break;
 
case '<':
b.Append("&lt;");
break;
 
case '>':
b.Append("&gt;");
break;
 
default:
b.Append(c);
break;
}
}
 
if(b.Length > 0)
writer.Write(b.ToString());
 
if(newLineFound)
{
writer.WriteLine();
writer.Write(indent + " ");
}
}
}
 
#endregion node display classes
 
#region other classes
private abstract class Utils
{
public static string BytesToString(byte[] bytes, int length)
{
if(bytes == null) return null;
 
char[] chars = new char[length + 1];
Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
int charLen = decoder.GetChars(bytes, 0, length, chars, 0);
 
for(int i = 0; i < charLen; i++)
{
char c = chars[i];
if(c != '\n' && c != '\r' && Char.IsControl(c)) chars[i] = '.';
}
 
return (new System.String(chars)).Substring(0, charLen);
}
 
public static ArrayList SplitLine(string line)
{
if(line == null) return null;
 
ArrayList res = new ArrayList();
StringBuilder b = new StringBuilder(200);
 
foreach(char c in line)
{
switch(c)
{
case '\r':
break;
 
case '\n':
res.Add(b.ToString());
b.Length = 0;
break;
 
default:
b.Append(c);
break;
}
}
 
res.Add(b.ToString());
 
return res;
}
}
#endregion other classes
}
 
public class LogMessages
{
private ArrayList messages = new ArrayList();
private ListBox logBox;
private LogLevel level = LogLevel.Debug;
 
public LogLevel Level
{
get { return level; }
set { level = value; Update(); }
}
 
public LogMessages(ListBox logBox)
{
this.logBox = logBox;
}
 
public ArrayList Messages
{
get { return new ArrayList(messages); }
}
 
public void Add(LogMessage message)
{
messages.Add(message);
if(Show(message))
logBox.TopIndex = logBox.Items.Count - 1;
}
 
public void Clear()
{
messages.Clear();
logBox.Items.Clear();
}
 
public void Update()
{
logBox.BeginUpdate();
 
logBox.Items.Clear();
foreach(LogMessage message in messages)
{
Show(message);
}
logBox.TopIndex = logBox.Items.Count - 1;
 
logBox.EndUpdate();
}
 
private bool Show(LogMessage message)
{
if(message.Level > this.level) return false;
 
try
{
logBox.Items.Add(message.ToString());
}
catch(ObjectDisposedException) {}
 
return true;
}
}
 
public class LogMessage
{
private TcpConnection tcp;
private LogLevel level;
private string message;
private Exception exception;
private DateTime timestamp = DateTime.Now;
 
public TcpConnection Tcp
{
get { return tcp; }
set { tcp = value; }
}
 
public LogLevel Level
{
get { return level; }
set { level = value; }
}
 
public string Message
{
get { return message; }
set { message = value; }
}
 
public Exception Exception
{
get { return exception; }
set { exception = value; }
}
 
public DateTime Timestamp
{
get { return timestamp; }
set { timestamp = value; }
}
 
public LogMessage(TcpConnection tcp, LogLevel level, string message, Exception exception)
{
this.tcp = tcp;
this.level = level;
this.message = message;
this.exception = exception;
}
 
public override string ToString()
{
string l = " ";
switch(level)
{
case LogLevel.Debug: l = "debug"; break;
case LogLevel.Info: l = "info "; break;
case LogLevel.Important: l = "Imp "; break;
case LogLevel.Warning: l = "Warn "; break;
case LogLevel.Error: l = "ERROR"; break;
case LogLevel.Critical: l = "CRIT "; break;
}
 
return (tcp == null ? "...." : tcp.Id) + " " + l + " " + timestamp.ToString("HH:mm:ss.ffff") + " "
+ (exception != null ? exception.Message : message);
}
}
 
public enum TcpShowMode
{
ByDirection,
ByTime
}
}
/TCPproxy/trunk/src/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,
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;
}
}
}
 
}
/TCPproxy/trunk/src/AssemblyInfo.cs
0,0 → 1,32
using System.Reflection;
using System.Runtime.CompilerServices;
 
// Information about this assembly is defined by the following
// attributes.
//
// change them to the information which is associated with the assembly
// you compile.
 
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
 
// The assembly version has following format :
//
// Major.Minor.Build.Revision
//
// You can specify all values by your own or you can build default build and revision
// numbers with the '*' character (the default):
 
[assembly: AssemblyVersion("1.0.*")]
 
// The following attributes specify the key for the sign of your assembly. See the
// .NET Framework documentation for more information about signing.
// This is not required, if you don't want signing let these attributes like they're.
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
/TCPproxy/trunk/src/ViewControl.cs
0,0 → 1,1707
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Text;
using System.Runtime.InteropServices;
 
namespace TCPproxy
{
// FIXME scroll line-by-line in word-wrap mode
// FIXME implement copy by keyboard commands
// FIXME show bin data
// FIXME abillity to insert text after saved marker
// FIXME don't use double buffering
// FIXME move markers and selection when text is changed
public class ViewControl : Control
{
private struct LineFragment
{
public string text;
public Color color;
public Color backColor;
public bool italic;
public bool bold;
public int indent;
public int wrapIndent;
}
 
private class ViewControlState
{
public bool deep;
public ArrayList lineBackColors;
public ArrayList lineFragments;
public ArrayList validMarkers;
public int curLine;
public int curCol;
public int selBeginLineOrig;
public int selBeginPosOrig;
public int selEndLineOrig;
public int selEndPosOrig;
}
 
private class TextMarker
{
public int beginLine;
public int beginPos;
public int endLine;
public int endPos;
 
public override string ToString()
{
return string.Format("{0},{1} - {2},{3}", beginLine, beginPos, endLine, endPos); // FIXME debug only
}
}
 
private static int NEW_LINE_LENGTH = 20;
 
private ArrayList lineBackColors = new ArrayList();
private ArrayList lineFragments = new ArrayList();
private int lastLineLen = 0;
private int linesCount = 0;
private int maxLineLength = 0;
 
private int curLine = 0;
private int curCol = 0;
private bool updateBlocked = false;
private int linesVisible = 0;
private int colsVisible = 0;
private float fontWidth = -1.0f;
private float fontHeight = -1.0f;
private bool wordWrap = false;
 
private ArrayList validMarkers = new ArrayList();
 
private ScrollBarsControl vScrollBar;
private ScrollBarsControl hScrollBar;
 
private StringFormat fontMeasureFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
 
private Font normal;
private Font italic;
private Font bold;
private Font boldIt;
 
private int selBeginLine = -1;
private int selBeginPos = -1;
private int selBeginLineOrig = -1;
private int selBeginPosOrig = -1;
private int selEndLine = -1;
private int selEndPos = -1;
private int selEndLineOrig = -1;
private int selEndPosOrig = -1;
 
public ViewControl()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer
| ControlStyles.ResizeRedraw, true);
 
fontMeasureFormat.LineAlignment = StringAlignment.Near;
fontMeasureFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox
| StringFormatFlags.NoWrap | StringFormatFlags.NoClip;
 
vScrollBar = new ScrollBarsControl(this, true);
vScrollBar.SmallChange = 1;
vScrollBar.Value = 0;
vScrollBar.Minimum = 0;
vScrollBar.Maximum = 0;
vScrollBar.Scroll += new ScrollEventHandler(this.vScrollBarScroll);
 
hScrollBar = new ScrollBarsControl(this, false);
hScrollBar.Scroll += new ScrollEventHandler(this.hScrollBarScroll);
 
RecalcParams();
}
 
const int WS_VSCROLL = 0x200000;
const int WS_HSCROLL = 0x100000;
const int WS_EX_CLIENTEDGE = 0x000200;
 
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
if(!base.DesignMode)
{
cp.Style = cp.Style | WS_HSCROLL | WS_VSCROLL;
cp.ExStyle = cp.ExStyle | WS_EX_CLIENTEDGE;
}
return cp;
}
}
 
public bool WordWrap
{
get { return wordWrap; }
set
{
wordWrap = value;
RecalcParams();
Invalidate();
// FIXME recalc number of lines for the vertical scroll bar
}
}
 
public void Clear()
{
lock(this)
{
lineBackColors = new ArrayList();
lineFragments = new ArrayList();
validMarkers = new ArrayList();
lastLineLen = 0;
linesCount = 0;
maxLineLength = 0;
curLine = 0;
curCol = 0;
selBeginLine = -1;
selBeginPos = -1;
selBeginLineOrig = -1;
selBeginPosOrig = -1;
selEndLine = -1;
selEndPos = -1;
selEndLineOrig = -1;
selEndPosOrig = -1;
 
if(!updateBlocked)
{
vScrollBar.Update(curLine, 0, linesCount, 1, 1);
UpdateHScrollBar();
Invalidate();
}
}
}
 
private void UpdateHScrollBar()
{
if(wordWrap)
hScrollBar.Update(0, 0, 0, 1, 1);
else
hScrollBar.Update(curCol, 0, maxLineLength + 2, 1, colsVisible);
}
 
// FIXME allow to save 'frozen' state
public object SaveState(bool deep)
{
if(deep) throw new NotImplementedException("Deep save is not yet implemented");
 
ViewControlState state = new ViewControlState();
 
lock(this)
{
state.deep = deep;
state.lineBackColors = lineBackColors;
state.lineFragments = lineFragments;
state.validMarkers = validMarkers;
state.curLine = curLine;
state.curCol = curCol;
state.selBeginLineOrig = selBeginLineOrig;
state.selBeginPosOrig = selBeginPosOrig;
state.selEndLineOrig = selEndLineOrig;
state.selEndPosOrig = selEndPosOrig;
}
 
return state;
}
 
public void RestoreState(object state, bool restorePosition)
{
if(!(state is ViewControlState))
throw new ArgumentException("Can restore only from object returned by SaveState()");
 
ViewControlState s = (ViewControlState)state;
 
if(s.deep) throw new NotImplementedException("Deep restore is not yet implemented");
 
if(lineBackColors.Count != lineFragments.Count) throw new ArgumentException("Wrong state");
 
lock(this)
{
lineBackColors = s.lineBackColors;
lineFragments = s.lineFragments;
linesCount = lineFragments.Count;
 
validMarkers = s.validMarkers;
 
lastLineLen = (linesCount > 0) ? ((LineFragment[])lineFragments[linesCount-1]).Length : 0;
 
maxLineLength = 0;
foreach(LineFragment[] line in lineFragments)
{
int lineLength = 0;
foreach(LineFragment frag in line)
{
lineLength += frag.indent + (frag.text == null ? 0 : frag.text.Length);
}
if(lineLength > maxLineLength) maxLineLength = lineLength;
}
 
if(restorePosition)
{
curLine = s.curLine;
curCol = s.curCol;
selBeginLineOrig = s.selBeginLineOrig;
selBeginPosOrig = s.selBeginPosOrig;
selEndLineOrig = s.selEndLineOrig;
selEndPosOrig = s.selEndPosOrig;
}
else
{
curLine = 0;
curCol = 0;
selBeginLineOrig = -1;
selBeginPosOrig = -1;
selEndLineOrig = -1;
selEndPosOrig = -1;
}
 
UpdateSelection();
updateBlocked = false;
 
vScrollBar.Update(curLine, 0, linesCount, 1, linesVisible);
UpdateHScrollBar();
Invalidate();
}
}
 
public object BeginMark()
{
TextMarker marker = new TextMarker();
 
lock(this)
{
marker.beginLine = linesCount-1;
marker.beginPos = lastLineLen;
 
validMarkers.Add(new WeakReference(marker));
}
 
return marker;
}
 
public void EndMark(object marker)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
m.endLine = linesCount-1;
m.endPos = lastLineLen;
}
}
 
public void BeginUpdate()
{
updateBlocked = true;
}
 
public void EndUpdate()
{
lock(this)
{
updateBlocked = false;
 
vScrollBar.Update(curLine, 0, linesCount, 1, linesVisible);
UpdateHScrollBar();
Invalidate();
}
}
 
public void AppendText(string text)
{
lock(this)
{
LineFragment[] line = null;
int lineLen = 0;
LineFragment fragment;
 
for(int i = linesCount - 1; i >= 0; i--)
{
line = (LineFragment[])lineFragments[i];
lineLen = (i == linesCount - 1) ? lastLineLen : line.Length;
if(lineLen > 0) break;
}
 
if(line == null || lineLen == 0)
{
fragment = new LineFragment();
fragment.color = this.ForeColor;
fragment.backColor = this.BackColor;
fragment.italic = false;
fragment.bold = false;
fragment.indent = 0;
fragment.wrapIndent = 0;
}
else
{
fragment = line[lineLen-1];
fragment.indent = 0;
}
 
fragment.text = text;
AppendText(fragment);
}
}
 
public void AppendText(string text, Color color, Color backColor, bool italic, bool bold, int indent, int wrapIndent)
{
LineFragment fragment;
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
lock(this)
{
AppendText(fragment);
}
}
 
// always called in lock(this)
private void AppendText(LineFragment fragment)
{
LineFragment[] lastLine = (linesCount > 0) ? (LineFragment[])lineFragments[linesCount-1] : null;
 
if(lastLine == null)
{
lastLine = AddEmptyLine(Color.Transparent);
}
else if(lastLine.Length == lastLineLen)
{
LineFragment[] newLine = new LineFragment[lastLineLen * 2];
Array.Copy(lastLine, 0, newLine, 0, lastLineLen);
lastLine = newLine;
lineFragments[linesCount-1] = lastLine;
}
 
lastLine[lastLineLen++] = fragment;
 
int lineLength = 0;
for(int i = 0; i < lastLineLen; i++)
{
lineLength += lastLine[i].indent + (lastLine[i].text == null ? 0 : lastLine[i].text.Length);
}
if(lineLength > maxLineLength)
{
maxLineLength = lineLength;
if(!updateBlocked && !wordWrap) hScrollBar.Maximum = maxLineLength + 2;
}
 
if(!updateBlocked)
{
if(curLine + linesVisible + 1 >= lastLineLen) Invalidate();
}
}
 
public void AppendNewLine()
{
AppendNewLine(Color.Transparent);
}
 
public void AppendNewLine(Color backColor)
{
lock(this)
{
SaveLastLine();
AddEmptyLine(backColor);
}
}
 
private TextMarker GetTextMarker(object marker)
{
if(!(marker is TextMarker))
throw new ArgumentException("Can mark only with object returned by BeginMark()");
 
TextMarker m = (TextMarker)marker;
 
// check if marker is valid
bool isValid = false;
for(int i = validMarkers.Count-1; i >= 0; i--)
{
WeakReference refer = (WeakReference)validMarkers[i];
 
if(!refer.IsAlive || refer.Target == null)
{
validMarkers.RemoveAt(i);
}
else
{
if(refer.Target == m)
{
isValid = true;
}
}
}
if(!isValid) throw new ArgumentException("Invalid marker");
 
if(m.beginLine > linesCount)
throw new ArgumentException("Wrong marker");
 
LineFragment[] line = (linesCount > 0) ? (LineFragment[])lineFragments[m.beginLine] : null;
int lineLen = (linesCount == 0 || m.beginLine == linesCount-1) ? lastLineLen : line.Length;
 
if(m.beginPos > lineLen)
throw new ArgumentException("Wrong marker");
 
return m;
}
 
public void DeleteText(object marker)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
// delete the first line
int lineDeleted = DeleteLinePart(m.beginLine, m.beginPos, (m.beginLine == m.endLine) ? m.endPos : -1);
 
// delete the middle
for(int i = m.beginLine + 1; i < m.endLine; i++)
{
lineFragments.RemoveAt(m.beginLine - lineDeleted + 1);
lineBackColors.RemoveAt(m.beginLine - lineDeleted + 1);
linesCount--;
}
if(m.endLine > m.beginLine) lineDeleted += m.endLine - m.beginLine - 1;
 
// delete the last line
if(m.beginLine != m.endLine && m.endLine < linesCount && m.endPos > 0)
DeleteLinePart(m.endLine - lineDeleted, 0, m.endPos);
 
// update screen
if(!updateBlocked)
{
Invalidate();
}
 
int lastLine = m.endLine;
ShiftTextMarkers(m.beginLine, m.beginPos, -lineDeleted, 0);
ShiftTextMarkers(lastLine, m.endPos, 0, -(m.endPos - (m.beginLine == lastLine ? m.beginPos : 0)));
}
}
 
private int DeleteLinePart(int lineNum, int begin, int end)
{
int deleted = 0;
LineFragment[] line = (LineFragment[])lineFragments[lineNum];
int lineLen = (lineNum == linesCount - 1) ? lastLineLen : line.Length;
 
if(end == 0) return 0;
 
if(end < 0) end = lineLen;
 
if(begin == 0 && end == lineLen)
{
// delete whole line
deleted = 1;
lineFragments.RemoveAt(lineNum);
lineBackColors.RemoveAt(lineNum);
linesCount--;
}
else
{
// delete part of line
LineFragment[] newLine = new LineFragment[(lineNum == linesCount - 1) ? line.Length : lineLen - end + begin];
if(begin > 0) Array.Copy(line, 0, newLine, 0, begin);
if(end < lineLen) Array.Copy(line, end, newLine, begin, lineLen - end);
if(lineNum == linesCount - 1) lastLineLen = lineLen - end + begin;
lineFragments[lineNum] = newLine;
}
 
return deleted;
}
 
public void ChangeText(object marker, string text, Color color, Color backColor,
bool italic, bool bold, int indent, int wrapIndent)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
// FIXME implement for multiple lines and fragments
 
LineFragment[] line = (LineFragment[])lineFragments[m.beginLine];
LineFragment fragment = line[m.beginPos];
 
if(fragment.text == text && fragment.color == color && fragment.backColor == backColor
&& fragment.italic == italic && fragment.bold == bold && fragment.indent == indent
&& fragment.wrapIndent == wrapIndent)
{
return; // the fragment is not changed
}
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
line[m.beginPos] = fragment;
 
if(!updateBlocked)
{
if(m.beginLine >= curLine && m.beginLine <= curLine + linesVisible + 1) Invalidate();
}
}
}
 
// side effect - the given marker is moved to position after the inserted text
public void InsertText(object marker, string text, Color color, Color backColor,
bool italic, bool bold, int indent, int wrapIndent)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
LineFragment[] line = (linesCount > m.endLine) ? (LineFragment[])lineFragments[m.endLine] : null;
LineFragment fragment = new LineFragment();
 
fragment.text = text;
fragment.color = color;
fragment.backColor = backColor;
fragment.italic = italic;
fragment.bold = bold;
fragment.indent = indent;
fragment.wrapIndent = wrapIndent;
 
if(line == null)
{
line = AddEmptyLine(Color.Transparent);
lastLineLen++;
}
else if(m.endLine == linesCount - 1)
{
if(line.Length <= lastLineLen || m.endPos < lastLineLen)
{
LineFragment[] newLine = new LineFragment[lastLineLen * (line.Length == lastLineLen ? 2 : 1)];
 
if(m.endPos > 0) Array.Copy(line, 0, newLine, 0, m.endPos);
if(lastLineLen > m.endPos) Array.Copy(line, m.endPos, newLine, m.endPos+1, lastLineLen - m.endPos);
line = newLine;
lineFragments[m.endLine] = line;
}
lastLineLen++;
}
else
{
LineFragment[] newLine = new LineFragment[line.Length + 1];
if(m.endPos > 0) Array.Copy(line, 0, newLine, 0, m.endPos);
if(line.Length > m.endPos) Array.Copy(line, m.endPos, newLine, m.endPos+1, line.Length - m.endPos);
line = newLine;
lineFragments[m.endLine] = line;
}
 
line[m.endPos] = fragment;
 
// recalc text width
int lineLength = 0;
int newLineLen = (m.endLine == linesCount - 1) ? lastLineLen : line.Length;
for(int i = 0; i < newLineLen; i++)
{
lineLength += line[i].indent + (line[i].text == null ? 0 : line[i].text.Length);
}
if(lineLength > maxLineLength)
{
maxLineLength = lineLength;
if(!updateBlocked && !wordWrap) hScrollBar.Maximum = maxLineLength + 2;
}
 
// update screen
if(!updateBlocked)
{
if(m.endLine >= curLine && m.endLine <= curLine + linesVisible + 1) Invalidate();
}
 
ShiftTextMarkers(m.endLine, m.endPos, 0, 1);
if(m.beginLine == m.endLine && m.beginPos == m.endPos) m.beginPos--; // return the begin back if empty marker
}
}
 
public void InsertNewLine(object marker)
{
InsertNewLine(marker, Color.Transparent);
}
 
public void InsertNewLine(object marker, Color backColor)
{
lock(this)
{
TextMarker m = GetTextMarker(marker);
 
if(m.endLine == linesCount || (m.endLine == linesCount - 1 && m.endPos == lastLineLen))
{
SaveLastLine();
AddEmptyLine(backColor);
}
else
{
LineFragment[] oldLine = (LineFragment[])lineFragments[m.endLine];
int oldLineLen = (m.endLine == linesCount - 1) ? lastLineLen : oldLine.Length;
LineFragment[] line1 = new LineFragment[m.endPos];
LineFragment[] line2 = new LineFragment[
(m.endLine == linesCount - 1) ? NEW_LINE_LENGTH : oldLine.Length - m.endPos];
 
if(m.endPos > 0) Array.Copy(oldLine, 0, line1, 0, m.endPos);
if(oldLineLen - m.endPos > 0) Array.Copy(oldLine, m.endPos, line2, 0, oldLineLen - m.endPos);
 
lineFragments[m.endLine] = line1;
lineFragments.Insert(m.endLine + 1, line2);
lineBackColors.Insert(m.endLine + 1, backColor);
linesCount++;
 
if(m.endLine == linesCount - 1) lastLineLen = oldLineLen - m.endPos;
}
 
// update screen
if(!updateBlocked)
{
if(m.endLine >= curLine && m.endLine <= curLine + linesVisible + 1) Invalidate();
vScrollBar.Maximum = linesCount;
}
 
ShiftTextMarkers(m.endLine, m.endPos, 1, 0);
if(m.beginLine == m.endLine && m.beginLine == m.endLine) m.beginLine--; // return the begin back if empty marker
}
}
 
private void ShiftTextMarkers(int line, int pos, int lineDelta, int posDelta)
{
for(int i = validMarkers.Count-1; i >= 0; i--)
{
WeakReference refer = (WeakReference)validMarkers[i];
 
if(!refer.IsAlive || refer.Target == null)
{
validMarkers.RemoveAt(i);
}
else
{
TextMarker m = (TextMarker)refer.Target;
 
ShiftTextMarkerBorder(ref m.beginLine, ref m.beginPos, line, pos, lineDelta, posDelta);
ShiftTextMarkerBorder(ref m.endLine, ref m.endPos, line, pos, lineDelta, posDelta);
}
}
}
 
private void ShiftTextMarkerBorder(ref int borderLine, ref int borderPos,
int line, int pos, int lineDelta, int posDelta)
{
if(borderLine > line)
{
borderLine += lineDelta;
if(borderLine < line) borderLine = line;
}
else if(borderLine == line)
{
if(lineDelta != 0)
{
if(borderPos >= pos)
{
borderLine += lineDelta;
if(borderLine < line) borderLine = line;
 
borderPos -= pos - posDelta;
}
}
else
{
if(borderPos >= pos) borderPos += posDelta;
}
}
}
 
private void SaveLastLine()
{
if(linesCount > 0)
{
LineFragment[] lastLine = (LineFragment[])lineFragments[linesCount-1];
LineFragment[] newLine = new LineFragment[lastLineLen];
Array.Copy(lastLine, 0, newLine, 0, lastLineLen);
lineFragments[linesCount-1] = newLine;
}
}
 
private LineFragment[] AddEmptyLine(Color backColor)
{
LineFragment[] lastLine = new LineFragment[NEW_LINE_LENGTH];
lastLineLen = 0;
lineBackColors.Add(backColor);
lineFragments.Add(lastLine);
 
linesCount++;
 
if(!updateBlocked) vScrollBar.Maximum = linesCount;
 
return lastLine;
}
 
public override Font Font
{
get
{
return base.Font;
}
 
set
{
lock(this)
{
base.Font = value;
 
normal = new Font(this.Font.FontFamily, this.Font.Size);
italic = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Italic);
bold = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Bold);
boldIt = new Font(this.Font.FontFamily, this.Font.Size, FontStyle.Bold | FontStyle.Italic);
 
fontWidth = -1.0f;
RecalcParams();
Invalidate();
}
}
}
 
private void RecalcParams()
{
fontHeight = this.Font.GetHeight();
linesVisible = (fontHeight > 0) ? (int)Math.Ceiling(this.ClientSize.Height / fontHeight) : 0;
vScrollBar.LargeChange = linesVisible;
 
RecalcColsVisible();
}
 
private void UpdateFontWidth(Graphics g)
{
fontWidth = g.MeasureString("x", this.Font, int.MaxValue, fontMeasureFormat).Width;
RecalcColsVisible();
}
 
private void RecalcColsVisible()
{
colsVisible = (fontWidth > 0) ? (int)Math.Ceiling(this.ClientSize.Width / fontWidth) : 0;
UpdateHScrollBar();
}
 
protected override void OnSystemColorsChanged(EventArgs e)
{
Invalidate();
base.OnSystemColorsChanged(e);
}
 
protected override void OnResize(EventArgs e)
{
lock(this)
{
RecalcParams();
}
base.OnResize(e);
}
 
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
 
protected override void OnPaint(PaintEventArgs e)
{
lock(this)
{
try
{
Graphics g = e.Graphics;
SolidBrush brush = new SolidBrush(Color.Black);
 
g.FillRectangle(SystemBrushes.Window, g.Clip.GetBounds(g));
 
if(fontWidth <= 0) UpdateFontWidth(g);
 
int drawLine = 0;
for(int lineNumber = curLine; drawLine < linesVisible && lineNumber < linesCount; lineNumber++)
{
Color backColor = (Color)lineBackColors[lineNumber];
LineFragment[] line = (LineFragment[])lineFragments[lineNumber];
int lineLen = (lineNumber == linesCount-1) ? lastLineLen : line.Length;
 
// line background
if(lineLen == 0 && selBeginLine <= lineNumber && lineNumber <= selEndLine)
{
brush.Color = SystemColors.Highlight;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
else if(backColor != Color.Transparent)
{
brush.Color = backColor;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
 
int indent = 0;
int shift = curCol;
for(int j = 0; j < lineLen; j++)
{
LineFragment fragment = line[j];
int curIndent = fragment.indent;
string text = fragment.text;
int textLen = (text == null) ? 0 : text.Length;
 
// calc shift using curCol
if(shift > 0)
{
if(shift < fragment.indent)
{
curIndent -= shift;
shift = 0;
}
else if(shift < textLen + fragment.indent)
{
if(text != null)
{
text = text.Substring(shift - fragment.indent);
textLen = text.Length;
}
shift = 0;
curIndent = 0;
}
else
{
curIndent = 0;
shift -= textLen + fragment.indent;
text = null;
textLen = 0;
}
}
 
// draw the line, m.b. split it to several display lines
while(true)
{
string drawText = text;
int drawLen = textLen;
bool lineWraped = false;
 
// wrap line
// FIXME try to wrap whole words only
// FIXME test if wrap is in fragment indent
if(wordWrap)
{
int visibleLen = colsVisible - 2;
int availableLen = visibleLen - indent - curIndent;
if(textLen > availableLen)
{
if(textLen == 0 || availableLen < 0)
{
drawText = null;
drawLen = 0;
text = null;
textLen = 0;
}
else if(availableLen == 0)
{
drawText = null; // only wrap line, draw nothing on current one
drawLen = 0;
}
else
{
drawText = text.Substring(0, availableLen);
drawLen = drawText.Length;
text = text.Substring(availableLen);
textLen = text.Length;
}
lineWraped = true;
}
}
 
// draw background
if(fragment.backColor != Color.Transparent && (indent >= 0) && (drawLen + curIndent > 0))
{
brush.Color = fragment.backColor;
g.FillRectangle(brush, indent * fontWidth, drawLine * fontHeight,
(float)Math.Ceiling((drawLen + curIndent) * fontWidth), fontHeight);
}
 
// draw selection background
int selBegin = -1;
int selEnd = -1;
if(selBeginLine <= lineNumber && lineNumber <= selEndLine)
{
selBegin = (lineNumber == selBeginLine) ? Math.Max(selBeginPos - curCol, 0) : 0;
selEnd = (lineNumber == selEndLine) ? selEndPos - curCol : colsVisible;
 
int selBeginBack = Math.Max(indent, selBegin);
int selLen = (j == lineLen-1) ? (selEnd - selBeginBack)
: Math.Min(drawLen + curIndent, selEnd - selBeginBack);
 
if(selLen > 0)
{
brush.Color = SystemColors.Highlight;
g.FillRectangle(brush, selBeginBack * fontWidth, drawLine * fontHeight,
(float)Math.Ceiling(selLen* fontWidth), fontHeight);
}
}
 
// draw the text
indent += curIndent;
if(drawText != null)
{
Font font = (fragment.bold) ? (fragment.italic ? boldIt : bold) : (fragment.italic ? italic : normal);
 
// split the fragment text into 3 pieces: before selection, the selection, after selection
// (some may be empty)
string s1 = drawText, s2 = null, s3 = null;
int p2 = 0, p3 = 0;
 
if(selBegin >= 0)
{
int begin = Math.Max(0, selBegin - indent);
if(begin < drawLen)
{
int len = Math.Min(selEnd - indent - begin, drawLen - begin);
if(len > 0)
{
s1 = (begin == 0) ? null : drawText.Substring(0, begin);
s2 = (begin == 0 && len == drawLen) ? drawText : drawText.Substring(begin, len);
s3 = (begin + len >= drawLen) ? null : drawText.Substring(begin + len);
}
}
}
 
if(s1 != null)
{
brush.Color = fragment.color;
g.DrawString(s1, font, brush, indent * fontWidth, drawLine * fontHeight, fontMeasureFormat);
p2 = s1.Length;
}
 
if(s2 != null)
{
brush.Color = SystemColors.HighlightText;
g.DrawString(s2, font, brush, (indent + p2) * fontWidth, drawLine * fontHeight, fontMeasureFormat);
p3 = p2 + s2.Length;
}
 
if(s3 != null)
{
brush.Color = fragment.color;
g.DrawString(s3, font, brush, (indent + p3) * fontWidth, drawLine * fontHeight, fontMeasureFormat);
}
 
indent += drawLen;
}
 
if(lineWraped)
{
indent = fragment.wrapIndent;
curIndent = 0;
drawLine++;
 
if(drawLine >= linesVisible) break;
 
if(backColor != Color.Transparent)
{
brush.Color = backColor;
g.FillRectangle(brush, 0, drawLine * fontHeight, this.ClientSize.Width, fontHeight);
}
}
else
{
break;
}
}
}
 
drawLine++;
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + "\n" + ex.StackTrace);
}
}
}
 
public void SelectAll()
{
lock(this)
{
selBeginLineOrig = 0;
selBeginPosOrig = 0;
selEndLineOrig = linesCount-1;
selEndPosOrig = 0;
 
if(linesCount > 0)
{
LineFragment[] line = (LineFragment[])lineFragments[linesCount - 1];
 
for(int i = 0; i < lastLineLen; i++)
{
selEndPosOrig += line[i].indent + ((line[i].text != null) ? line[i].text.Length : 0);
}
}
 
UpdateSelection();
Invalidate();
}
}
 
public string SelectedText
{
get
{
lock(this)
{
if(selBeginLine < 0) return null;
 
StringBuilder b = new StringBuilder((selEndLine - selBeginLine + 1) * maxLineLength);
 
for(int lineNumber = selBeginLine; lineNumber <= selEndLine && lineNumber < linesCount; lineNumber++)
{
int cur = 0;
int begin = (lineNumber == selBeginLine) ? Math.Max(selBeginPos, 0) : 0;
int end = (lineNumber == selEndLine) ? Math.Max(selEndPos, 0) : int.MaxValue;
 
LineFragment[] line = (LineFragment[])lineFragments[lineNumber];
int lineLen = (lineNumber == linesCount-1) ? lastLineLen : line.Length;
for(int i = 0; i < lineLen; i++)
{
LineFragment fragment = line[i];
 
if(cur + fragment.indent <= begin || cur >= end)
{
cur += fragment.indent;
}
else
{
for(int j = fragment.indent - 1; j >= 0; j--)
{
if(cur >= begin && cur < end) b.Append(' ');
cur++;
}
}
 
if(fragment.text != null)
{
int curEnd = cur + fragment.text.Length;
if(begin <= cur && curEnd <= end)
b.Append(fragment.text);
else if(begin <= cur && cur < end || begin < curEnd && curEnd <= end || cur <= begin && end <= curEnd)
{
int copyBegin = Math.Max(begin - cur, 0);
b.Append(fragment.text.Substring(copyBegin, Math.Min(end, curEnd) - cur - copyBegin));
}
 
cur += fragment.text.Length;
}
 
if(cur >= end) break;
if(i == lineLen - 1) b.Append("\r\n");
}
}
 
return b.ToString();
}
}
}
 
private void vScrollBarScroll(Object sender, ScrollEventArgs e)
{
MoveToVertical(e.NewValue, true);
}
 
private void hScrollBarScroll(Object sender, ScrollEventArgs e)
{
MoveToHorizontal(e.NewValue, true);
}
 
private void MoveToVertical(int pos, bool invalidate)
{
curLine = pos;
if(curLine + linesVisible > linesCount) curLine = linesCount - linesVisible + 1;
if(curLine < 0) curLine = 0;
vScrollBar.Value = curLine;
if(invalidate) this.Invalidate();
}
 
private void MoveByVertical(int delta, bool invalidate)
{
MoveToVertical(curLine + delta, invalidate);
}
 
private void MoveToHorizontal(int pos, bool invalidate)
{
if(wordWrap)
{
curCol = 0;
}
else
{
curCol = pos;
if(curCol < 0) curCol = 0;
hScrollBar.Value = curCol;
}
if(invalidate) this.Invalidate();
}
 
private void MoveByHorizontal(int delta, bool invalidate)
{
MoveToHorizontal(curCol + delta, invalidate);
}
 
protected override bool IsInputKey(Keys keyData)
{
switch(keyData)
{
case Keys.PageDown:
case Keys.PageUp:
case Keys.Down:
case Keys.Up:
case Keys.Left:
case Keys.Right:
case Keys.Home:
case Keys.End:
return true;
default:
return base.IsInputKey(keyData);
}
}
 
protected override void OnKeyDown(KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.PageDown:
lock(this)
{
MoveByVertical(linesVisible, true);
}
break;
 
case Keys.PageUp:
lock(this)
{
MoveByVertical(-linesVisible, true);
}
break;
case Keys.Down:
lock(this)
{
MoveByVertical(1, true);
}
break;
case Keys.Up:
lock(this)
{
MoveByVertical(-1, true);
}
break;
case Keys.Right:
lock(this)
{
MoveByHorizontal(1, true);
}
break;
case Keys.Left:
lock(this)
{
MoveByHorizontal(-1, true);
}
break;
case Keys.Home:
lock(this)
{
MoveToHorizontal(0, false);
MoveToVertical(0, true);
}
break;
case Keys.End:
lock(this)
{
MoveToHorizontal(0, false);
MoveToVertical(linesCount - linesVisible + 1, true);
}
break;
default:
base.OnKeyDown(e);
break;
}
}
 
private void UpdateSelection()
{
if(selBeginLineOrig < 0)
{
selBeginLine = -1;
selBeginPos = -1;
selEndLine = -1;
selEndPos = -1;
}
else if(selEndLineOrig < selBeginLineOrig || selEndLineOrig == selBeginLineOrig && selEndPosOrig < selBeginPosOrig)
{ // swap if end before begin
selBeginLine = selEndLineOrig;
selBeginPos = selEndPosOrig;
selEndLine = selBeginLineOrig;
selEndPos = selBeginPosOrig;
}
else
{
selBeginLine = selBeginLineOrig;
selBeginPos = selBeginPosOrig;
selEndLine = selEndLineOrig;
selEndPos = selEndPosOrig;
}
}
 
private void UpdateSelectionEnd(int x, int y)
{
if(fontWidth > 0 && fontHeight > 0 && selBeginLineOrig >= 0)
{
selEndLineOrig = (int)(y / fontHeight) + curLine;
selEndPosOrig = (int)Math.Round(x / fontWidth) + curCol;
 
UpdateSelection();
Invalidate();
}
}
 
protected override void OnMouseMove(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
lock(this)
{
UpdateSelectionEnd(e.X, e.Y);
// FIXME scroll if selection is dragged outside of the control, use timer to scroll
if(e.Y > this.ClientSize.Height && fontWidth > 0)
{
MoveToVertical(selEndLine - linesVisible + 1, true);
}
}
}
 
base.OnMouseMove(e);
}
 
protected override void OnMouseDown(MouseEventArgs e)
{
this.Focus();
 
if(e.Button == MouseButtons.Left)
{
lock(this)
{
if(fontWidth > 0 && fontHeight > 0)
{
selBeginLineOrig = (int)(e.Y / fontHeight) + curLine;
selBeginPosOrig = (int)Math.Round(e.X / fontWidth) + curCol;
selEndLineOrig = selBeginLineOrig;
selEndPosOrig = selBeginPosOrig;
 
UpdateSelection();
Invalidate();
}
}
}
 
base.OnMouseDown(e);
}
 
private const int WHEEL_DELTA = 120;
 
protected override void OnMouseWheel(MouseEventArgs e)
{
lock(this)
{
MoveByVertical(e.Delta / WHEEL_DELTA * SystemInformation.MouseWheelScrollLines * (-1), true);
}
 
base.OnMouseWheel(e);
}
 
private const int WM_ACTIVATE = 0x0006;
private const int WM_ACTIVATEAPP = 0x001C;
private const int WM_HSCROLL = 0x0114;
private const int WM_VSCROLL = 0x0115;
 
protected override void WndProc(ref System.Windows.Forms.Message m)
{
switch(m.Msg)
{
case WM_VSCROLL:
vScrollBar.HandleWindowMessage(ref m);
break;
 
case WM_HSCROLL:
hScrollBar.HandleWindowMessage(ref m);
break;
 
default:
base.WndProc(ref m);
break;
}
}
 
private class ScrollBarsControl
{
private Control owner;
private bool vertical;
private int smallChange;
private int largeChange;
private int minimum;
private int maximum;
private bool showAlways = true;
private int currentValue = 0;
private SCROLLINFO scrollInfo;
 
public event ScrollEventHandler Scroll;
 
public ScrollBarsControl(Control owner, bool vertical)
{
this.owner = owner;
this.vertical = vertical;
 
scrollInfo = new SCROLLINFO();
scrollInfo.cbSize = (uint)Marshal.SizeOf(typeof(SCROLLINFO));
 
// get initial state
scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
GetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo);
minimum = scrollInfo.nMin;
maximum = scrollInfo.nMax;
smallChange = 1;
largeChange = (int)scrollInfo.nPage;
}
 
public void HandleWindowMessage(ref System.Windows.Forms.Message m)
{
switch(m.Msg)
{
case WM_VSCROLL:
if(!vertical)
throw new ArgumentException("I'm horizontal scroll bar, can not handle vertical scroll");
break;
 
case WM_HSCROLL:
if(vertical)
throw new ArgumentException("I'm vertical scroll bar, can not handle horizontal scroll");
break;
 
default:
throw new ArgumentException("Unknown message");
}
 
short cmd = (short)((uint)m.WParam & 0x0000FFFF);
ScrollEventType type;
bool update = false;
 
switch(cmd)
{
case SB_LINEDOWN:
type = ScrollEventType.SmallIncrement;
currentValue += smallChange;
update = true;
break;
 
case SB_LINEUP:
type = ScrollEventType.SmallDecrement;
currentValue -= smallChange;
update = true;
break;
 
case SB_PAGEDOWN:
type = ScrollEventType.LargeIncrement;
currentValue += largeChange;
update = true;
break;
 
case SB_PAGEUP:
type = ScrollEventType.LargeDecrement;
currentValue -= largeChange;
update = true;
break;
 
case SB_TOP:
type = ScrollEventType.First;
currentValue = minimum;
update = true;
break;
 
case SB_BOTTOM:
type = ScrollEventType.Last;
currentValue = maximum;
update = true;
break;
 
case SB_ENDSCROLL:
type = ScrollEventType.EndScroll;
break;
 
case SB_THUMBTRACK:
type = ScrollEventType.ThumbTrack;
currentValue = GetTrackPos();
break;
 
case SB_THUMBPOSITION:
type = ScrollEventType.ThumbPosition;
currentValue = GetTrackPos();
update = true;
break;
 
default:
throw new ArgumentException("Unknown command " + cmd);
}
 
UpdateCurrentValue(update);
ScrollEventArgs e = new ScrollEventArgs(type, currentValue);
OnScroll(e);
}
 
private int GetTrackPos()
{
lock(this)
{
scrollInfo.fMask = SIF_TRACKPOS;
GetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo);
return scrollInfo.nTrackPos;
}
}
 
public int Value
{
get
{
return currentValue;
}
 
set
{
currentValue = value;
UpdateCurrentValue(true);
}
}
 
public bool ShowAlways
{
get { return showAlways; }
set
{
showAlways = value;
SetRange(false);
}
}
 
public int Minimum
{
get
{
return minimum;
}
 
set
{
minimum = value;
SetRange(false);
}
}
 
public int Maximum
{
get
{
return maximum;
}
 
set
{
maximum = value;
SetRange(false);
}
}
 
public void Update(int value, int minimum, int maximum, int smallChange, int largeChange)
{
this.currentValue = value;
this.minimum = minimum;
this.maximum = maximum;
this.smallChange = smallChange;
this.largeChange = largeChange;
 
SetRange(true);
}
 
private void SetRange(bool setPos)
{
lock(this)
{
scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nMin = minimum;
scrollInfo.nMax = maximum;
scrollInfo.nPage = (uint)largeChange;
 
if(setPos || currentValue < minimum || currentValue > maximum)
{
if(currentValue < minimum) currentValue = minimum;
if(currentValue > maximum) currentValue = maximum;
scrollInfo.fMask |= SIF_POS;
scrollInfo.nPos = currentValue;
}
 
if(setPos)
{
scrollInfo.fMask |= SIF_PAGE;
scrollInfo.nPage = (uint)largeChange;
}
 
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
public int SmallChange
{
get
{
return smallChange;
}
 
set
{
if(value < 0) throw new ArgumentException("SmallChange must be non-negative");
smallChange = value;
}
}
 
public int LargeChange
{
get
{
return largeChange;
}
 
set
{
if(value < 0) throw new ArgumentException("LargeChange must be non-negative");
largeChange = value;
 
lock(this)
{
scrollInfo.fMask = SIF_PAGE;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nPage = (uint)largeChange;
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
}
 
private void UpdateCurrentValue(bool force)
{
if(!force && currentValue >= minimum && currentValue <= maximum) return;
 
if(currentValue < minimum) currentValue = minimum;
if(currentValue > maximum) currentValue = maximum;
 
lock(this)
{
scrollInfo.fMask = SIF_POS;
if(showAlways) scrollInfo.fMask |= SIF_DISABLENOSCROLL;
scrollInfo.nPos = currentValue;
SetScrollInfo(owner.Handle, vertical ? SB_VERT : SB_HORZ, ref scrollInfo, true);
}
}
 
protected virtual void OnScroll(ScrollEventArgs e)
{
if(Scroll != null)
{
Scroll(this, e);
}
}
 
private const short SB_HORZ = 0;
private const short SB_VERT = 1;
private const short SB_CTL = 2;
private const short SB_BOTH = 3;
 
private const short SB_LINEUP = 0;
private const short SB_LINELEFT = 0;
private const short SB_LINEDOWN = 1;
private const short SB_LINERIGHT = 1;
private const short SB_PAGEUP = 2;
private const short SB_PAGELEFT = 2;
private const short SB_PAGEDOWN = 3;
private const short SB_PAGERIGHT = 3;
private const short SB_THUMBPOSITION = 4;
private const short SB_THUMBTRACK = 5;
private const short SB_TOP = 6;
private const short SB_LEFT = 6;
private const short SB_BOTTOM = 7;
private const short SB_RIGHT = 7;
private const short SB_ENDSCROLL = 8;
 
private const uint SIF_RANGE = 0x0001;
private const uint SIF_PAGE = 0x0002;
private const uint SIF_POS = 0x0004;
private const uint SIF_DISABLENOSCROLL = 0x0008;
private const uint SIF_TRACKPOS = 0x0010;
private const uint SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS);
 
/*
typedef struct tagSCROLLINFO {
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, *LPSCROLLINFO;
typedef SCROLLINFO CONST *LPCSCROLLINFO;
*/
 
[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
private struct SCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
 
/*
BOOL GetScrollInfo(
HWND hwnd,
int fnBar,
LPSCROLLINFO lpsi
);
*/
 
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern bool GetScrollInfo(IntPtr hWnd, int fnBar, ref SCROLLINFO lpsi);
 
/*
int SetScrollInfo(
HWND hwnd,
int fnBar,
LPCSCROLLINFO lpsi,
BOOL fRedraw
);
*/
 
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetScrollInfo(IntPtr hWnd, int fnBar, ref SCROLLINFO lpsi, bool fRedraw);
}
}
}
/TCPproxy/trunk/resources/images/arrow.bmp
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/TCPproxy/trunk/resources/images/connected.bmp
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/TCPproxy/trunk/resources/images/disconnected.bmp
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/TCPproxy/trunk/resources/images/clear.bmp
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property