Saturday, March 21, 2015

C# Chat Client v1

In today's post I want to connect all the previous posts to present a first version of my chat client. The actual client is written in C# and looks as follows:


















There is a central server on which PHP scripts lie. These are used for the user management (registration / login) and the message management. All files are saved into a MySQL database on the server. So there is one table for the users and one for the messages. When sending a message the corresponding PHP script is called by the client, which then writes the messages into the database. The client of the receiver periodically calls a script for checking for messages, this then forwards the possible messages to him.

Basically there are 2 classes: The class Form1 for the representation of the graphical interface, and the class SimpleChatClient, which represents the core of the chat client and does the communication with the server.
The design of the graphical interface is the same as in the Advanced Facebook Chat, I do not want to discuss this code here but instead focus on the core of the chat.

In the class SimpleChatClient the function HTTPPost() is of great importance. It creates HTTP POST requests with the HttpWebRequest class and also uses cookies, so we can use PHP sessions. We pass the target URL and parameters to the function. The functions Register(), Login(), Send() and Receive() do what their names say and call HTTPPost() with the right arguments. The functions are very easy, they bascially just forward the request to the corresponding PHP scripts and the (possible) outputs to the client. The functions Send() and Receive() should only be available to authorized users, therefor the corresponding PHP scripts check wether the current user already started a session by loggin in. The global variable Cookies saves the cookie for the HTTP request, which is created when loggin in.

Let's come to the PHP code on the server. All PHP scripts have at the beginning the line include("connect.php");, which includes the script connect.php. This simply prepares the MySQL connection via $conn = new mysqli(server, username, password, database);. I will not publish the data here, because I want that the client can indeed be used (safely).
Of course everybody can create his own server and for that just has to change the script connect.php (and of course also the URLs in the code), in the database the following tables are needed: Users, with the attributes username and password, and Messages with Sender, Recipient and Message.
For simplicity here I again first want to use PHP code using the class mysql, and then in the next post introduce a secure variant with mysqli.
The files register.php and login.php are the ones presented in the post about a login system in PHP:

register.php (http://bloggeroliver.bplaced.net/Chat/Insecure/register.php):

<?php
include("connect.php");

$username = $_POST["username"];
$password = $_POST["password"];
$hashedpw = md5($password);

$checkuser = mysql_query("SELECT username FROM Users WHERE username = '$username'");
$num_rows = mysql_num_rows($checkuser);

if ($num_rows > 0) {
     echo "Existing";
}
else {
     mysql_query("INSERT INTO Users (username, password) VALUES ('$username', '$hashedpw');");
     echo "Success";
}
?>

login.php (http://bloggeroliver.bplaced.net/Chat/Insecure/login.php):

<?php
session_start();

include("connect.php");

$username = $_POST["username"];
$password = $_POST["password"];
$hashedpw = md5($password);

$result = mysql_query("SELECT username, password FROM Users WHERE username = '$username' LIMIT 1");
$row = mysql_fetch_object($result);
    
if($row->password == $hashedpw) {
    $_SESSION["username"] = $username;
    echo "LoginGood";
}
else {
    echo "LoginBad";
}
?>

In the file send.php (http://bloggeroliver.bplaced.net/Chat/Insecure/send.php) first for a valid session is checked, and if this exists the submitted message is written to the database, the logged in user is entered as sender:

<?php
session_start();

include("connect.php");

$Recipient = $_POST["Recipient"];
$Message = $_POST["Message"];
$Sender = $_SESSION['username'];

if(!isset($_SESSION['username'])) {
     echo "Login first.";
     exit;
}

mysql_query("INSERT INTO Messages () VALUES ('$Sender', '$Recipient', '$Message');");
?>

In receive.php (http://bloggeroliver.bplaced.net/Chat/Insecure/receive.php) again for a valid session is checked, then all messages with the current user as recipient are read from the database, returned and eventually deleted:

<?php

session_start();

include("connect.php");

if(!isset($_SESSION['username']))
   {
   echo "Bitte erst login";
   exit;
   }
    
$Recipient = $_SESSION['username'];

$eintrag = "SELECT Sender, Message FROM Messages WHERE Recipient = '$Recipient'";
$eintragen = mysql_query($eintrag);
while($row = mysql_fetch_object($eintragen))
   {
          echo "$row->Sender<br />";
          echo "$row->Message<br />";
   }
$delete  = "DELETE FROM Messages WHERE Recipient = '$Recipient'";
$eintragen2 = mysql_query($delete);
?>

Now the code of the C# client:

Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net;
using System.IO;

namespace SimpleChatClient
{
    public class SimpleChatClient
    {
        public class User
        {
            public string Username;

            public User(string username)
            {
                Username = username;
            }
        }

        public class Message
        {
            public string Sender;
            public string Msg;

            public Message(string sender, string message)
            {
                Sender = sender;
                Msg = message;
            }
        }

        User CurrentUser = null;
        CookieContainer Cookie = null;
        string ServerUrl = "http://bloggeroliver.bplaced.net/Chat/Insecure/";

        public string GetUsername()
        {
            return CurrentUser.Username;
        }

        private string HTTPPost(string url, string postparams)
        {
            string responseString = "";

            try
            {
                // performs the desired http post request for the url and parameters
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.CookieContainer = Cookie; // explicitely use the cookiecontainer to save the session

                string postData = postparams;
                byte[] data = Encoding.UTF8.GetBytes(postData);

                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
                request.ContentLength = data.Length;

                using (var stream = request.GetRequestStream())
                {
                    stream.Write(data, 0, data.Length);
                }

                var response = (HttpWebResponse)request.GetResponse();

                responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

                return responseString;
            }
            catch
            {
                // MessageBox.Show("error" + url + postparams + responseString);
                return null;
            }
        }

        public string Register(string username, string password)
        {
            // register a new user
            return HTTPPost(ServerUrl + "register.php", "username=" + username + "&password=" + password);
        }

        public bool Login(string username, string password)
        {
            // login
            Cookie = new CookieContainer();
            string Login = HTTPPost(ServerUrl + "login.php", "username=" + username + "&password=" + password);
            if (Login == "LoginGood")
            {
                CurrentUser = new User(username);
                return true;
            }
            else
            {
                Cookie = null;
                return false;
            }
        }

        public void Logout()
        {
            // logout
            CurrentUser = null;
            Cookie = null;
        }

        public string Send(string recipient, string message)
        {
            // send
            return (HTTPPost(ServerUrl + "send.php", "Recipient=" + recipient + "&Message=" + message));
        }

        public List<Message> Receive()
        {
            // receive messages
            if (CurrentUser == null)
                return new List<Message>();
            string Messages = HTTPPost(ServerUrl + "receive.php", "");
            if (Messages == null)
                return new List<Message>();

            // message format is: sender<br />message<br />, split regarding to this in single messages
            string[] Splits = Messages.Split(new string[] { "<br />" }, StringSplitOptions.None);
            List<Message> Received = new List<Message>();
            for (int i = 0; i < Splits.Length - 1; i += 2)
            {
                Received.Add(new Message(Splits[i], Splits[i + 1]));
            }
            return Received;
        }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        SimpleChatClient Client;
        bool LoggedIn = false;
        Dictionary<string, Chat> Chats = new Dictionary<string, Chat>(); // stores all active chats

        private void button1_Click(object sender, EventArgs e)
        {
            if (!LoggedIn)
            {
                Client = new SimpleChatClient();

                bool Login = (Client.Login(textBox1.Text, textBox2.Text));
                if (Login)
                {
                    // Login successful, enable chatting etc.
                    textBox3.Enabled = true;
                    button3.Enabled = true;
                    this.Height = 570;
                    tabControl1.Visible = true;
                    LoggedIn = true;
                    button1.Text = "Logout";
                    button2.Enabled = false;
                }
                else
                    MessageBox.Show("Login failed.");
            }
            else
            {
                // logout, disable chatting etc.
                Client = new SimpleChatClient();
                Chats = new Dictionary<string, Chat>();
                tabControl1.TabPages.Clear();
                textBox1.Text = "";
                textBox2.Text = "";
                button2.Enabled = true;
                textBox3.Enabled = false;
                button3.Enabled = false;
                this.Height = 176;
                tabControl1.Visible = false;
                LoggedIn = false;
                button1.Text = "Login";
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Client = new SimpleChatClient();
            string Register = Client.Register(textBox1.Text, textBox2.Text);
            if (Register == "Success")
            {
                MessageBox.Show("Account created.");
            }
            if (Register == "Existing")
            {
                MessageBox.Show("Username already in use.");
            }
        }

        private void button3_Click_1(object sender, EventArgs e)
        {
            ProvideChat(textBox3.Text);
        }

        private void ProvideChat(string name)
        {
            Chat Dummy;
            bool ChatExisting = Chats.TryGetValue(name, out Dummy);
            if (ChatExisting)
            {   // if chat already exists, make its tabpage active
                tabControl1.SelectedTab = Dummy.ChatPage;
                return;
            }
            else
            {
                // create new tabpage for the conversation
                TabPage NewPage = new TabPage(name);

                TextBox ChatWindow = new TextBox();
                ChatWindow.Left = 10;
                ChatWindow.Top = 10;
                ChatWindow.Width = 532;
                ChatWindow.Height = 180;
                ChatWindow.Multiline = true;
                ChatWindow.ScrollBars = ScrollBars.Vertical;
                ChatWindow.ReadOnly = false;
                NewPage.Controls.Add(ChatWindow);

                TextBox SendBox = new TextBox();
                SendBox.Left = 10;
                SendBox.Top = 200;
                SendBox.Width = 450;
                NewPage.Controls.Add(SendBox);
                SendBox.Name = "snd" + name;
                SendBox.Click += new EventHandler(SendBox_Click);
                SendBox.TextChanged += new EventHandler(SendBox_TextChanged);

                Button SendButton = new Button();
                SendButton.Left = 470;
                SendButton.Top = 200;
                SendButton.Text = "Send";
                SendButton.Name = "btn" + name;
                SendButton.Click += new EventHandler(SendButton_Click);
                NewPage.Controls.Add(SendButton);

                Chat NewChat = new Chat();
                NewChat.ChatWindow = ChatWindow;
                NewChat.SendBox = SendBox;
                NewChat.ChatPage = NewPage;
                NewChat.Partner = name;
                NewChat.SendButton = SendButton;

                NewPage.Name = "tpg" + name;
                tabControl1.SelectedIndexChanged += new EventHandler(tabControl1_SelectedIndexChanged);

                Chats.Add(name, NewChat);

                this.AcceptButton = NewChat.SendButton;

                if (tabControl1.InvokeRequired)
                {
                    tabControl1.Invoke(new Action(() =>
                    {
                        tabControl1.TabPages.Add(NewPage);
                        tabControl1.SelectedTab = NewPage;
                    }));
                }
                else
                {
                    tabControl1.TabPages.Add(NewPage);
                    tabControl1.SelectedTab = NewPage;
                }

                this.ActiveControl = NewChat.SendBox;
            }
        }

        private void tabControl1_SelectedIndexChanged(Object sender, EventArgs e)
        {
            // change tabpages / chats
            if (((TabControl)sender).SelectedIndex != -1)
            {
                string Receiver = ((TabControl)sender).TabPages[((TabControl)sender).SelectedIndex].Name.ToString().Substring(3, ((TabControl)sender).TabPages[((TabControl)sender).SelectedIndex].Name.ToString().Length - 3);
                this.AcceptButton = Chats[Receiver].SendButton;
                this.ActiveControl = Chats[Receiver].SendBox;
            }
        }

        private void SendBox_Click(Object sender, EventArgs e)
        {
            // click in textbox for sending
            string Receiver = ((TextBox)sender).Name.Substring(3, ((TextBox)sender).Name.Length - 3);
            Chat CurrentChat = Chats[Receiver];
            CurrentChat.NewMessage = false;
            CurrentChat.ChatPage.Text = CurrentChat.Partner;
        }

        private void SendBox_TextChanged(Object sender, EventArgs e)
        {
            string Receiver = ((TextBox)sender).Name.Substring(3, ((TextBox)sender).Name.Length - 3);
            Chat CurrentChat = Chats[Receiver];
            CurrentChat.NewMessage = false;
            CurrentChat.ChatPage.Text = CurrentChat.Partner;
        }

        private void SendButton_Click(Object sender, EventArgs e)
        {
            // send message
            string Receiver = ((Button)sender).Name.Substring(3, ((Button)sender).Name.Length - 3);
            Chat CurrentChat = Chats[Receiver];
            Client.Send(Receiver, CurrentChat.SendBox.Text);
            CurrentChat.ChatWindow.Text += Client.GetUsername() + ": " + CurrentChat.SendBox.Text + Environment.NewLine;
            CurrentChat.SendBox.Text = "";
            Chats[Receiver].ChatWindow.SelectionStart = Chats[Receiver].ChatWindow.TextLength;
            Chats[Receiver].ChatWindow.ScrollToCaret();
        }

        private void IncomingMessage(string NameSender, string message)
        {
            // rceive an incoming message and display it in the correct chat window
            ProvideChat(NameSender);

            Chats[NameSender].NewMessage = true;
            Chats[NameSender].ChatWindow.Invoke(new Action(() =>
            {
                Chats[NameSender].ChatWindow.Text += NameSender + ": " + message + Environment.NewLine;
                Chats[NameSender].ChatWindow.SelectionStart = Chats[NameSender].ChatWindow.TextLength;
                Chats[NameSender].ChatWindow.ScrollToCaret();
            }));
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            // periodically poll messages from server
            if (Client != null)
            {
                List<SimpleChatClient.Message> Messages = Client.Receive();
                foreach (SimpleChatClient.Message m in Messages)
                {
                    IncomingMessage(m.Sender, m.Msg);
                }
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Height = 176;
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            // timer used for the blinking effect for new messages
            foreach (Chat c in Chats.Values)
            {
                if (c.NewMessage)
                {
                    string BlankName = "";
                    BlankName = BlankName.PadLeft(c.Partner.Length, ' ');
                    if (c.ChatPage.Text == BlankName)
                        c.ChatPage.Text = c.Partner;
                    else
                        c.ChatPage.Text = BlankName;
                }
            }
        }

        private void button4_Click(object sender, EventArgs e)
        {
            if (tabControl1.SelectedIndex != -1)
            {
                string Current = tabControl1.TabPages[tabControl1.SelectedIndex].Name.ToString().Substring(3, tabControl1.TabPages[tabControl1.SelectedIndex].Name.ToString().Length - 3);
                tabControl1.TabPages.RemoveAt(tabControl1.SelectedIndex);
                Chats.Remove(Current);
            }
        }
    }

    public class Chat
    {
        public TextBox ChatWindow;
        public TextBox SendBox;
        public bool NewMessage = false;
        public TabPage ChatPage;
        public string Partner;
        public Button SendButton;
    }
  
}

Form1.Designer.cs:

namespace SimpleChatClient
{
    partial class Form1
    {
        /// <summary>
       /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
       /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #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.button2 = new System.Windows.Forms.Button();
            this.button1 = new System.Windows.Forms.Button();
            this.label2 = new System.Windows.Forms.Label();
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.tabControl1 = new System.Windows.Forms.TabControl();
            this.button3 = new System.Windows.Forms.Button();
            this.textBox3 = new System.Windows.Forms.TextBox();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.timer2 = new System.Windows.Forms.Timer(this.components);
            this.button4 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // button2
            //
            this.button2.Location = new System.Drawing.Point(125, 86);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(80, 25);
            this.button2.TabIndex = 11;
            this.button2.Text = "Register";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(25, 86);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(81, 25);
            this.button1.TabIndex = 10;
            this.button1.Text = "Login";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            //
            // label2
            //
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(271, 49);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(53, 13);
            this.label2.TabIndex = 9;
            this.label2.Text = "Password";
            //
            // textBox2
            //
            this.textBox2.Location = new System.Drawing.Point(25, 49);
            this.textBox2.Name = "textBox2";
            this.textBox2.PasswordChar = '*';
            this.textBox2.Size = new System.Drawing.Size(219, 20);
            this.textBox2.TabIndex = 8;
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(271, 23);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(55, 13);
            this.label1.TabIndex = 7;
            this.label1.Text = "Username";
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(25, 23);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(219, 20);
            this.textBox1.TabIndex = 6;
            //
            // tabControl1
            //
            this.tabControl1.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.tabControl1.Location = new System.Drawing.Point(0, 199);
            this.tabControl1.Name = "tabControl1";
            this.tabControl1.SelectedIndex = 0;
            this.tabControl1.Size = new System.Drawing.Size(804, 329);
            this.tabControl1.TabIndex = 12;
            this.tabControl1.Visible = false;
            //
            // button3
            //
            this.button3.Enabled = false;
            this.button3.Location = new System.Drawing.Point(274, 150);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(77, 32);
            this.button3.TabIndex = 13;
            this.button3.Text = "Start Chat";
            this.button3.UseVisualStyleBackColor = true;
            this.button3.Click += new System.EventHandler(this.button3_Click_1);
            //
            // textBox3
            //
            this.textBox3.Enabled = false;
            this.textBox3.Location = new System.Drawing.Point(25, 157);
            this.textBox3.Name = "textBox3";
            this.textBox3.Size = new System.Drawing.Size(219, 20);
            this.textBox3.TabIndex = 14;
            //
            // timer1
            //
            this.timer1.Enabled = true;
            this.timer1.Interval = 1000;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            //
            // timer2
            //
            this.timer2.Enabled = true;
            this.timer2.Interval = 1000;
            this.timer2.Tick += new System.EventHandler(this.timer2_Tick);
            //
            // button4
            //
            this.button4.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button4.Location = new System.Drawing.Point(751, 157);
            this.button4.Name = "button4";
            this.button4.Size = new System.Drawing.Size(41, 26);
            this.button4.TabIndex = 15;
            this.button4.Text = "X";
            this.button4.UseVisualStyleBackColor = true;
            this.button4.Click += new System.EventHandler(this.button4_Click);
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(804, 528);
            this.Controls.Add(this.button4);
            this.Controls.Add(this.textBox3);
            this.Controls.Add(this.button3);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.tabControl1);
            this.Name = "Form1";
            this.Text = "SimpleChat";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.TextBox textBox2;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.TabControl tabControl1;
        private System.Windows.Forms.Button button3;
        private System.Windows.Forms.TextBox textBox3;
        private System.Windows.Forms.Timer timer1;
        private System.Windows.Forms.Timer timer2;
        private System.Windows.Forms.Button button4;
    }
}

The complete project can be downloaded herehere you find the link to the install files (executing setup.exe will install the program).
I am also called bloggeroliver (casesensitive) in the chat and am looking forward to your messages!

In the next post I show, as mentioned, how to prevent MySQL Injections. Further I will also describe there other security issues of this chat and show its solution.

No comments:

Post a Comment