Subversion Repositories general

Compare Revisions

No changes between revisions

Ignore whitespace Rev 1203 → Rev 1204

/TCPproxy/tags/0001-first-public/MainForm.cs
0,0 → 1,2390
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
// - use pool of threads?
// - do not store intermediate info, just row packets and the parsed fragments to display
// - make the text fragment store switchable
// - save/restore window layout
namespace TCPproxy
{
public class MainForm : System.Windows.Forms.Form
{
#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
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;
#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\Anatoli Klassen\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;
 
// this.Top = (int)subkey.GetValue("Window.Top", this.Top);
// this.Left = (int)subkey.GetValue("Window.Left", this.Left);
// this.Hight = (int)subkey.GetValue("Window.Hight", this.Hight);
// this.Width = (int)subkey.GetValue("Window.Width", this.Width);
}
}
 
private void SaveToRegistry()
{
Microsoft.Win32.RegistryKey subkey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(
@"Software\Anatoli Klassen\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);
 
subkey.SetValue("Window.Top", this.Top);
subkey.SetValue("Window.Left", this.Left);
subkey.SetValue("Window.Hight", this.Height);
subkey.SetValue("Window.Width", this.Width);
}
 
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)
{
// close all connetions
foreach(object tcp in treeNodes.Keys)
if(tcp is TcpConnection)
((TcpConnection)tcp).Cancel();
 
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)
{
lock(this)
{
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)
{
lock(this)
{
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)
{
lock(this)
{
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)
{
lock(this)
{
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)
{
lock(this)
{
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) return; // might be call by Cancel
 
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) return; // might be call by Cancel
 
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 requestStartMarker = 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 responseStartMarker = 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 Start: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
requestStartMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.RequestStartTimestamp == DateTime.MinValue
? "<unknown>" : http.RequestStartTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(requestStartMarker);
owner.messagesBox.AppendNewLine();
 
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 Start: ",
Color.DarkRed, Color.Transparent, false, false, 0, 0);
responseStartMarker = owner.messagesBox.BeginMark();
owner.messagesBox.AppendText(http.ResponseStartTimestamp == DateTime.MinValue
? "<unknown>" : http.ResponseStartTimestamp.ToString("HH:mm:ss.ffff"),
Color.DarkRed, Color.LightGray, false, false, 0, 27);
owner.messagesBox.EndMark(responseStartMarker);
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/tags/0001-first-public/Network.cs
0,0 → 1,2324
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
 
// FIXME deny write access to all public properties
// FIXME for text/xml get encoding from body if not specified in header
// FIXME option to not store parsed data, just raw packets and reparse on demand
namespace TCPproxy
{
public enum LogLevel
{
Critical,
Error,
Warning,
Important,
Info,
Debug
}
 
public enum SocketState
{
None,
Connecting,
Connected,
ShutdownSend,
ShutdownReceived,
Closed
}
 
public enum TcpMessageDirection
{
Local,
Remote
}
 
public enum HttpVersion
{
V0_9,
V1_0,
V1_1
}
 
public enum HttpEncoding
{
Identify,
Gzip,
Compress,
Deflate,
Unknown
}
 
public class TcpLogEventArgs : EventArgs
{
private readonly LogLevel level;
private readonly string message;
private readonly Exception exception;
 
public LogLevel Level
{
get { return level; }
}
 
public string Message
{
get { return message; }
}
 
public Exception Exception
{
get { return exception; }
}
 
internal TcpLogEventArgs(LogLevel level, string message, Exception exception)
{
this.level = level;
this.message = message;
this.exception = exception;
}
}
 
public class TcpEventArgs : EventArgs
{
internal TcpEventArgs()
{
}
}
 
public class TcpConnectionEventArgs : EventArgs
{
private readonly TcpConnection tcp;
 
public TcpConnection Tcp
{
get { return tcp; }
}
 
internal TcpConnectionEventArgs(TcpConnection tcp)
{
this.tcp = tcp;
}
}
 
public class TcpHttpEventArgs : EventArgs
{
private readonly HttpMessage http;
 
public HttpMessage Http
{
get { return http; }
}
 
internal TcpHttpEventArgs(HttpMessage http)
{
this.http = http;
}
}
 
public delegate void TcpLogEventHandler(object sender, TcpLogEventArgs e);
 
public delegate void TcpEventHandler(object sender, TcpEventArgs e);
 
public delegate void TcpConnectionEventHandler(object sender, TcpConnectionEventArgs e);
 
public delegate void TcpHttpEventHandler(object sender, TcpHttpEventArgs e);
 
public class TcpListener
{
private int listenPort = -1;
private IPAddress resendHost;
private int resendPort = -1;
private Socket socket;
private int tcpId = 0;
private ArrayList tcpConnections = new ArrayList();
 
public TcpListener(int listenPort, IPAddress resendHost, int resendPort)
{
this.listenPort = listenPort;
this.resendHost = resendHost;
this.resendPort = resendPort;
}
 
public void StartListening()
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, listenPort));
socket.Listen(100);
socket.BeginAccept(new AsyncCallback(OnClientConnect), null);
SendLog(null, LogLevel.Important, "Listen on " + socket.LocalEndPoint);
}
 
public void StopListening()
{
try
{
if(socket != null)
{
SendLog(null, LogLevel.Important, "Stop listening " + socket.LocalEndPoint);
socket.Close();
}
}
catch(ObjectDisposedException) // socket is already closed
{
}
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
public void CancelAll()
{
ArrayList tcpConnectionsCopy;
 
lock(tcpConnections)
{
tcpConnectionsCopy = new ArrayList(tcpConnections);
}
 
foreach(TcpConnection tcp in tcpConnectionsCopy)
{
tcp.Cancel();
}
}
 
protected virtual void OnClientConnect(IAsyncResult asyn)
{
try
{
Socket worker = socket.EndAccept(asyn);
socket.BeginAccept(new AsyncCallback(OnClientConnect), null); // wait for next client
 
TcpConnection tcp = new TcpConnection(string.Format("{0:0000}", tcpId++));
tcp.Close += new TcpEventHandler(TcpConnectionClosed);
OnNewTcp(new TcpConnectionEventArgs(tcp));
 
lock(tcpConnections)
{
tcpConnections.Add(tcp);
}
 
tcp.Continue(resendHost, resendPort, worker);
}
catch(ObjectDisposedException) {} // socket is closed
catch(Exception ex)
{
Console.WriteLine(ex.Message + " (" + ex.GetType().Name + ")\n" + ex.StackTrace);
}
}
 
protected virtual void TcpConnectionClosed(object sender, TcpEventArgs e)
{
lock(tcpConnections)
{
tcpConnections.Remove((TcpConnection)sender);
}
}
 
public event TcpConnectionEventHandler NewTcp;
 
protected virtual void OnNewTcp(TcpConnectionEventArgs e)
{
if(NewTcp != null)
{
NewTcp(this, e);
}
}
 
public event TcpLogEventHandler Log;
 
protected virtual void OnLog(TcpLogEventArgs e)
{
if(Log != null)
{
Log(this, e);
}
}
 
protected virtual void SendLog(TcpConnection tcp, LogLevel level, string message)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
OnLog(e);
}
 
protected virtual void SendLog(TcpConnection tcp, LogLevel level, Exception ex)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
OnLog(e);
}
}
 
