Connection.cs :  » Chat-Servers » Thresher » Sharkbite » Irc » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Chat Servers » Thresher 
Thresher » Sharkbite » Irc » Connection.cs
/*
 * Thresher IRC client library
 * Copyright (C) 2002 Aaron Hunter <thresher@sharkbite.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * 
 * See the gpl.txt file located in the top-level-directory of
 * the archive of this library for complete text of license.
*/

using System.Runtime.InteropServices;
using System;
using System.Collections;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.IO;
using System.Globalization;
using System.Diagnostics;
using System.Text.RegularExpressions;

#if SSL
using Org.Mentalis.Security.Ssl;
#endif

[assembly:CLSCompliant(true)]
[assembly:ComVisible(false)]
namespace Sharkbite.Irc{
  /// <summary>
  /// This class manages the connection to the IRC server and provides
  /// access to all the objects needed to send and receive messages.
  /// </summary>
  public sealed class Connection 
  {
    /// <summary>
    /// Receive all the messages, unparsed, sent by the IRC server. This is not
    /// normally needed but provided for those who are interested.
    /// </summary>
    public event RawMessageReceivedEventHandler OnRawMessageReceived;
    /// <summary>
    /// Receive all the raw messages sent to the IRC from this connection
    /// </summary>
    public event RawMessageSentEventHandler OnRawMessageSent;


    #if SSL
    private SecureTcpClient client;
    #else
      private TcpClient client;
    #endif
    
    private readonly Regex propertiesRegex;
    private Listener listener;
    private Sender sender;
    private CtcpListener ctcpListener;
    private CtcpSender ctcpSender;
    private CtcpResponder ctcpResponder;
    private bool ctcpEnabled;
    private bool dccEnabled;
    private Thread socketListenThread;
    private StreamReader reader;
    private DateTime timeLastSent;
    //Connected and registered with IRC server
    private bool registered;
    //TCP/IP connection established with IRC server
    private bool connected;
    private bool handleNickFailure;
    private ArrayList parsers;
    private ServerProperties properties;
    private Encoding encoding;

    internal StreamWriter writer; //Access is internal for testing
    internal ConnectionArgs connectionArgs;

    /// <summary>
    /// Used for internal test purposes only.
    /// </summary>
    internal Connection( ConnectionArgs args ) 
    {
      connectionArgs = args;
      sender = new Sender( this );
      listener = new Listener();
      timeLastSent = DateTime.Now;
      EnableCtcp = true;
      EnableDcc = true;
      TextEncoding = Encoding.Default;
    }
    /// <summary>
    /// Prepare a connection to an IRC server but do not open it. This sets the text Encoding to Default.
    /// </summary>
    /// <param name="args">The set of information need to connect to an IRC server</param>
    /// <param name="enableCtcp">True if this Connection should support CTCP.</param>
    /// <param name="enableDcc">True if this Connection should support DCC.</param>
    public Connection( ConnectionArgs args, bool enableCtcp, bool enableDcc ) 
    {
      propertiesRegex = new Regex("([A-Z]+)=([^\\s]+)", RegexOptions.Compiled | RegexOptions.Singleline);
      registered = false;
      connected = false;
      handleNickFailure = true;
      connectionArgs = args;
      parsers = new ArrayList();
      sender = new Sender( this );
      listener = new Listener( );
      RegisterDelegates();
      timeLastSent = DateTime.Now;
      EnableCtcp = enableCtcp;
      EnableDcc = enableDcc;
      TextEncoding = Encoding.Default;
    }

  
  /// <summary>
  /// Prepare a connection to an IRC server but do not open it.
  /// </summary>
  /// <param name="args">The set of information need to connect to an IRC server</param>
  /// <param name="enableCtcp">True if this Connection should support CTCP.</param>
  /// <param name="enableDcc">True if this Connection should support DCC.</param>
  /// <param name="textEncoding">The text encoding for the incoming stream.</param>
  public Connection( Encoding textEncoding, ConnectionArgs args, bool enableCtcp, bool enableDcc ) : this( args,enableCtcp, enableDcc)
  {
    TextEncoding = textEncoding;
  }

    
  /// <summary>
  /// Sets the text encoding used by the read and write streams.
  /// Must be set before Connect() is called and should not be changed
  /// while the connection is processing messages.
  /// </summary>
  /// <value>An Encoding constant.</value>
  public Encoding TextEncoding 
{
  get 
{
  return encoding;
}
  set 
{
  encoding = value;
}
}
    