public class TcpConnection
{
private string id;
private DateTime startTimestamp = DateTime.MinValue;
private DateTime localEndTimestamp = DateTime.MinValue;
private DateTime remoteEndTimestamp = DateTime.MinValue;
private IPEndPoint localPoint;
private IPEndPoint remotePoint;
private LinkedList messages = new LinkedList();
private SocketState localState = SocketState.None;
private SocketState remoteState = SocketState.None;
private SocketWorker worker;
private LinkedList https = new LinkedList();
private HttpParser httpParser;
 
public string Id
{
get { return id; }
}
 
public DateTime StartTimestamp
{
get { return startTimestamp; }
}
 
public DateTime LocalEndTimestamp
{
get { return localEndTimestamp; }
}
 
public DateTime RemoteEndTimestamp
{
get { return remoteEndTimestamp; }
}
 
public IPEndPoint LocalPoint
{
get { return localPoint; }
}
 
public IPEndPoint RemotePoint
{
get { return remotePoint; }
}
 
public LinkedList Messages
{
get { return messages; } // FIXME return read-only object
}
 
public SocketState LocalState
{
get { return localState; }
}
 
public SocketState RemoteState
{
get { return remoteState; }
}
 
private int CountBytes(TcpMessageDirection direction)
{
int count = 0;
 
foreach(TcpMessage message in messages)
{
if(message.Direction == direction)
count += message.Length;
}
return count;
}
 
public int SentBytes
{
get { return CountBytes(TcpMessageDirection.Local); }
}
 
public int ReceivedBytes
{
get { return CountBytes(TcpMessageDirection.Remote); }
}
 
internal TcpConnection(string id)
{
this.id = id;
this.httpParser = HttpParser.Parse(this);
}
 
internal void Continue(IPAddress resendHost, int resendPort, Socket localSocket)
{
SendLog(LogLevel.Important, "Client connected from " + ((IPEndPoint)localSocket.RemoteEndPoint).ToString());
SetLocalState(SocketState.Connected);
this.worker = new SocketWorker(resendHost, resendPort, localSocket, this);
}
 
public void Cancel()
{
worker.Cancel();
}
 
protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes)
{
return Append(direction, newBytes, newBytes.Length);
}
 
protected TcpMessage Append(TcpMessageDirection direction, byte[] newBytes, int length)
{
if(newBytes == null) return null;
 
TcpMessage message;
 
lock(this)
{
message = new TcpMessage();
message.Direction = direction;
messages.Add(message);
message.Append(newBytes, length);
}
 
httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
 
return message;
}
 
internal void AddHttpMessage(HttpMessage http)
{
lock(this)
{
https.Add(http);
TcpHttpEventArgs e = new TcpHttpEventArgs(http);
OnNewHttp(e);
}
}
 
 
public override string ToString() // FIXME delete the method
{
return id + " " + startTimestamp.ToString("HH:mm:ss.ffff");
}
 
 
protected void SetLocalPoint(IPEndPoint localPoint)
{
this.localPoint = localPoint;
OnUpdate(new TcpEventArgs());
}
 
protected void SetRemotePoint(IPEndPoint remotePoint)
{
this.remotePoint = remotePoint;
OnUpdate(new TcpEventArgs());
}
 
protected void SetLocalState(SocketState localState)
{
if(this.localState == SocketState.None && localState == SocketState.Connecting)
{
startTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.None && localState == SocketState.Connected)
{
startTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.None && localState == SocketState.Closed)
{
}
else if(this.localState == SocketState.Connecting && localState == SocketState.Connected)
{
}
else if(this.localState == SocketState.Connecting && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownSend)
{
}
else if(this.localState == SocketState.Connected && localState == SocketState.ShutdownReceived)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Connected && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownSend && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownSend && localState == SocketState.ShutdownReceived)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.ShutdownSend)
{
}
else if(this.localState == SocketState.ShutdownReceived && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else if(this.localState == SocketState.Closed && localState == SocketState.Closed)
{
if(localEndTimestamp == DateTime.MinValue) localEndTimestamp = DateTime.Now;
}
else
{
throw new Exception("Wrong local socket state change: from " + this.localState + " to " + localState);
}
this.localState = localState;
if(this.localState == SocketState.Closed) httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
}
 
protected void SetRemoteState(SocketState remoteState)
{
if(this.remoteState == SocketState.None && remoteState == SocketState.Connecting)
{
}
else if(this.remoteState == SocketState.None && remoteState == SocketState.Connected)
{
}
else if(this.remoteState == SocketState.None && remoteState == SocketState.Closed)
{
}
else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Connected)
{
}
else if(this.remoteState == SocketState.Connecting && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownSend)
{
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.ShutdownReceived)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Connected && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownSend && remoteState == SocketState.ShutdownReceived)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.ShutdownSend)
{
}
else if(this.remoteState == SocketState.ShutdownReceived && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else if(this.remoteState == SocketState.Closed && remoteState == SocketState.Closed)
{
if(remoteEndTimestamp == DateTime.MinValue) remoteEndTimestamp = DateTime.Now;
}
else
{
throw new Exception("Wrong remote socket state change: from " + this.remoteState + " to " + remoteState);
}
this.remoteState = remoteState;
if(this.remoteState == SocketState.Closed) httpParser.NewMessageArived();
OnUpdate(new TcpEventArgs());
}
 
public event TcpEventHandler Update;
 
protected virtual void OnUpdate(TcpEventArgs e)
{
if(Update != null)
{
Update(this, e);
}
}
 
public event TcpHttpEventHandler NewHttp;
 