  /// <summary>
  /// A read-only property indicating whether the connection 
  /// has been opened with the IRC server and the 
  /// client has been successfully registered.
  /// </summary>
  /// <value>True if the client is connected and registered.</value>
  public bool Registered 
{
  get
{
  return registered;
}
}
  /// <summary>
  /// A read-only property indicating whether a connection 
  /// has been opened with the IRC server (but not whether 
  /// registration has succeeded).
  /// </summary>
  /// <value>True if the client is connected.</value>
  public bool Connected 
{
  get
{
  return connected;
}
}
  /// <summary>
  /// By default the connection itself will handle the case
  /// where, while attempting to register the client's nick
  /// is already in use. It does this by simply appending
  /// 2 random numbers to the end of the nick. 
  /// </summary>
  /// <remarks>
  /// The NickError event is shows that the nick collision has happened
  /// and it is fixed by calling Sender's Register() method passing
  /// in the replacement nickname.
  /// </remarks>
  /// <value>True if the connection should handle this case and
  /// false if the client will handle it itself.</value>
  public bool HandleNickTaken 
{
  get 
{
  return handleNickFailure;
}
  set 
{
  handleNickFailure = value;
}
}
  /// <summary>
  /// A user friendly name of this Connection in the form 'nick@host'
  /// </summary>
  /// <value>Read only string</value>
  public string Name
{
  get
{
  return connectionArgs.Nick + "@" + connectionArgs.Hostname;
}
}
  /// <summary>
  /// Whether Ctcp commands should be processed and if
  /// Ctcp events will be raised.
  /// </summary>
  /// <value>True will enable the CTCP sender and listener and
  /// false will cause their property calls to return null.</value>
  public bool EnableCtcp
{
  get
{
  return ctcpEnabled;
}
  set 
{
  if( value && !ctcpEnabled ) 
{
  ctcpListener = new CtcpListener( this );
  ctcpSender = new CtcpSender( this );
}
  else if(!value ) 
{
  ctcpListener = null;
  ctcpSender = null;
}
  ctcpEnabled = value;
}
}
  /// <summary>
  /// Whether DCC requests should be processed or ignored 
  /// by this Connection. Since the DccListener is a singleton and
  /// shared by all Connections, listeners to DccListener events should
  /// be manually removed when no longer needed.
  /// </summary>
  /// <value>True to process DCC requests.</value>
  public bool EnableDcc
{
  get
{
  return dccEnabled;
}
  set 
{
  dccEnabled = value;
}
}
  /// <summary>
  /// Sets an automatic responder to Ctcp queries.
  /// </summary>
  /// <value>Once this is set it can be removed by setting it to null.</value>
  public CtcpResponder CtcpResponder
{
  get
{
  return ctcpResponder;
}
  set 
{
  if( value == null && ctcpResponder != null ) 
{
  ctcpResponder.Disable();
}
  ctcpResponder = value;
}
}
  /// <summary>
  /// The amount of time that has passed since the client
  /// sent a command to the IRC server.
  /// </summary>
  /// <value>Read only TimeSpan</value>
  public TimeSpan IdleTime
{
  get
{
  return DateTime.Now - timeLastSent;
}
}
  /// <summary>
  /// The object used to send commands to the IRC server.
  /// </summary>
  /// <value>Read-only Sender.</value>
  public Sender Sender
{
  get
{
  return sender;
}
}
  /// <summary>
  /// The object that parses messages and notifies the appropriate delegate.
  /// </summary>
  /// <value>Read only Listener.</value>
  public Listener Listener
{
  get
{
  return listener;
}
}
  /// <summary>
  /// The object used to send CTCP commands to the IRC server.
  /// </summary>
  /// <value>Read only CtcpSender. Null if CtcpEnabled is false.</value>
  public CtcpSender CtcpSender
{
  get
{
  return ctcpSender;
}
}
  /// <summary>
  /// The object that parses CTCP messages and notifies the appropriate delegate.
  /// </summary>
  /// <value>Read only CtcpListener. Null if CtcpEnabled is false.</value>
  public CtcpListener CtcpListener
{
  get
{
  if( ctcpEnabled ) 
{
  return ctcpListener;
}
  else 
{
  return null;
}
}
}
  /// <summary>
  /// The collection of data used to establish this connection.
  /// </summary>
  /// <value>Read only ConnectionArgs.</value>
  public ConnectionArgs ConnectionData
{
  get
{
  return connectionArgs;
}
}
    
  /// <summary>
  /// A read-only collection of string key/value pairs
  /// representing IRC server proprties.
  /// </summary>
  /// <value>This connection's ServerProperties obejct or null if it 
  /// has not been created.</value>
  public ServerProperties ServerProperties 
{
  get 
{
  return properties;
}
}

  private bool CustomParse( string line ) 
{
  foreach( IParser parser in parsers ) 
{
  if( parser.CanParse( line ) ) 
{
  parser.Parse( line );
  return true;
}
}
  return false;
}
  /// <summary>
  /// Respond to IRC keep-alives.
  /// </summary>
  /// <param name="message">The message that should be echoed back</param>
  private void KeepAlive(string message) 
{
  sender.Pong( message );
}
  /// <summary>
  /// Update the ConnectionArgs object when the user
  /// changes his nick.
  /// </summary>
  /// <param name="user">Who changed their nick</param>
  /// <param name="newNick">The new nick name</param>
  private void MyNickChanged(UserInfo user, string newNick) 
{
  if ( connectionArgs.Nick == user.Nick ) 
{
  connectionArgs.Nick = newNick;
}
}
  private void OnRegistered() 
{
  registered = true;
  listener.OnRegistered -= new RegisteredEventHandler( OnRegistered );
}
  /// <summary>
  /// 
  /// </summary>
  /// <param name="badNick"></param>
  /// <param name="reason"></param>
  private void OnNickError( string badNick, string reason ) 
{
  //If this is our initial connection attempt
  if( !registered && handleNickFailure ) 
{
  NameGenerator generator = new NameGenerator();
  string nick;
  do 
{
  nick = generator.MakeName();
}
  while( !Rfc2812Util.IsValidNick( nick) || nick.Length == 1 ); 
  //Try to reconnect
  Sender.Register( nick );
}
}
  /// <summary>
  /// Listen for the 005 info messages sent during registration so that the maximum lengths
  /// of certain items (Nick, Away, Topic) can be determined dynamically.
  /// </summary>
  /// <param name="code">Reply code enum</param>
  /// <param name="info">An info line</param>
  private void OnReply( ReplyCode code, string info) 
{
  if( code == ReplyCode.RPL_BOUNCE ) //Code 005
{
  //Lazy instantiation
  if( properties == null ) 
{
  properties = new ServerProperties();
}
  //Populate properties from name/value matches
  MatchCollection matches = propertiesRegex.Matches( info );
  if( matches.Count > 0 ) 
{
  foreach( Match match in matches ) 
{
  properties.SetProperty(match.Groups[1].ToString(), match.Groups[2].ToString() );
}
        }
        //Extract ones we are interested in
        ExtractProperties();
      }
    }
    private void ExtractProperties() 
    {
      //For the moment the only one we care about is NickLen
      //In fact we don't cae about any but keep here as an example
      /*
      if( properties.ContainsKey("NICKLEN") ) 
      {
        try 
        {
          maxNickLength = int.Parse( properties[ "NICKLEN" ] );
        }
        catch( Exception e )
        {
        }
      }
      */
    }
    private void RegisterDelegates() 
    {
      listener.OnPing += new PingEventHandler( KeepAlive );
      listener.OnNick += new NickEventHandler( MyNickChanged );
      listener.OnNickError += new NickErrorEventHandler( OnNickError );
      listener.OnReply += new ReplyEventHandler( OnReply );
      listener.OnRegistered += new RegisteredEventHandler( OnRegistered );
    }