protected virtual void OnNewHttp(TcpHttpEventArgs e)
{
if(NewHttp != null)
{
NewHttp(this, e);
}
}
 
public event TcpEventHandler Close;
 
protected virtual void OnClose(TcpEventArgs e)
{
if(Close != null)
{
Close(this, e);
}
}
 
public event TcpLogEventHandler Log;
 
protected virtual void OnLog(TcpLogEventArgs e)
{
if(Log != null)
{
Log(this, e);
}
}
 
protected virtual void SendLog(LogLevel level, string message)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, message, null);
OnLog(e);
}
 
protected virtual void SendLog(LogLevel level, Exception ex)
{
TcpLogEventArgs e = new TcpLogEventArgs(level, null, ex);
OnLog(e);
}
 
protected class SocketWorker
{
private enum SendCommandType
{
Send,
Shutdown,
Reset
}
 
private class SendCommand
{
public byte[] buffer = null;
public int length = 0;
public SendCommandType cmdType = SendCommandType.Send;
}
 
private static int BUF_SIZE = 2048;
 
private TcpConnection tcp;
private Socket localSocket;
private Socket remoteSocket;
private byte[] localDataBuffer;
private byte[] remoteDataBuffer;
private AsyncCallback receiveLocalMethod;
private AsyncCallback receiveRemoteMethod;
private Queue localSendQueue = new Queue();
private Queue remoteSendQueue = new Queue();
private AutoResetEvent localSendEvent = new AutoResetEvent(false);
private AutoResetEvent remoteSendEvent = new AutoResetEvent(false);
private bool localSocketSendShutdown = false;
private bool localSocketReceiveShutdown = false;
private bool remoteSocketSendShutdown = false;
private bool remoteSocketReceiveShutdown = false;
private ManualResetEvent remoteSocketEvent = new ManualResetEvent(false);
private Thread localSendThread;
private Thread remoteSendThread;
 
public SocketWorker(IPAddress resendHost, int resendPort, Socket localSocket, TcpConnection tcp)
{
try
{
tcp.SendLog(LogLevel.Debug, string.Format("Local socket: {0}:{1} <-> {2}:{3}",
((IPEndPoint)localSocket.LocalEndPoint).Address,
((IPEndPoint)localSocket.LocalEndPoint).Port,
((IPEndPoint)localSocket.RemoteEndPoint).Address,
((IPEndPoint)localSocket.RemoteEndPoint).Port));
 
this.localSocket = localSocket;
this.tcp = tcp;
receiveLocalMethod = new AsyncCallback(OnLocalReceived);
receiveRemoteMethod = new AsyncCallback(OnRemoteReceived);
 
tcp.SetLocalPoint((IPEndPoint)localSocket.RemoteEndPoint);
this.localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
 
localSendThread = new Thread(new ThreadStart(LocalSendProc));
localSendThread.Name = "SocketWorker.LocalSendProc";
localSendThread.Start();
 
ContinueLocalReceive();
 
if(resendHost == null)
{
remoteSocket = null;
}
else
{
tcp.SetRemoteState(SocketState.Connecting);
 
IPEndPoint point = new IPEndPoint(resendHost, resendPort);
remoteSocket = new Socket(point.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
remoteSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
remoteSocket.Connect(point);
tcp.SetRemoteState(SocketState.Connected);
tcp.SetRemotePoint((IPEndPoint)remoteSocket.RemoteEndPoint);
 
remoteSendThread = new Thread(new ThreadStart(RemoteSendProc));
remoteSendThread.Name = "SocketWorker.RemoteSendProc";
remoteSendThread.Start();
 
ContinueRemoteReceive();
remoteSocketEvent.Set(); // remote socket ready to send data
tcp.SendLog(LogLevel.Info, "Connected to server " + tcp.RemotePoint.ToString());
 
tcp.SendLog(LogLevel.Debug, string.Format("Remote socket: {0}:{1} <-> {2}:{3}",
((IPEndPoint)remoteSocket.LocalEndPoint).Address,
((IPEndPoint)remoteSocket.LocalEndPoint).Port,
((IPEndPoint)remoteSocket.RemoteEndPoint).Address,
((IPEndPoint)remoteSocket.RemoteEndPoint).Port));
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void ContinueLocalReceive()
{
try
{
localDataBuffer = new byte[BUF_SIZE];
localSocket.BeginReceive(localDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveLocalMethod, this);
}
catch(ObjectDisposedException ex) // the socket is closed
{
tcp.SendLog(LogLevel.Info, ex);
Cancel();
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void ContinueRemoteReceive()
{
try
{
remoteDataBuffer = new byte[BUF_SIZE];
remoteSocket.BeginReceive(remoteDataBuffer, 0, BUF_SIZE, SocketFlags.None, receiveRemoteMethod, this);
}
catch(ObjectDisposedException ex) // the socket is closed
{
tcp.SendLog(LogLevel.Info, ex);
Cancel();
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void CheckLocalSocket()
{
lock(localSocket)
{
try
{
if(localSocketReceiveShutdown && localSocketSendShutdown)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
 
private void CheckRemoteSocket()
{
lock(remoteSocket)
{
try
{
if(remoteSocketReceiveShutdown && remoteSocketSendShutdown)
{
if(remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
 
private void OnLocalReceived(IAsyncResult asyn)
{
try
{
int bytesReceived = 0;
bool reset = false;
 
try
{
bytesReceived = localSocket.EndReceive(asyn);
}
catch(ObjectDisposedException)
{
reset = true;
}
catch(SocketException ex)
{
if(ex.ErrorCode == 10054)
reset = true;
else
throw ex;
}
 
if(reset)
{
tcp.SendLog(LogLevel.Info, "Got reset from local end");
 
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Reset;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
}
else if(bytesReceived <= 0)
{
tcp.SendLog(LogLevel.Info, "Got showdown from local end");
 
localSocket.Shutdown(SocketShutdown.Receive);
tcp.SetLocalState(SocketState.ShutdownReceived);
localSocketReceiveShutdown = true;
CheckLocalSocket();
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Shutdown;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
}
else
{
tcp.SendLog(LogLevel.Debug, string.Format("Local received {0} bytes", bytesReceived));
 
SendCommand cmd = new SendCommand();
cmd.buffer = localDataBuffer;
cmd.length = bytesReceived;
lock(localSendQueue)
{
localSendQueue.Enqueue(cmd);
localSendEvent.Set();
}
ContinueLocalReceive();
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void LocalSendProc()
{
try
{
while(true)
{
SendCommand cmd;
 
if(localSendQueue.Count == 0)
{
localSendEvent.WaitOne();
}
 
lock(localSendQueue)
{
localSendEvent.Reset();
cmd = (SendCommand)localSendQueue.Dequeue();
}
 
if(cmd.cmdType == SendCommandType.Reset) // reset marker
{
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send reset to remote end"));
lock(remoteSocket)
{
if(!remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
 
break; // no more send allowed
}
else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
{
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to remote end"));
remoteSocket.Shutdown(SocketShutdown.Send);
tcp.SetRemoteState(SocketState.ShutdownSend);
remoteSocketSendShutdown = true;
CheckRemoteSocket();
 
break; // no more send allowed
}
else
{
// store received bytes
tcp.Append(TcpMessageDirection.Local, cmd.buffer, cmd.length);
 
// forward it
if(remoteSocket == null || !remoteSocket.Connected) remoteSocketEvent.WaitOne();
tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to remote end", cmd.length));
remoteSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
}
}
}
catch(ThreadAbortException)
{
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void OnRemoteReceived(IAsyncResult asyn)
{
try
{
int bytesReceived = 0;
bool reset = false;
 
try
{
bytesReceived = remoteSocket.EndReceive(asyn);
}
catch(ObjectDisposedException)
{
reset = true;
}
catch(SocketException ex)
{
if(ex.ErrorCode == 10054)
reset = true;
else
throw ex;
}
 
if(reset)
{
tcp.SendLog(LogLevel.Info, "Got reset from remote end");
 
lock(remoteSocket)
{
if(remoteSocket.Connected) remoteSocket.Close();
tcp.SetRemoteState(SocketState.Closed);
}
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Reset;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
}
else if(bytesReceived <= 0)
{
tcp.SendLog(LogLevel.Info, "Got showdown from remote end");
 
remoteSocket.Shutdown(SocketShutdown.Receive);
tcp.SetRemoteState(SocketState.ShutdownReceived);
remoteSocketReceiveShutdown = true;
CheckRemoteSocket();
 
SendCommand cmd = new SendCommand();
cmd.cmdType = SendCommandType.Shutdown;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
}
else
{
tcp.SendLog(LogLevel.Debug, string.Format("Remote received {0} bytes", bytesReceived));
 
SendCommand cmd = new SendCommand();
cmd.buffer = remoteDataBuffer;
cmd.length = bytesReceived;
lock(remoteSendQueue)
{
remoteSendQueue.Enqueue(cmd);
remoteSendEvent.Set();
}
ContinueRemoteReceive();
}
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
private void RemoteSendProc()
{
try
{
while(true)
{
SendCommand cmd;
 
if(remoteSendQueue.Count == 0)
{
remoteSendEvent.WaitOne();
}
 
lock(remoteSendQueue)
{
remoteSendEvent.Reset();
cmd = (SendCommand)remoteSendQueue.Dequeue();
}
 
if(cmd.cmdType == SendCommandType.Reset) // reset marker
{
tcp.SendLog(LogLevel.Debug, string.Format("Send reset to local end"));
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
tcp.SetLocalState(SocketState.Closed);
}
 
break; // no more send allowed
}
else if(cmd.cmdType == SendCommandType.Shutdown) // shutdown marker
{
tcp.SendLog(LogLevel.Debug, string.Format("Send shutdown to local end"));
localSocket.Shutdown(SocketShutdown.Send);
tcp.SetLocalState(SocketState.ShutdownSend);
localSocketSendShutdown = true;
CheckLocalSocket();
 
break; // no more send allowed
}
else
{
// store received bytes
tcp.Append(TcpMessageDirection.Remote, cmd.buffer, cmd.length);
 
// forward it
tcp.SendLog(LogLevel.Debug, string.Format("Send {0} bytes to local end", cmd.length));
localSocket.Send(cmd.buffer, cmd.length, SocketFlags.None);
}
}
}
catch(ThreadAbortException)
{
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
Cancel();
}
}
 
public void Cancel()
{
tcp.SendLog(LogLevel.Important, "Connection canceled");
 
try
{
if(localSendThread != null && localSendThread.IsAlive) localSendThread.Abort();
if(remoteSendThread != null && remoteSendThread.IsAlive) remoteSendThread.Abort();
 
// close sockets
try
{
if(localSocket != null)
{
lock(localSocket)
{
if(localSocket.Connected) localSocket.Close();
}
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
tcp.SetLocalState(SocketState.Closed);
 
try
{
if(remoteSocket != null)
{
lock(remoteSocket)
{
if(remoteSocket.Connected) remoteSocket.Close();
}
}
}
catch(Exception ex) // in any case we want to close the socket
{
tcp.SendLog(LogLevel.Warning, ex);
}
tcp.SetRemoteState(SocketState.Closed);
 
// return
tcp.OnClose(new TcpEventArgs());
}
catch(Exception ex)
{
tcp.SendLog(LogLevel.Warning, ex);
}
}
}
}
 
internal class HttpParser
{
private enum HttpCharType
{
None,
Control,
Digit,
UpAlpha,
LoAlpha,
NonChar,
Separator,
CrLf,
}
 
private static HttpCharType[] charTypes = null;
private static bool[] tokenChars = null;
private static char[] charValues = {
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\0'
};
 
private static void InitTables()
{
if(charTypes != null) return;
 
// main table
charTypes = new HttpCharType[256];
 
for(int i = 0; i < charTypes.Length; i++) charTypes[i] = HttpCharType.None;
 
for(int i = 0; i <= 31; i++) charTypes[i] = HttpCharType.Control;
charTypes[127] = HttpCharType.Control; // <del>
 
for(int i = 48; i <= 57; i++) charTypes[i] = HttpCharType.Digit;
 
for(int i = 65; i <= 90; i++) charTypes[i] = HttpCharType.UpAlpha;
for(int i = 97; i <= 122; i++) charTypes[i] = HttpCharType.LoAlpha;
 
for(int i = 128; i < charTypes.Length; i++) charTypes[i] = HttpCharType.NonChar;
 
charTypes[ 40] = HttpCharType.Separator; // (
charTypes[ 41] = HttpCharType.Separator; // )
charTypes[ 60] = HttpCharType.Separator; // <
charTypes[ 62] = HttpCharType.Separator; // >
charTypes[ 64] = HttpCharType.Separator; // @
charTypes[ 44] = HttpCharType.Separator; // ,
charTypes[ 59] = HttpCharType.Separator; // ;
charTypes[ 58] = HttpCharType.Separator; // :
charTypes[ 92] = HttpCharType.Separator; // \
charTypes[ 34] = HttpCharType.Separator; // "
charTypes[ 47] = HttpCharType.Separator; // /
charTypes[ 91] = HttpCharType.Separator; // [
charTypes[ 93] = HttpCharType.Separator; // ]
charTypes[ 63] = HttpCharType.Separator; // ?
charTypes[ 61] = HttpCharType.Separator; // =
charTypes[123] = HttpCharType.Separator; // {
charTypes[125] = HttpCharType.Separator; // }
charTypes[ 32] = HttpCharType.Separator; // <space>
charTypes[ 9] = HttpCharType.Separator; // <tab>
 
charTypes[ 13] = HttpCharType.CrLf; // <CR>
charTypes[ 10] = HttpCharType.CrLf; // <LF>
 
// token table
tokenChars = new bool[256];
for(int i = 0; i < tokenChars.Length; i++)
{
tokenChars[i] = !(charTypes[i] == HttpCharType.NonChar
|| charTypes[i] == HttpCharType.Control || charTypes[i] == HttpCharType.Separator
|| charTypes[i] == HttpCharType.CrLf);
}
}
 
private class ParsePosition
{
private TcpConnection messages;
private IEnumerator messagesEnum;
private TcpMessage tcp = null;
private int tcpPos;
private int tcpLen;
private bool tcpEnd = false;
private AutoResetEvent newMessageEvent;
private AutoResetEvent nextMessageEvent;
 
public ParsePosition(TcpConnection messages, AutoResetEvent nextMessageEvent)
{
this.messages = messages;
this.messagesEnum = messages.Messages.GetEnumerator();
this.newMessageEvent = new AutoResetEvent(false);
this.nextMessageEvent = nextMessageEvent;
}
 
public AutoResetEvent NewMessageEvent
{
get { return newMessageEvent; }
}
 
public bool IsEnd
{
get { return tcpEnd; }
}
 
public TcpMessage CurrentMessage
{
get { return tcp; }
}
 
private bool MoveNext()
{
for(bool moved = false; !moved; )
{
lock(messages)
{
newMessageEvent.Reset();
moved = messagesEnum.MoveNext();
}
 
if(moved) break;
 
if(!newMessageEvent.WaitOne())
throw new Exception("Cannot get next TCP message");
 
lock(messages)
{
if(messages.LocalState == SocketState.Closed && messages.RemoteState == SocketState.Closed)
return false;
 
moved = messagesEnum.MoveNext();
}
}
 
return true;
}
 
private void NextTcp()
{
if(tcpEnd) return;
 
TcpMessage newTcp = null;
 
do
{
if(!MoveNext())
{
tcpEnd = true;
break;
}
newTcp = (TcpMessage)messagesEnum.Current;
}
while(tcp != null && tcp.Direction != newTcp.Direction);
 
if(!tcpEnd)
{
tcp = newTcp;
tcpLen = tcp.Length;
tcpPos = 0;
if(nextMessageEvent != null) nextMessageEvent.Set();
}
}
 
public byte CurrentOctet()
{
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
}
 
public byte NextOctet()
{
tcpPos++;
 
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
}
 
public void SetDirection(TcpMessageDirection direction)
{
do
{
if(!MoveNext())
{
tcp = null;
tcpEnd = true;
break;
}
tcp = (TcpMessage)messagesEnum.Current;
}
while(tcp.Direction != direction);
 
if(!tcpEnd)
{
tcpLen = tcp.Length;
tcpPos = 0;
}
}
}
 
private TcpConnection messages;
private AutoResetEvent requestEvent = new AutoResetEvent(false); // new request found
private AutoResetEvent nextMessageEvent = new AutoResetEvent(false); // request goes to next TCP message
private LinkedList https = new LinkedList();
private ParsePosition requestPos;
private ParsePosition responsePos;
private AutoResetEvent newMessageEvent = new AutoResetEvent(false); // new TCP message available
private Thread runThread;
 
public static HttpParser Parse(TcpConnection messages)
{
HttpParser parser = new HttpParser(messages);
parser.runThread = new Thread(new ThreadStart(parser.Run));
parser.RunThread.Name = "HttpParser.Run";
parser.runThread.Start();
 
return parser;
}
 
public void NewMessageArived()
{
requestPos.NewMessageEvent.Set();
responsePos.NewMessageEvent.Set();
}
 
public Thread RunThread
{
get { return runThread; }
}
 
private HttpParser(TcpConnection messages)
{
this.messages = messages;
InitTables();
}
 
/// <summary>
/// Try to recognize the stored TCP packets as sequence of HTTP messages (request-response)
/// </summary>
private void Run()
{
Thread responseThread = null;
 
try
{
requestPos = new ParsePosition(messages, nextMessageEvent);
responsePos = new ParsePosition(messages, null);
 
responseThread = new Thread(new ThreadStart(MatchResponses));
responseThread.Name = "HttpParser.MatchResponses";
responseThread.Start();
 
// find requests
while(!requestPos.IsEnd)
{
HttpMessage http = new HttpMessage();
lock(https)
{
https.Add(http);
requestEvent.Set(); // new request available
}
 
messages.AddHttpMessage(http);
SkipEmptyLines(requestPos);
http.RequestStartTimestamp = requestPos.CurrentMessage.Timestamp;
 
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.ResponseStartTimestamp = responsePos.CurrentMessage.Timestamp;
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 DateTime requestStartTimestamp = DateTime.MinValue;
 
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;
private DateTime responseStartTimestamp = DateTime.MinValue;
 
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 DateTime RequestStartTimestamp
{
get { return requestStartTimestamp; }
set { requestStartTimestamp = 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 DateTime ResponseStartTimestamp
{
get { return responseStartTimestamp; }
set { responseStartTimestamp = 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/tags/0001-first-public/TCPproxy.sln
0,0 → 1,20

Microsoft Visual Studio Solution File, Format Version 9.00
# Visual C# Express 2005
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TCPproxy", "TCPproxy.csproj", "{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
/TCPproxy/tags/0001-first-public/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="*.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/tags/0001-first-public/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/tags/0001-first-public/TCPproxy.csproj
0,0 → 1,75
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{5051426D-C5F7-4C1D-90F2-BD73A255FBD1}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TCPproxy</RootNamespace>
<AssemblyName>TCPproxy</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="LinkedList.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Network.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Compile Include="ViewControl.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
/TCPproxy/tags/0001-first-public/Properties/Resources.resx
0,0 → 1,117
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
 
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
/TCPproxy/tags/0001-first-public/Properties/Resources.Designer.cs
0,0 → 1,71
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.42
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace TCPproxy.Properties
{
 
 
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
 
private static global::System.Resources.ResourceManager resourceMan;
 
private static global::System.Globalization.CultureInfo resourceCulture;
 
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
 
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TCPproxy.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
 
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}
/TCPproxy/tags/0001-first-public/Properties/AssemblyInfo.cs
0,0 → 1,33
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TCPproxy")]
[assembly: AssemblyDescription("A proxy for any TCP connections which saves and shows all catched information, special processing for HTTP protocol and XML data in it.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Anatoli Klassen")]
[assembly: AssemblyProduct("TCPproxy")]
[assembly: AssemblyCopyright("Copyleft 2005 Anatoli Klassen. Public domain.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
 
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
 
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e1b78820-40c9-41df-b27c-6edbd61b9131")]
 
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.0")]
/TCPproxy/tags/0001-first-public/Properties/Settings.settings
0,0 → 1,7
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
/TCPproxy/tags/0001-first-public/Properties/Settings.Designer.cs
0,0 → 1,30
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.42
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace TCPproxy.Properties
{
 
 
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "8.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
 
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
 
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}
/TCPproxy/tags/0001-first-public/ViewControl.cs
0,0 → 1,1709
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 + 1 >= 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)
? (int)Math.Ceiling((double)(oldLineLen - m.endPos) / NEW_LINE_LENGTH) * NEW_LINE_LENGTH
: oldLineLen - 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);
 
if(m.endLine == linesCount - 1) lastLineLen = oldLineLen - m.endPos;
linesCount++;
}
 
// 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/tags/0001-first-public/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/tags/0001-first-public/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/tags/0001-first-public/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/tags/0001-first-public/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
/TCPproxy/tags/0001-first-public
Property changes:
Added: svn:ignore
+bin
+obj
+TCPproxy.suo
+combine