  #if SSL
    private void ConnectClient( SecureProtocol protocol )   
    {
      lock ( this ) 
      {
        if( connected ) 
        {
          throw new Exception("Connection with IRC server already opened.");
        }
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo,"[" + Thread.CurrentThread.Name +"] Connection::Connect()");
      
          SecurityOptions options = new SecurityOptions( protocol );
          options.Certificate = null;
          options.Entity = ConnectionEnd.Client;
          options.VerificationType = CredentialVerification.None;
          options.Flags = SecurityFlags.Default;
          options.AllowedAlgorithms = SslAlgorithms.SECURE_CIPHERS;
          client = new SecureTcpClient( options );    
          client.Connect( connectionArgs.Hostname, connectionArgs.Port );
      
        connected = true;
        writer = new StreamWriter( client.GetStream(), TextEncoding );
        writer.AutoFlush = true;
        reader = new StreamReader(  client.GetStream(), TextEncoding );
        socketListenThread = new Thread(new ThreadStart( ReceiveIRCMessages ) );
        socketListenThread.Name = Name;
        socketListenThread.Start();    
        sender.RegisterConnection( connectionArgs );
      }
    }
  #endif

    /// <summary>
    /// Read in message lines from the IRC server
    /// and send them to a parser for processing.
    /// Discard CTCP and DCC messages if these protocols
    /// are not enabled.
    /// </summary>
    internal void ReceiveIRCMessages() 
    {
      Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo, "[" + Thread.CurrentThread.Name +"] Connection::ReceiveIRCMessages()");
      string line;
      try 
      {
        while ( (line = reader.ReadLine() ) != null ) 
        {
          try 
          {
            Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceVerbose, "[" + Thread.CurrentThread.Name +"] Connection::ReceiveIRCMessages() rec'd:" + line );
            //Try any custom parsers first
            if( CustomParse( line ) ) 
            {
              //One of the custom parsers handled this message so
              //we go back to listening
              continue;
            }
            if( DccListener.IsDccRequest( line ) ) 
            {
              if( dccEnabled ) 
              {
                DccListener.DefaultInstance.Parse( this, line );
              }
            }
            else if( CtcpListener.IsCtcpMessage( line) ) 
            {
              if( ctcpEnabled ) 
              {
                ctcpListener.Parse( line );
              }
            }
            else 
            {
              listener.Parse( line );
            }
            if( OnRawMessageReceived != null ) 
            {
              OnRawMessageReceived( line );
            }
          }
          catch( ThreadAbortException e ) 
          {
            Thread.ResetAbort();
            //This exception is raised when the Thread
            //is stopped at user request, i.e. via Disconnect()
            //This will stop the read loop and close the connection.
            break;
          }
        }
      }
      catch (IOException e) 
      { 
        //Trap a connection failure
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceWarning, "[" + Thread.CurrentThread.Name +"] Connection::ReceiveIRCMessages() IO Error while listening for messages " + e);  
        listener.Error( ReplyCode.ConnectionFailed, "Connection to server unexpectedly failed.");
      }
      //The connection to the IRC server has been closed either
      //by client request or the server itself closed the connection.
      client.Close();
      registered = false;
      connected = false;
      listener.Disconnected();
    }
    /// <summary>
    /// Send a message to the IRC server and clear the command buffer.
    /// </summary>
    internal void SendCommand( StringBuilder command) 
    {  
      try
      {
        writer.WriteLine( command.ToString() );  
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceVerbose,"[" + Thread.CurrentThread.Name +"] Connection::SendCommand() sent= " + command);  
        timeLastSent = DateTime.Now;
      }
      catch( Exception e ) 
      {
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceWarning,"[" + Thread.CurrentThread.Name +"] Connection::SendCommand() exception=" + e);
      }
      if( OnRawMessageSent != null ) 
      {
        OnRawMessageSent( command.ToString() );
      }
      command.Remove(0, command.Length );
    }
    /// <summary>
    /// Send a message to the IRC server which does
    /// not affect the client's idle time. Used for automatic replies
    /// such as PONG or Ctcp repsones.
    /// </summary>
    internal void SendAutomaticReply( StringBuilder command) 
    {  
      try
      {
        writer.WriteLine( command.ToString() );  
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceVerbose,"[" + Thread.CurrentThread.Name +"] Connection::SendAutomaticReply() message=" + command);  
      }
      catch( Exception e ) 
      {
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceWarning,"[" + Thread.CurrentThread.Name +"] Connection::SendAutomaticReply() exception=" + e );
      }
      command.Remove(0, command.Length );
    }


  #if SSL
    ///<summary>
    /// Connect to the IRC server and start listening for messages
    /// on a new thread.
    /// </summary>
    /// <exception cref="SocketException">If a connection cannot be established with the IRC server</exception>
    public void Connect() 
    {
      Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo,"Connecting over clear socket");
      ConnectClient( SecureProtocol.None );
    }

    ///<summary>
    /// Connect to the IRC server over an encrypted connection using TLS.
    /// </summary>
    /// <exception cref="SocketException">If a connection cannot be established with the IRC server</exception>
    public void SecureConnect() 
    {
      Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo,"Connecting over encrypted socket");
      ConnectClient( SecureProtocol.Tls1 );
    }

    
  #else
    /// <summary>
    /// Connect to the IRC server and start listening for messages
    /// on a new thread.
    /// </summary>
    /// <exception cref="SocketException">If a connection cannot be established with the IRC server</exception>
    public void Connect() 
    {
      lock ( this ) 
      {
        if( connected ) 
        {
          throw new Exception("Connection with IRC server already opened.");
        }
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo,"[" + Thread.CurrentThread.Name +"] Connection::Connect()");
        client = new TcpClient();
        client.Connect( connectionArgs.Hostname, connectionArgs.Port );
        connected = true;
        writer = new StreamWriter( client.GetStream(), TextEncoding );
        writer.AutoFlush = true;
        reader = new StreamReader( client.GetStream(), TextEncoding );
        socketListenThread = new Thread(new ThreadStart( ReceiveIRCMessages ) );
        socketListenThread.Name = Name;
        socketListenThread.Start();    
        sender.RegisterConnection( connectionArgs );
      }
    }
  #endif
    /// <summary>
    /// Sends a 'Quit' message to the server, closes the connection,
    /// and stops the listening thread. 
    /// </summary>
    /// <remarks>The state of the connection will remain the same even after a disconnect,
    /// so the connection can be reopened. All the event handlers will remain registered.</remarks>
    /// <param name="reason">A message displayed to IRC users upon disconnect.</param>
    public void Disconnect( string reason ) 
    {  
      lock ( this ) 
      {
        if( !connected ) 
        {
          throw new Exception("Not connected to IRC server.");
        }
        Debug.WriteLineIf( Rfc2812Util.IrcTrace.TraceInfo,"[" + Thread.CurrentThread.Name +"] Connection::Disconnect()");
        listener.Disconnecting();
        sender.Quit( reason );
        listener.Disconnected();
        //Thanks to Thomas for this next block
        if ( socketListenThread.Join( TimeSpan.FromSeconds( 1 ) ) == false )
          socketListenThread.Abort();
      }
    }
    /// <summary>
    /// A friendly name for this connection.
    /// </summary>
    /// <returns>The Name property</returns>
    public override string ToString() 
    {
      return this.Name;
    }

    /// <summary>
    /// Adds a parser class to a list of custom parsers. 
    /// Any number can be added. The custom parsers
    /// will be tested using <c>CanParse()</c> before
    /// the default parsers. The last parser to be added
    /// will be the first to process a message.
    /// </summary>
    /// <param name="parser">Any class that implements IParser.</param>
    public void AddParser( IParser parser ) 
    {
      parsers.Insert(0, parser );
    }
    /// <summary>
    /// Remove a custom parser class.
    /// </summary>
    /// <param name="parser">Any class that implements IParser.</param>
    public void RemoveParser( IParser parser ) 
    {
      parsers.Remove( parser );
    }

  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.