Advertisement
Guest User

Untitled

a guest
May 3rd, 2017
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 73.79 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Sockets;
  10. using System.Reflection;
  11. using System.Text;
  12. using System.Text.RegularExpressions;
  13. using System.Threading;
  14. using IrcDotNet.Common.Collections;
  15.  
  16. namespace IrcDotNet
  17. {
  18.     /// <summary>
  19.     /// Provides methods for communicating with an IRC (Internet Relay Chat) server.
  20.     /// </summary>
  21.     public partial class IrcClient : IDisposable
  22.     {
  23.         private const int defaultPort = 6667;
  24.         private const int maxParamsCount = 15;
  25.  
  26.         // Regular expressions used for extracting information from protocol messages.
  27.         private static readonly string regexNickName;
  28.         private static readonly string regexUserName;
  29.         private static readonly string regexHostName;
  30.         private static readonly string regexChannelName;
  31.         private static readonly string regexTargetMask;
  32.         private static readonly string regexServerName;
  33.         private static readonly string regexNickNameId;
  34.         private static readonly string regexUserNameId;
  35.         private static readonly string regexMessagePrefix;
  36.         private static readonly string regexMessageTarget;
  37.  
  38.         static IrcClient()
  39.         {
  40.             regexNickName = @"(?<nick>[^!@]+)";
  41.             regexUserName = @"(?<user>[^!@]+)";
  42.             regexHostName = @"(?<host>[^%@]+)";
  43.             regexChannelName = @"(?<channel>[#+!&].+)";
  44.             regexTargetMask = @"(?<targetMask>[$#].+)";
  45.             regexServerName = @"(?<server>[^%@]+?\..*)";
  46.             regexNickNameId = string.Format(@"{0}(?:(?:!{1})?@{2})?", regexNickName, regexUserName, regexHostName);
  47.             regexUserNameId = string.Format(@"{0}(?:(?:%{1})?@{2}|%{1})", regexUserName, regexHostName,
  48.                 regexServerName);
  49.             regexMessagePrefix = string.Format(@"^(?:{0}|{1})$", regexServerName, regexNickNameId);
  50.             regexMessageTarget = string.Format(@"^(?:{0}|{1}|{2}|{3})$", regexChannelName, regexUserNameId,
  51.                 regexTargetMask, regexNickNameId);
  52.         }
  53.  
  54.         // Internal collection of all known servers.
  55.         private Collection<IrcServer> servers;
  56.         // True if connection has been registered with server;
  57.         private bool isRegistered;
  58.         // Stores information about local user.
  59.         private IrcLocalUser localUser;
  60.         // Internal and exposable dictionaries of various features supported by server.
  61.         private Dictionary<string, string> serverSupportedFeatures;
  62.         private ReadOnlyDictionary<string, string> serverSupportedFeaturesReadOnly;
  63.         // Builds MOTD (message of the day) string as it is received from server.
  64.         private StringBuilder motdBuilder;
  65.         // Internal and exposable collections of all currently joined channels.
  66.         private ObservableCollection<IrcChannel> channels;
  67.         private IrcChannelCollection channelsReadOnly;
  68.         // Internal and exposable collections of all known users.
  69.         private ObservableCollection<IrcUser> users;
  70.         private IrcUserCollection usersReadOnly;
  71.  
  72.         private TcpClient client;
  73.         private Thread readThread;
  74.         private Thread writeThread;
  75.         private NetworkStream stream;
  76.         private StreamWriter writer;
  77.         private StreamReader reader;
  78.         // True if client can currently be disconnected.
  79.         private bool canDisconnect;
  80.         // Dictionary of message processor routines, keyed by their command names.
  81.         private Dictionary<string, MessageProcessor> messageProcessors;
  82.         // Array of message processor routines, keyed by their numeric codes (000 to 999).
  83.         private Dictionary<int, MessageProcessor> numMessageProcessors;
  84.         // Queue of messages to be sent by write loop when appropiate.
  85.         private Queue<string> messageSendQueue;
  86.         // Prevents client from flooding server with messages by limiting send rate.
  87.         private IIrcFloodPreventer floodPreventer;
  88.  
  89.         private bool isDisposed = false;
  90.  
  91.         /// <summary>
  92.         /// Initialises a new instance of the <see cref="IrcClient"/> class.
  93.         /// </summary>
  94.         public IrcClient()
  95.         {
  96.             this.client = new TcpClient();
  97.             this.readThread = new Thread(ReadLoop);
  98.             this.writeThread = new Thread(WriteLoop);
  99.             this.canDisconnect = false;
  100.             this.messageProcessors = new Dictionary<string, MessageProcessor>(
  101.                 StringComparer.InvariantCultureIgnoreCase);
  102.             this.numMessageProcessors = new Dictionary<int, MessageProcessor>(1000);
  103.             this.messageSendQueue = new Queue<string>();
  104.             this.floodPreventer = null;
  105.  
  106.             InitialiseMessageProcessors();
  107.             ResetState();
  108.         }
  109.  
  110.         /// <summary>
  111.         /// Finalises an instance of the <see cref="IrcClient"/> class.
  112.         /// </summary>
  113.         ~IrcClient()
  114.         {
  115.             Dispose(false);
  116.         }
  117.  
  118.         /// <summary>
  119.         /// Gets whether the client connection has been registered with the server.
  120.         /// </summary>
  121.         /// <value><see langword="true"/> if the connection has been registered; <see langword="false"/>, otherwise.
  122.         /// </value>
  123.         public bool IsRegistered
  124.         {
  125.             get { return this.isRegistered; }
  126.         }
  127.  
  128.         /// <summary>
  129.         /// Gets the local user. The local user is the user managed by this client connection.
  130.         /// </summary>
  131.         /// <value>The local user.</value>
  132.         public IrcLocalUser LocalUser
  133.         {
  134.             get { return this.localUser; }
  135.         }
  136.  
  137.         /// <summary>
  138.         /// Gets the Welcome message sent by the server.
  139.         /// This value is set after successful registration of the connection.
  140.         /// </summary>
  141.         /// <value>The server Welcome message.</value>
  142.         public string WelcomeMessage
  143.         {
  144.             get;
  145.             private set;
  146.         }
  147.  
  148.         /// <summary>
  149.         /// Gets the Your Host message sent by the server.
  150.         /// This value is set after successful registration of the connection.
  151.         /// </summary>
  152.         /// <value>The server Your Host message.</value>
  153.         public string YourHostMessage
  154.         {
  155.             get;
  156.             private set;
  157.         }
  158.  
  159.         /// <summary>
  160.         /// Gets the Created message sent by the server.
  161.         /// This value is set after successful registration of the connection.
  162.         /// </summary>
  163.         /// <value>The server Created message.</value>
  164.         public string ServerCreatedMessage
  165.         {
  166.             get;
  167.             private set;
  168.         }
  169.  
  170.         /// <summary>
  171.         /// Gets the host name of the server.
  172.         /// This value is set after successful registration of the connection.
  173.         /// </summary>
  174.         /// <value>The server host name.</value>
  175.         public string ServerName
  176.         {
  177.             get;
  178.             private set;
  179.         }
  180.  
  181.         /// <summary>
  182.         /// Gets the version of the server.
  183.         /// This value is set after successful registration of the connection.
  184.         /// </summary>
  185.         /// <value>The server version.</value>
  186.         public string ServerVersion
  187.         {
  188.             get;
  189.             private set;
  190.         }
  191.  
  192.         /// <summary>
  193.         /// Gets a collection of the user modes available on the server.
  194.         /// This value is set after successful registration of the connection.
  195.         /// </summary>
  196.         /// <value>A list of user modes available on the server.</value>
  197.         public IEnumerable<char> ServerAvailableUserModes
  198.         {
  199.             get;
  200.             private set;
  201.         }
  202.  
  203.         /// <summary>
  204.         /// Gets a collection of the channel modes available on the server.
  205.         /// This value is set after successful registration of the connection.
  206.         /// </summary>
  207.         /// <value>A list of channel modes available on the server.</value>
  208.         public IEnumerable<char> ServerAvailableChannelModes
  209.         {
  210.             get;
  211.             private set;
  212.         }
  213.  
  214.         /// <summary>
  215.         /// Gets a dictionary of the features supported by the server, keyed by feature name, as returned by the
  216.         /// ISUPPORT message.
  217.         /// This value is set after successful registration of the connection.
  218.         /// </summary>
  219.         /// <value>A dictionary of features supported by the server.</value>
  220.         public ReadOnlyDictionary<string, string> ServerSupportedFeatures
  221.         {
  222.             get { return this.serverSupportedFeaturesReadOnly; }
  223.         }
  224.  
  225.         /// <summary>
  226.         /// Gets the Message of the Day (MOTD) sent by the server.
  227.         /// This value is set after successful registration of the connection.
  228.         /// </summary>
  229.         /// <value>The Message of the Day sent by the server.</value>
  230.         public string MessageOfTheDay
  231.         {
  232.             get;
  233.             private set;
  234.         }
  235.  
  236.         /// <summary>
  237.         /// Gets a collection of all channels known to the client.
  238.         /// </summary>
  239.         /// <value>A collection of known channels.</value>
  240.         public IrcChannelCollection Channels
  241.         {
  242.             get { return this.channelsReadOnly; }
  243.         }
  244.  
  245.         /// <summary>
  246.         /// Gets a collection of all users known to the client, including the local user.
  247.         /// </summary>
  248.         /// <value>A collection of known users.</value>
  249.         public IrcUserCollection Users
  250.         {
  251.             get { return this.usersReadOnly; }
  252.         }
  253.  
  254.         /// <summary>
  255.         /// Gets or sets an object that limits the rate of outgoing messages in order to prevent flooding the server.
  256.         /// The value is <see langword="null"/> by default, which indicates that no flood prevention should be
  257.         /// performed.
  258.         /// </summary>
  259.         /// <value>A flood preventer object.</value>
  260.         public IIrcFloodPreventer FloodPreventer
  261.         {
  262.             get { return floodPreventer; }
  263.             set { this.floodPreventer = value; }
  264.         }
  265.  
  266.         /// <summary>
  267.         /// Gets whether the client is currently connected to a server.
  268.         /// </summary>
  269.         /// <value><see langword="true"/> if the client is connected; <see langword="false"/>, otherwise.</value>
  270.         public bool IsConnected
  271.         {
  272.             get { return this.client.Connected; }
  273.         }
  274.  
  275.         /// <summary>
  276.         /// Gets whether the <see cref="IrcClient"/> object has been disposed.
  277.         /// </summary>
  278.         /// <value><see langword="true"/> if the <see cref="IrcClient"/> object has been disposed;
  279.         /// <see langword="false"/>, otherwise.</value>
  280.         public bool IsDisposed
  281.         {
  282.             get { return this.isDisposed; }
  283.         }
  284.  
  285.         /// <summary>
  286.         /// Releases all resources used by the <see cref="IrcClient"/> object.
  287.         /// </summary>
  288.         public void Dispose()
  289.         {
  290.             Dispose(true);
  291.             GC.SuppressFinalize(this);
  292.         }
  293.  
  294.         /// <summary>
  295.         /// Releases all resources used by the <see cref="IrcClient"/>.
  296.         /// </summary>
  297.         /// <param name="disposing"><see langword="true"/> if the user is actively disposing the object;
  298.         /// <see langword="false"/> if the garbage collector is finalising the object.</param>
  299.         protected void Dispose(bool disposing)
  300.         {
  301.             if (!this.isDisposed)
  302.             {
  303.                 if (disposing)
  304.                 {
  305.                     DisconnectInternal();
  306.  
  307.                     if (this.client != null)
  308.                     {
  309.                         this.client.Close();
  310.                         this.client = null;
  311.                     }
  312.                     if (this.readThread != null)
  313.                     {
  314.                         if (this.readThread.IsAlive)
  315.                             this.readThread.Join(1000);
  316.                         this.readThread = null;
  317.                     }
  318.                     if (this.writeThread != null)
  319.                     {
  320.                         if (this.writeThread.IsAlive)
  321.                             this.writeThread.Join(1000);
  322.                         this.writeThread = null;
  323.                     }
  324.                     if (this.stream != null)
  325.                     {
  326.                         this.stream.Close();
  327.                         this.stream = null;
  328.                     }
  329.                     if (this.writer != null)
  330.                     {
  331.                         this.writer.Close();
  332.                         this.writer = null;
  333.                     }
  334.                     if (this.reader != null)
  335.                     {
  336.                         this.reader.Close();
  337.                         this.reader = null;
  338.                     }
  339.                 }
  340.             }
  341.             this.isDisposed = true;
  342.         }
  343.  
  344.         /// <summary>
  345.         /// Occurs when the client has connected to the server.
  346.         /// </summary>
  347.         public event EventHandler<EventArgs> Connected;
  348.         /// <summary>
  349.         /// Occurs when the client has failed to connect to the server.
  350.         /// </summary>
  351.         public event EventHandler<IrcErrorEventArgs> ConnectFailed;
  352.         /// <summary>
  353.         /// Occurs when the client has disconnected from the server.
  354.         /// </summary>
  355.         public event EventHandler<EventArgs> Disconnected;
  356.         /// <summary>
  357.         /// Occurs when the client encounters an error.
  358.         /// </summary>
  359.         public event EventHandler<IrcErrorEventArgs> Error;
  360.         /// <summary>
  361.         /// Occurs when a protocol (numeric) error is received from the server.
  362.         /// </summary>
  363.         public event EventHandler<IrcProtocolErrorEventArgs> ProtocolError;
  364.         /// <summary>
  365.         /// Occurs when an Error message is received from the server.
  366.         /// </summary>
  367.         public event EventHandler<IrcErrorMessageEventArgs> ErrorMessageReceived;
  368.         /// <summary>
  369.         /// Occurs when the connection has been registered.
  370.         /// </summary>
  371.         public event EventHandler<EventArgs> Registered;
  372.         /// <summary>
  373.         /// Occurs when a bounce message is received from the server, telling the client to connect to a new server.
  374.         /// </summary>
  375.         public event EventHandler<IrcServerInfoEventArgs> ServerBounce;
  376.         /// <summary>
  377.         /// Occurs when a list of features supported by the server (ISUPPORT) has been received.
  378.         /// This event may be raised more than once after registration, depending on the size of the list received.
  379.         /// </summary>
  380.         public event EventHandler<EventArgs> ServerSupportedFeaturesReceived;
  381.         /// <summary>
  382.         /// Occurs when a ping query is received from the server.
  383.         /// The client automatically replies to pings from the server; this event is only a notification.
  384.         /// </summary>
  385.         public event EventHandler<IrcPingOrPongReceivedEventArgs> PingReceived;
  386.         /// <summary>
  387.         /// Occurs when a pong reply is received from the server.
  388.         /// </summary>
  389.         public event EventHandler<IrcPingOrPongReceivedEventArgs> PongReceived;
  390.         /// <summary>
  391.         /// Occurs when the Message of the Day (MOTD) has been received from the server.
  392.         /// </summary>
  393.         public event EventHandler<EventArgs> MotdReceived;
  394.         /// <summary>
  395.         /// Occurs when a reply to a Who query has been received from the server.
  396.         /// </summary>
  397.         public event EventHandler<IrcNameEventArgs> WhoReplyReceived;
  398.         /// <summary>
  399.         /// Occurs when a reply to a Who Is query has been received from the server.
  400.         /// </summary>
  401.         public event EventHandler<IrcUserEventArgs> WhoIsReplyReceived;
  402.         /// <summary>
  403.         /// Occurs when a reply to a Who Was query has been received from the server.
  404.         /// </summary>
  405.         public event EventHandler<IrcUserEventArgs> WhoWasReplyReceived;
  406.  
  407.         /// <summary>
  408.         /// Sends a Who query to the server targeting the specified channel or user masks.
  409.         /// </summary>
  410.         /// <param name="mask">A wildcard expression for matching against channel names; or if none can be found,
  411.         /// host names, server names, real names, and nick names of users.</param>
  412.         /// <param name="onlyOperators"><see langword="true"/> to match only server operators; otherwise,
  413.         /// <see langword="false"/>. Default is to match all users.</param>
  414.         public void QueryWho(string mask = null, bool onlyOperators = false)
  415.         {
  416.             SendMessageWho(mask, onlyOperators);
  417.         }
  418.  
  419.         /// <inheritdoc cref="QueryWhoIs(IEnumerable{string})"/>
  420.         public void QueryWhoIs(params string[] nickNameMasks)
  421.         {
  422.             QueryWhoIs((IEnumerable<string>)nickNameMasks);
  423.         }
  424.  
  425.         /// <overloads>Sends a Who Is query to the server.</overloads>
  426.         /// <summary>
  427.         /// Sends a Who Is query to server targeting the specified nick name masks.
  428.         /// </summary>
  429.         /// <param name="nickNameMasks">A collection of wildcard expressions for matching against nick names of users.
  430.         /// </param>
  431.         public void QueryWhoIs(IEnumerable<string> nickNameMasks)
  432.         {
  433.             SendMessageWhoIs(nickNameMasks);
  434.         }
  435.  
  436.         /// <inheritdoc cref="QueryWhoWas(IEnumerable{string}, int)"/>
  437.         public void QueryWhoWas(params string[] nickNames)
  438.         {
  439.             QueryWhoWas((IEnumerable<string>)nickNames);
  440.         }
  441.  
  442.         /// <summary>
  443.         /// Sends a Who Was query to server targeting the specified nick names.
  444.         /// </summary>
  445.         /// <param name="nickNames">The nick names of the users to query.</param>
  446.         /// <param name="entriesCount">The maximum number of entries to return from the query. Default is an unlimited
  447.         /// number.</param>
  448.         public void QueryWhoWas(IEnumerable<string> nickNames, int entriesCount = -1)
  449.         {
  450.             SendMessageWhoWas(nickNames, entriesCount);
  451.         }
  452.  
  453.         /// <summary>
  454.         /// Requests the Message of the Day (MOTD) from the specified server.
  455.         /// </summary>
  456.         /// <param name="serverName">The name of the server from which to request the MOTD. Default is the current
  457.         /// server.</param>
  458.         public void GetMessageOfTheDay(string serverName = null)
  459.         {
  460.             SendMessageMotd(serverName);
  461.         }
  462.  
  463.         /// <summary>
  464.         /// Requests statistics about the size of the network.
  465.         /// If <paramref name="serverMask"/> is specified, then the server only returns information about the part of
  466.         /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole
  467.         /// network
  468.         /// </summary>
  469.         /// <param name="serverMask">A wildcard expression for matching against server names. Default matches the whole
  470.         /// network.</param>
  471.         /// <param name="targetServer">The server to which to forward the request.</param>
  472.         public void GetNetworkStatistics(string serverMask = null, string targetServer = null)
  473.         {
  474.             SendMessageLUsers(serverMask, targetServer);
  475.         }
  476.  
  477.         /// <summary>
  478.         /// Requests the version of the specified server.
  479.         /// </summary>
  480.         /// <param name="serverName">The name of the server whose version to request.</param>
  481.         public void GetServerVersion(string serverName = null)
  482.         {
  483.             SendMessageVersion(serverName);
  484.         }
  485.  
  486.         /// <summary>
  487.         /// Requests the statistics of the specified server.
  488.         /// </summary>
  489.         /// <param name="query">The query that indicates to the server what statistics to return.</param>
  490.         /// <param name="serverName">The name of the server whose statistics to request.</param>
  491.         public void GetServerStats(string query = null, string serverName = null)
  492.         {
  493.             SendMessageStats(query, serverName);
  494.         }
  495.  
  496.         /// <summary>
  497.         /// Requests a list of all servers known by the target server.
  498.         /// If <paramref name="serverMask"/> is specified, then the server only returns information about the part of
  499.         /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole
  500.         /// network.
  501.         /// </summary>
  502.         /// <param name="serverMask">A wildcard expression for matching against server names. Default matches the whole
  503.         /// network.</param>
  504.         /// <param name="targetServer">The server to which to forward the request.</param>
  505.         public void GetServerLinks(string serverMask = null, string targetServer = null)
  506.         {
  507.             SendMessageStats(targetServer, serverMask);
  508.         }
  509.  
  510.         /// <summary>
  511.         /// Requests the local time on the specified server.
  512.         /// </summary>
  513.         /// <param name="serverName">The name of the server whose time to request</param>
  514.         public void GetServerTime(string serverName = null)
  515.         {
  516.             SendMessageTime(serverName);
  517.         }
  518.  
  519.         /// <summary>
  520.         /// Sends a ping to the specified server.
  521.         /// </summary>
  522.         /// <param name="serverName">The name of the server to ping.</param>
  523.         public void Ping(string serverName = null)
  524.         {
  525.             SendMessagePing(this.localUser.NickName, serverName);
  526.         }
  527.  
  528.         /// <summary>
  529.         /// Quits the server, giving the specified comment.
  530.         /// </summary>
  531.         /// <param name="comment">The comment to send the server upon quitting.</param>
  532.         public void Quit(string comment = null)
  533.         {
  534.             SendMessageQuit(comment);
  535.         }
  536.  
  537.         #region Proxy Methods
  538.  
  539.         internal void SetNickName(string nickName)
  540.         {
  541.             SendMessageNick(nickName);
  542.         }
  543.  
  544.         internal void SetAway(string text)
  545.         {
  546.             SendMessageAway(text);
  547.         }
  548.  
  549.         internal void UnsetAway()
  550.         {
  551.             SendMessageAway();
  552.         }
  553.  
  554.         internal void GetChannelModes(IrcChannel channel, string modes = null)
  555.         {
  556.             SendMessageChannelMode(channel.Name, modes);
  557.         }
  558.  
  559.         internal void SetChannelModes(IrcChannel channel, string modes, IEnumerable<string> modeParameters = null)
  560.         {
  561.             SendMessageChannelMode(channel.Name, modes, modeParameters);
  562.         }
  563.  
  564.         internal void GetLocalUserModes(IrcLocalUser user)
  565.         {
  566.             SendMessageUserMode(user.NickName);
  567.         }
  568.  
  569.         internal void SetLocalUserModes(IrcLocalUser user, string modes)
  570.         {
  571.             SendMessageUserMode(user.NickName, modes);
  572.         }
  573.  
  574.         internal void Join(IEnumerable<string> channels)
  575.         {
  576.             SendMessageJoin(channels);
  577.         }
  578.  
  579.         internal void Join(IEnumerable<Tuple<string, string>> channels)
  580.         {
  581.             SendMessageJoin(channels);
  582.         }
  583.  
  584.         internal void Leave(IEnumerable<string> channels, string comment = null)
  585.         {
  586.             SendMessagePart(channels, comment);
  587.         }
  588.  
  589.         internal void Invite(IrcChannel channel, IrcUser user)
  590.         {
  591.             SendMessageInvite(channel.Name, user.NickName);
  592.         }
  593.  
  594.         internal void Kick(IrcChannel channel, IEnumerable<IrcUser> users, string comment = null)
  595.         {
  596.             SendMessageKick(channel.Name, users.Select(u => u.NickName), comment);
  597.         }
  598.  
  599.         internal void Kick(IEnumerable<IrcChannelUser> channelUsers, string comment = null)
  600.         {
  601.             SendMessageKick(channelUsers.Select(cu => Tuple.Create(cu.Channel.Name, cu.User.NickName)), comment);
  602.         }
  603.  
  604.         internal void SendPrivateMessage(IEnumerable<string> targetsNames, string text)
  605.         {
  606.             var targetsNamesArray = targetsNames.ToArray();
  607.             var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray();
  608.             CheckTextValid(text);
  609.             SendMessagePrivateMessage(targetsNamesArray, text);
  610.             this.localUser.HandleMessageSent(targets, text);
  611.         }
  612.  
  613.         internal void SendNotice(IEnumerable<string> targetsNames, string text)
  614.         {
  615.             var targetsNamesArray = targetsNames.ToArray();
  616.             var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray();
  617.             CheckTextValid(text);
  618.             SendMessageNotice(targetsNamesArray, text);
  619.             this.localUser.HandleNoticeSent(targets, text);
  620.         }
  621.  
  622.         private void CheckTextValid(string text)
  623.         {
  624.             if (text.Any(c => c == '\r' || c == '\n'))
  625.                 throw new ArgumentException(Properties.Resources.ErrorMessageTextCannotContainNewLine, "text");
  626.         }
  627.  
  628.         #endregion
  629.  
  630.         private void InitialiseMessageProcessors()
  631.         {
  632.             // Find all methods in class that are marked by one or more instances of MessageProcessrAttribute.
  633.             // Add  each pair of command & processor  to dictionary (with at least one command per processor).
  634.            var messageProcessorsMethods = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
  635.             foreach (var methodInfo in messageProcessorsMethods)
  636.             {
  637.                 var messageProcessorAttributes = (MessageProcessorAttribute[])methodInfo.GetCustomAttributes(
  638.                     typeof(MessageProcessorAttribute), true);
  639.                 if (messageProcessorAttributes.Length > 0)
  640.                 {
  641.                     var methodDelegate = (MessageProcessor)Delegate.CreateDelegate(typeof(MessageProcessor), this,
  642.                         methodInfo);
  643.                     foreach (var attribute in messageProcessorAttributes)
  644.                     {
  645.                         var commandRangeParts = attribute.Command.Split('-');
  646.                         if (commandRangeParts.Length == 2)
  647.                         {
  648.                             // Numeric command range was specified.
  649.                             var commandRangeStart = int.Parse(commandRangeParts[0]);
  650.                             var commandRangeEnd = int.Parse(commandRangeParts[1]);
  651.                             for (int code = commandRangeStart; code <= commandRangeEnd; code++)
  652.                                this.numMessageProcessors.Add(code, methodDelegate);
  653.                        }
  654.                        else
  655.                        {
  656.                            // Single command was specified. Check whether it is numeric or alphabetic.
  657.                            int commandCode;
  658.                            if (int.TryParse(attribute.Command, out commandCode))
  659.                                // Numeric
  660.                                this.numMessageProcessors.Add(commandCode, methodDelegate);
  661.                            else
  662.                                // Alphabetic
  663.                                this.messageProcessors.Add(attribute.Command, methodDelegate);
  664.                        }
  665.                    }
  666.                }
  667.            }
  668.        }
  669.  
  670.        private void ReadLoop()
  671.        {
  672.            try
  673.            {
  674.                // Read each message from network stream, one per line, until client is disconnected.
  675.                while (this.client != null && this.client.Connected)
  676.                {
  677.                    var line = this.reader.ReadLine();
  678.                    if (line == null)
  679.                        break;
  680.  
  681.                    Debug.WriteLine(DateTime.Now.ToLongTimeString() + " >>> " + line);
  682.  
  683.                     string prefix = null;
  684.                     string command = null;
  685.  
  686.                     // Extract prefix from message, if it contains one.
  687.                     if (line[0] == ':')
  688.                     {
  689.                         var firstSpaceIndex = line.IndexOf(' ');
  690.                         prefix = line.Substring(1, firstSpaceIndex - 1);
  691.                         line = line.Substring(firstSpaceIndex + 1);
  692.                     }
  693.  
  694.                     // Extract command from message.
  695.                     command = line.Substring(0, line.IndexOf(' '));
  696.                     line = line.Substring(command.Length + 1);
  697.  
  698.                     // Extract parameters from message.
  699.                     // Each parameter is separated by a single space, except the last one, which may contain spaces if it is prefixed by a colon.
  700.                     var parameters = new string[maxParamsCount];
  701.                     int paramStartIndex, paramEndIndex = -1;
  702.                     int lineColonIndex = line.LastIndexOf(':');
  703.                     if (lineColonIndex == -1)
  704.                         lineColonIndex = line.Length;
  705.                     for (int i = 0; i < parameters.Length; i++)
  706.                    {
  707.                        paramStartIndex = paramEndIndex + 1;
  708.                        paramEndIndex = line.IndexOf(' ', paramStartIndex);
  709.                        if (paramEndIndex == -1)
  710.                            paramEndIndex = line.Length;
  711.                        if (paramEndIndex > lineColonIndex)
  712.                         {
  713.                             paramStartIndex++;
  714.                             paramEndIndex = line.Length;
  715.                         }
  716.                         parameters[i] = line.Substring(paramStartIndex, paramEndIndex - paramStartIndex);
  717.                         if (paramEndIndex == line.Length)
  718.                             break;
  719.                     }
  720.  
  721.                     var message = new IrcMessage(this, prefix, command, parameters);
  722.                     ReadMessage(message);
  723.                 }
  724.             }
  725.             catch (IOException exIO)
  726.             {
  727.                 var socketException = exIO.InnerException as SocketException;
  728.                 if (socketException != null)
  729.                 {
  730.                     switch (socketException.SocketErrorCode)
  731.                     {
  732.                         case SocketError.Interrupted:
  733.                         case SocketError.NotConnected:
  734.                             return;
  735.                     }
  736.                 }
  737.  
  738.                 OnError(new IrcErrorEventArgs(exIO));
  739.             }
  740. #if !DEBUG
  741.             catch (Exception ex)
  742.             {
  743.                 OnError(new IrcErrorEventArgs(ex));
  744.             }
  745. #endif
  746.             finally
  747.             {
  748.                 DisconnectInternal();
  749.             }
  750.         }
  751.  
  752.         private void WriteLoop()
  753.         {
  754.             try
  755.             {
  756.                 // Continuously write messages in send queue to network stream, within given rate limit.
  757.                 while (this.client != null && this.client.Connected)
  758.                {
  759.                    // Send messages in send queue until flood preventer stops it.
  760.                    while (this.messageSendQueue.Count > 0)
  761.                    {
  762.                        if (this.floodPreventer != null && !this.floodPreventer.CanSendMessage())
  763.                            break;
  764.                         var line = this.messageSendQueue.Dequeue();
  765.                         this.writer.WriteLine(line);
  766.                         if (this.floodPreventer != null)
  767.                             this.floodPreventer.HandleMessageSent();
  768.  
  769.                         Debug.WriteLine(DateTime.Now.ToLongTimeString() + " <<< " + line);
  770.                    }
  771.                    this.writer.Flush();
  772.                    Thread.Sleep(50);
  773.                }
  774.            }
  775.            catch (IOException exIO)
  776.            {
  777.                var socketException = exIO.InnerException as SocketException;
  778.                if (socketException != null)
  779.                {
  780.                    switch (socketException.SocketErrorCode)
  781.                    {
  782.                        case SocketError.Interrupted:
  783.                        case SocketError.NotConnected:
  784.                            return;
  785.                    }
  786.                }
  787.                OnError(new IrcErrorEventArgs(exIO));
  788.            }
  789. #if !DEBUG
  790.            catch (Exception ex)
  791.            {
  792.                OnError(new IrcErrorEventArgs(ex));
  793.            }
  794. #endif
  795.            finally
  796.            {
  797.                DisconnectInternal();
  798.            }
  799.        }
  800.        private void ReadMessage(IrcMessage message)
  801.        {
  802.            // Try to find corresponding message processor for command of given message.
  803.            MessageProcessor msgProc;
  804.            int commandCode;
  805.            if (this.messageProcessors.TryGetValue(message.Command, out msgProc) ||
  806.                (int.TryParse(message.Command, out commandCode) &&
  807.                this.numMessageProcessors.TryGetValue(commandCode, out msgProc)))
  808.            {
  809.                try
  810.                {
  811.                    msgProc(message);
  812.                }
  813. #if !DEBUG
  814.                catch (Exception ex)
  815.                {
  816.                    OnError(new IrcErrorEventArgs(ex));
  817.                }
  818. #endif
  819.                finally
  820.                {
  821.                }
  822.            }
  823.            else
  824.            {
  825.                // Unknown command.
  826.                Debug.WriteLine("Unknown message command '{0}'.", message.Command);
  827.            }
  828.        }
  829.        /// <inheritdoc cref="WriteMessage(string, string, IEnumerable{string})"/>
  830.         protected void WriteMessage(string prefix, string command, params string[] parameters)
  831.         {
  832.             WriteMessage(new IrcMessage(this, null, command, parameters));
  833.         }
  834.  
  835.         /// <inheritdoc cref="WriteMessage(IrcMessage)"/>
  836.         /// <param name="prefix">The message prefix, which represents the source of the message.</param>
  837.         /// <param name="command">The name of the command.</param>
  838.         /// <param name="parameters">A collection of the parameters to the command.</param>
  839.         protected void WriteMessage(string prefix, string command, IEnumerable<string> parameters)
  840.         {
  841.             WriteMessage(new IrcMessage(this, null, command, parameters.ToArray()));
  842.         }
  843.  
  844.         /// <inheritdoc cref="WriteMessage(string)"/>
  845.         /// <summary>
  846.         /// Writes the specified message (prefix, command, and parameters) to the network stream.
  847.         /// </summary>
  848.         /// <param name="message">The message to write.</param>
  849.         /// <exception cref="ArgumentException">
  850.         /// <paramref name="message"/> contains more than 15 many parameters. -or-
  851.         /// The value of <see cref="IrcMessage.Prefix"/> of <paramref name="message"/> is invalid. -or-
  852.         /// The value of <see cref="IrcMessage.Command"/> of <paramref name="message"/> is invalid. -or-
  853.         /// The value of one of the items of <see cref="IrcMessage.Parameters"/> of <paramref name="message"/> is
  854.         /// invalid.
  855.         /// </exception>
  856.         protected void WriteMessage(IrcMessage message)
  857.         {
  858.             if (message.Parameters.Count > maxParamsCount)
  859.                 throw new ArgumentException(Properties.Resources.ErrorMessageTooManyParams, "parameters");
  860.  
  861.             var line = new StringBuilder();
  862.             if (message.Prefix != null)
  863.                 line.Append(":" + CheckPrefix(message.Prefix) + " ");
  864.             line.Append(CheckCommand(message.Command).ToUpper());
  865.             for (int i = 0; i < message.Parameters.Count - 1; i++)
  866.            {
  867.                if (message.Parameters[i] != null)
  868.                    line.Append(" " + CheckMiddleParameter(message.Parameters[i].ToString()));
  869.            }
  870.            if (message.Parameters.Count > 0)
  871.             {
  872.                 var lastParameter = message.Parameters[message.Parameters.Count - 1];
  873.                 if (lastParameter != null)
  874.                     line.Append(" :" + CheckTrailingParameter(lastParameter));
  875.             }
  876.             WriteMessage(line.ToString());
  877.         }
  878.  
  879.         /// <summary>
  880.         /// Writes the specified line to the network stream.
  881.         /// </summary>
  882.         /// <param name="line">The line to send.</param>
  883.         /// <remarks>
  884.         /// This method adds the specified line to the send queue and then immediately returns.
  885.         /// The message is in fact only sent when the write loop takes the message from the queue and sends it over the
  886.         /// connection.
  887.         /// </remarks>
  888.         /// <exception cref="ObjectDisposedException">The object has already been been disposed.</exception>
  889.         /// <exception cref="ArgumentException"><paramref name="line"/> is longer than 510 characters.</exception>
  890.         private void WriteMessage(string line)
  891.         {
  892.             CheckDisposed();
  893.  
  894.             if (line.Length > 510)
  895.                 throw new ArgumentException(Properties.Resources.ErrorMessageLineTooLong, "line");
  896.  
  897.             messageSendQueue.Enqueue(line);
  898.         }
  899.  
  900.         private string CheckPrefix(string value)
  901.         {
  902.             if (value.Length == 0 || value.Any(IsInvalidMessageChar))
  903.             {
  904.                 throw new ArgumentException(string.Format(
  905.                     Properties.Resources.ErrorMessageInvalidPrefix, value), "value");
  906.             }
  907.  
  908.             return value;
  909.         }
  910.  
  911.         private string CheckCommand(string value)
  912.         {
  913.             if (value.Length == 0 || value.Any(IsInvalidMessageChar))
  914.             {
  915.                 throw new ArgumentException(string.Format(
  916.                     Properties.Resources.ErrorMessageInvalidCommand, value), "value");
  917.             }
  918.  
  919.             return value;
  920.         }
  921.  
  922.         private string CheckMiddleParameter(string value)
  923.         {
  924.             if (value.Length == 0 || value.Any(c => IsInvalidMessageChar(c)) || value[0] == ':')
  925.             {
  926.                 throw new ArgumentException(string.Format(
  927.                     Properties.Resources.ErrorMessageInvalidMiddleParameter, value), "value");
  928.             }
  929.  
  930.             return value;
  931.         }
  932.  
  933.         private string CheckTrailingParameter(string value)
  934.         {
  935.             if (value.Length == 0 || value.Any(c => IsInvalidMessageChar(c)))
  936.             {
  937.                 throw new ArgumentException(string.Format(
  938.                     Properties.Resources.ErrorMessageInvalidMiddleParameter, value), "value");
  939.             }
  940.  
  941.             return value;
  942.         }
  943.  
  944.         private bool IsInvalidMessageChar(char value)
  945.         {
  946.             return value == '\0' || value == '\r' || value == '\n';
  947.         }
  948.  
  949.         /// <inheritdoc cref="Connect(string, int, string, string, string, string, ICollection{char})"/>
  950.         public void Connect(string host, string password,
  951.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  952.         {
  953.             CheckDisposed();
  954.  
  955.             Connect(host, defaultPort, password, nickName, userName, realName, userMode);
  956.         }
  957.  
  958.         /// <inheritdoc cref="Connect(IPEndPoint, string, string, string, string, ICollection{char})"/>
  959.         /// <param name="host">The name of the remote host.</param>
  960.         /// <param name="port">The port number of the remote host.</param>
  961.         public void Connect(string host, int port, string password,
  962.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  963.         {
  964.             CheckDisposed();
  965.  
  966.             DisconnectInternal();
  967.             this.client.BeginConnect(host, port, ConnectCallback,
  968.                 CreateConnectState(password, nickName, userName, realName, userMode));
  969.             HandleClientConnecting();
  970.         }
  971.  
  972.         /// <inheritdoc cref="Connect(IPAddress, int, string, string, string, string, ICollection{char})"/>
  973.         public void Connect(IPAddress address, string password,
  974.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  975.         {
  976.             CheckDisposed();
  977.  
  978.             Connect(address, defaultPort, password, nickName, userName, realName, userMode);
  979.         }
  980.  
  981.         /// <inheritdoc cref="Connect(IPEndPoint, string, string, string, string, ICollection{char})"/>
  982.         /// <param name="address">An IP addresses that designates the remote host.</param>
  983.         /// <param name="port">The port number of the remote host.</param>
  984.         public void Connect(IPAddress address, int port, string password,
  985.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  986.         {
  987.             CheckDisposed();
  988.  
  989.             DisconnectInternal();
  990.             this.client.BeginConnect(address, port, ConnectCallback,
  991.                 CreateConnectState(password, nickName, userName, realName, userMode));
  992.             HandleClientConnecting();
  993.         }
  994.  
  995.         /// <inheritdoc cref="Connect(IPAddress[], string, string, string, string, ICollection{char})"/>
  996.         public void Connect(IPAddress[] addresses, string password,
  997.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  998.         {
  999.             CheckDisposed();
  1000.  
  1001.             Connect(addresses, defaultPort, password, nickName, userName, realName, userMode);
  1002.         }
  1003.  
  1004.         /// <inheritdoc cref="Connect(IPEndPoint, string, string, string, string, ICollection{char})"/>
  1005.         /// <param name="addresses">A collection of one or more IP addresses that designates the remote host.</param>
  1006.         /// <param name="port">The port number of the remote host.</param>
  1007.         public void Connect(IPAddress[] addresses, int port, string password,
  1008.             string nickName, string userName, string realName, ICollection<char> userMode = null)
  1009.         {
  1010.             CheckDisposed();
  1011.  
  1012.             DisconnectInternal();
  1013.             this.client.BeginConnect(addresses, port, ConnectCallback,
  1014.                 CreateConnectState(password, nickName, userName, realName, userMode));
  1015.             HandleClientConnecting();
  1016.         }
  1017.  
  1018.         /// <summary>
  1019.         /// Connects to a server using the specified host and user information.
  1020.         /// </summary>
  1021.         /// <param name="remoteEP">The network endpoint (IP address and port) of the server to which to connect.</param>
  1022.         /// <param name="password">The password to register with the server.</param>
  1023.         /// <param name="nickName">The nick name to register with the server. This can later be changed.</param>
  1024.         /// <param name="userName">The user name to register with the server.</param>
  1025.         /// <param name="realName">The real name to register with the server.</param>
  1026.         /// <param name="userMode">The initial user mode to register with the server. The value should not contain any
  1027.         /// character except 'w' or 'i'.</param>
  1028.         /// <exception cref="ObjectDisposedException">The object has already been been disposed.</exception>
  1029.         public void Connect(IPEndPoint remoteEP, string password, string nickName,
  1030.             string userName, string realName, ICollection<char> userMode = null)
  1031.         {
  1032.             CheckDisposed();
  1033.  
  1034.             DisconnectInternal();
  1035.             this.client.BeginConnect(remoteEP.Address, remoteEP.Port, ConnectCallback,
  1036.                 CreateConnectState(password, nickName, userName, realName, userMode));
  1037.             HandleClientConnecting();
  1038.         }
  1039.  
  1040.         private object CreateConnectState(string password, string nickName, string userName, string realName,
  1041.             ICollection<char> userMode)
  1042.         {
  1043.             CheckDisposed();
  1044.  
  1045.             if (nickName == null)
  1046.                 throw new ArgumentException(Properties.Resources.ErrorMessageInvalidNickName, "nickName");
  1047.             if (userName == null)
  1048.                 throw new ArgumentException(Properties.Resources.ErrorMessageInvalidNickName, "userName");
  1049.             if (realName == null)
  1050.                 throw new ArgumentException(Properties.Resources.ErrorMessageInvalidNickName, "realName");
  1051.  
  1052.             return new IrcConnectContext
  1053.                 {
  1054.                     Password = password,
  1055.                     NickName = nickName,
  1056.                     UserName = userName,
  1057.                     RealName = realName,
  1058.                     UserMode = userMode,
  1059.                 };
  1060.         }
  1061.  
  1062.         /// <summary>
  1063.         /// Disconnects immediately from the server. A quit message is sent if the connection is still active.
  1064.         /// </summary>
  1065.         /// <exception cref="ObjectDisposedException">The object has already been been disposed.</exception>
  1066.         public void Disconnect()
  1067.         {
  1068.             CheckDisposed();
  1069.             DisconnectInternal();
  1070.         }
  1071.  
  1072.         /// <summary>
  1073.         /// Disconnects from the server, regardless of whether the client object has already been disposed.
  1074.         /// </summary>
  1075.         protected void DisconnectInternal()
  1076.         {
  1077.             if (this.client != null && this.client.Client.Connected)
  1078.            {
  1079.                try
  1080.                {
  1081.                    SendMessageQuit();
  1082.                     this.client.Client.Disconnect(true);
  1083.                 }
  1084.                 catch (SocketException exSocket)
  1085.                 {
  1086.                     if (exSocket.SocketErrorCode != SocketError.NotConnected)
  1087.                         throw;
  1088.                 }
  1089.             }
  1090.  
  1091.             if (this.canDisconnect)
  1092.             {
  1093.                 this.canDisconnect = false;
  1094.                 OnDisconnected(new EventArgs());
  1095.                 HandleClientClosed();
  1096.             }
  1097.         }
  1098.  
  1099.         private void ConnectCallback(IAsyncResult ar)
  1100.         {
  1101.             try
  1102.             {
  1103.                 this.client.EndConnect(ar);
  1104.                 this.stream = this.client.GetStream();
  1105.                 this.writer = new StreamWriter(this.stream, Encoding.ASCII);
  1106.                 this.reader = new StreamReader(this.stream, Encoding.ASCII);
  1107.  
  1108.                 HandleClientConnected((IrcConnectContext)ar.AsyncState);
  1109.                 this.readThread.Start();
  1110.                 this.writeThread.Start();
  1111.  
  1112.                 OnConnected(new EventArgs());
  1113.             }
  1114.             catch (Exception ex)
  1115.             {
  1116.                 OnConnectFailed(new IrcErrorEventArgs(ex));
  1117.             }
  1118.         }
  1119.  
  1120.         private void HandleClientConnecting()
  1121.         {
  1122.             Debug.WriteLine("Connecting to server...");
  1123.  
  1124.             this.canDisconnect = true;
  1125.         }
  1126.  
  1127.         private void HandleClientConnected(IrcConnectContext initState)
  1128.         {
  1129.             Debug.WriteLine("Connected to server '{0}'.", ((IPEndPoint)this.client.Client.RemoteEndPoint).Address);
  1130.  
  1131.             try
  1132.             {
  1133.                 if (initState.Password != null)
  1134.                     SendMessagePassword(initState.Password);
  1135.                 SendMessageNick(initState.NickName);
  1136.                 SendMessageUser(initState.UserName, GetNumericUserMode(initState.UserMode), initState.RealName);
  1137.  
  1138.                 // Initialise local user and add it to collection.
  1139.                 this.localUser = new IrcLocalUser(initState.NickName, initState.UserName, initState.RealName,
  1140.                     initState.UserMode);
  1141.                 this.users.Add(this.localUser);
  1142.             }
  1143.             catch (Exception ex)
  1144.             {
  1145.                 OnError(new IrcErrorEventArgs(ex));
  1146.                 DisconnectInternal();
  1147.             }
  1148.         }
  1149.  
  1150.         private void HandleClientClosed()
  1151.         {
  1152.             Debug.WriteLine("Disconnected from server.");
  1153.  
  1154.             ResetState();
  1155.         }
  1156.  
  1157.         /// <summary>
  1158.         /// Extracts the nick name and user mode from the specified value.
  1159.         /// </summary>
  1160.         /// <param name="input">The input value, containing a nick name prefixed by a user mode.</param>
  1161.         /// <returns>A 2-tuple of the nick name and user mode.</returns>
  1162.         protected Tuple<string, string> ExtractUserMode(string input)
  1163.         {
  1164.             switch (input[0])
  1165.             {
  1166.                 case '@':
  1167.                     return Tuple.Create(input.Substring(1), "o");
  1168.                 case '+':
  1169.                     return Tuple.Create(input.Substring(1), "v");
  1170.                 default:
  1171.                     return Tuple.Create(input, string.Empty);
  1172.             }
  1173.         }
  1174.  
  1175.         /// <summary>
  1176.         /// Gets a collection of mode characters and mode parameters from the specified mode parameters.
  1177.         /// Combines multiple mode strings into a single mode string.
  1178.         /// </summary>
  1179.         /// <param name="messageParameters">A collection of message parameters, which consists of mode strings and mode
  1180.         /// parameters. A mode string is of the form `( "+" / "-" ) *( mode character )`, and specifies mode changes.
  1181.         /// A mode parameter is arbitrary text associated with a certain mode.</param>
  1182.         /// <returns>A 2-tuple of a single mode string and a collection of mode parameters.
  1183.         /// Each mode parameter corresponds to a single mode character, in the same order.</returns>
  1184.         protected Tuple<string, IEnumerable<string>> GetModeAndParameters(IEnumerable<string> messageParameters)
  1185.         {
  1186.             var modes = new StringBuilder();
  1187.             var modeParameters = new List<string>();
  1188.             foreach (var p in messageParameters)
  1189.             {
  1190.                 if (p == null)
  1191.                     break;
  1192.                 else if (p.Length == 0)
  1193.                     continue;
  1194.                 else if (p[0] == '+' || p[0] == '-')
  1195.                     modes.Append(p);
  1196.                 else
  1197.                     modeParameters.Add(p);
  1198.             }
  1199.             return Tuple.Create(modes.ToString(), (IEnumerable<string>)modeParameters.AsReadOnly());
  1200.         }
  1201.  
  1202.         /// <summary>
  1203.         /// Gets a list of channel objects from the specified comma-separated list of channel names.
  1204.         /// </summary>
  1205.         /// <param name="namesList">A value that contains a comma-separated list of names of channels.</param>
  1206.         /// <returns>A list of channel objects that corresponds to the given list of channel names.</returns>
  1207.         protected IEnumerable<IrcChannel> GetChannelsFromList(string namesList)
  1208.         {
  1209.             return namesList.Split(',').Select(n => GetChannelFromName(n));
  1210.         }
  1211.  
  1212.         /// <summary>
  1213.         /// Gets a list of user objedcts from the specified comma-separated list of nick names.
  1214.         /// </summary>
  1215.         /// <param name="nickNamesList">A value that contains a comma-separated list of nick names of users.</param>
  1216.         /// <returns>A list of user objects that corresponds to the given list of nick names.</returns>
  1217.         protected IEnumerable<IrcUser> GetUsersFromList(string nickNamesList)
  1218.         {
  1219.             return nickNamesList.Split(',').Select(n => this.users.Single(u => u.NickName == n));
  1220.         }
  1221.  
  1222.         /// <summary>
  1223.         /// Determines whether the specified name refers to a channel.
  1224.         /// </summary>
  1225.         /// <param name="name">The name to check.</param>
  1226.         /// <returns><see langword="true"/> if the specified name represents a channel; <see langword="false"/>,
  1227.         /// otherwise.</returns>
  1228.         protected bool IsChannelName(string name)
  1229.         {
  1230.             return Regex.IsMatch(name, regexChannelName);
  1231.         }
  1232.  
  1233.         /// <summary>
  1234.         /// Gets the type of the channel from the specified character.
  1235.         /// </summary>
  1236.         /// <param name="type">A character that represents the type of the channel.
  1237.         /// The character may be one of the following:
  1238.         /// <list type="bullet">
  1239.         ///     <listheader>
  1240.         ///         <term>Character</term>
  1241.         ///         <description>Channel type</description>
  1242.         ///     </listheader>
  1243.         ///     <item>
  1244.         ///         <term>=</term>
  1245.         ///         <description>Public channel</description>
  1246.         ///     </item>
  1247.         ///     <item>
  1248.         ///         <term>*</term>
  1249.         ///         <description>Private channel</description>
  1250.         ///     </item>
  1251.         ///     <item>
  1252.         ///         <term>@</term>
  1253.         ///         <description>Secret channel</description>
  1254.         ///     </item>
  1255.         /// </list></param>
  1256.         /// <returns>The channel type that corresponds to the specified character.</returns>
  1257.         /// <exception cref="ArgumentException"><paramref name="type"/> does not correspond to any known channel type.
  1258.         /// </exception>
  1259.         protected IrcChannelType GetChannelType(char type)
  1260.         {
  1261.             switch (type)
  1262.             {
  1263.                 case '=':
  1264.                     return IrcChannelType.Public;
  1265.                 case '*':
  1266.                     return IrcChannelType.Private;
  1267.                 case '@':
  1268.                     return IrcChannelType.Secret;
  1269.                 default:
  1270.                     throw new ArgumentException(string.Format(
  1271.                         Properties.Resources.ErrorMessageInvalidChannelType, type), "type");
  1272.             }
  1273.         }
  1274.  
  1275.         /// <summary>
  1276.         /// Gets the target of a message from the specified name.
  1277.         /// A message target may be an <see cref="IrcUser"/>, <see cref="IrcChannel"/>, or <see cref="IrcTargetMask"/>.
  1278.         /// </summary>
  1279.         /// <param name="targetName">The name of the target.</param>
  1280.         /// <returns>The target object that corresponds to the given name. The object is an instance of
  1281.         /// <see cref="IrcUser"/>, <see cref="IrcChannel"/>, or <see cref="IrcTargetMask"/>.</returns>
  1282.         /// <exception cref="ArgumentException"><paramref name="targetName"/> does not represent a valid message target.
  1283.         /// </exception>
  1284.         protected IIrcMessageTarget GetMessageTarget(string targetName)
  1285.         {
  1286.             Debug.Assert(targetName.Length > 0);
  1287.  
  1288.             // Check whether target name represents channel, user, or target mask.
  1289.             var targetNameMatch = Regex.Match(targetName, regexMessageTarget);
  1290.             var channelName = targetNameMatch.Groups["channel"].GetValue();
  1291.             var nickName = targetNameMatch.Groups["nick"].GetValue();
  1292.             var userName = targetNameMatch.Groups["user"].GetValue();
  1293.             var hostName = targetNameMatch.Groups["host"].GetValue();
  1294.             var serverName = targetNameMatch.Groups["server"].GetValue();
  1295.             var targetMask = targetNameMatch.Groups["targetMask"].GetValue();
  1296.             if (channelName != null)
  1297.             {
  1298.                 return GetChannelFromName(channelName);
  1299.             }
  1300.             else if (nickName != null)
  1301.             {
  1302.                 // Find user by nick name. If no user exists in list, create it and set its properties.
  1303.                 bool createdNew;
  1304.                 var user = GetUserFromNickName(nickName, true, out createdNew);
  1305.                 if (createdNew)
  1306.                 {
  1307.                     user.UserName = userName;
  1308.                     user.HostName = hostName;
  1309.                 }
  1310.                 return user;
  1311.             }
  1312.             else if (userName != null)
  1313.             {
  1314.                 // Find user by user  name. If no user exists in list, create it and set its properties.
  1315.                 bool createdNew;
  1316.                 var user = GetUserFromNickName(nickName, true, out createdNew);
  1317.                 if (createdNew)
  1318.                 {
  1319.                     user.HostName = hostName;
  1320.                 }
  1321.                 return user;
  1322.             }
  1323.             else if (targetMask != null)
  1324.             {
  1325.                 return new IrcTargetMask(targetMask);
  1326.             }
  1327.             else
  1328.             {
  1329.                 throw new ArgumentException(string.Format(
  1330.                     Properties.Resources.ErrorMessageInvalidSource, targetName), "targetName");
  1331.             }
  1332.         }
  1333.  
  1334.         /// <summary>
  1335.         /// Gets the source of a message from the specified prefix.
  1336.         /// A message source may be a <see cref="IrcUser"/> or <see cref="IrcServer"/>.
  1337.         /// </summary>
  1338.         /// <param name="prefix">The raw prefix of the message.</param>
  1339.         /// <returns>The message source that corresponds to the specified prefix. The object is an instance of
  1340.         /// <see cref="IrcUser"/> or <see cref="IrcServer"/>.</returns>
  1341.         /// <exception cref="ArgumentException"><paramref name="prefix"/> does not represent a valid message source.
  1342.         /// </exception>
  1343.         protected IIrcMessageSource GetSourceFromPrefix(string prefix)
  1344.         {
  1345.             if (prefix == null)
  1346.                 return null;
  1347.             Debug.Assert(prefix.Length > 0);
  1348.  
  1349.             // Check whether prefix represents server or user.
  1350.             var prefixMatch = Regex.Match(prefix, regexMessagePrefix);
  1351.             var serverName = prefixMatch.Groups["server"].GetValue();
  1352.             var nickName = prefixMatch.Groups["nick"].GetValue();
  1353.             var userName = prefixMatch.Groups["user"].GetValue();
  1354.             var hostName = prefixMatch.Groups["host"].GetValue();
  1355.             if (serverName != null)
  1356.             {
  1357.                 return GetServerFromHostName(serverName);
  1358.             }
  1359.             else if (nickName != null)
  1360.             {
  1361.                 // Find user by nick name. If no user exists in list, create it and set its properties.
  1362.                 bool createdNew;
  1363.                 var user = GetUserFromNickName(nickName, true, out createdNew);
  1364.                 if (createdNew)
  1365.                 {
  1366.                     user.UserName = userName;
  1367.                     user.HostName = hostName;
  1368.                 }
  1369.                 return user;
  1370.             }
  1371.             else
  1372.             {
  1373.                 throw new ArgumentException(string.Format(
  1374.                     Properties.Resources.ErrorMessageInvalidSource, prefix), "prefix");
  1375.             }
  1376.         }
  1377.  
  1378.         /// <inheritdoc cref="GetServerFromHostName(string, out bool)"/>
  1379.         protected IrcServer GetServerFromHostName(string hostName)
  1380.         {
  1381.             bool createdNew;
  1382.             return GetServerFromHostName(hostName, out createdNew);
  1383.         }
  1384.  
  1385.         /// <summary>
  1386.         /// Gets the server with the specified host name, creating it if necessary.
  1387.         /// </summary>
  1388.         /// <param name="hostName">The host name of the server.</param>
  1389.         /// <param name="createdNew"><see langword="true"/> if the server object was created during the call;
  1390.         /// <see langword="false"/>, otherwise.</param>
  1391.         /// <returns>The server object that corresponds to the specified host name.</returns>
  1392.         protected IrcServer GetServerFromHostName(string hostName, out bool createdNew)
  1393.         {
  1394.             // Search for server  with given name in list of known servers. If it does not exist, add it.
  1395.             var server = this.servers.SingleOrDefault(s => s.HostName == hostName);
  1396.             if (server == null)
  1397.             {
  1398.                 server = new IrcServer(hostName);
  1399.                 this.servers.Add(server);
  1400.                 createdNew = true;
  1401.             }
  1402.             else
  1403.             {
  1404.                 createdNew = false;
  1405.             }
  1406.             return server;
  1407.         }
  1408.  
  1409.         /// <inheritdoc cref="GetChannelFromName(string, out bool)"/>
  1410.         protected IrcChannel GetChannelFromName(string channelName)
  1411.         {
  1412.             bool createdNew;
  1413.             return GetChannelFromName(channelName, out createdNew);
  1414.         }
  1415.  
  1416.         /// <summary>
  1417.         /// Gets the channel with the specified name, creating it if necessary.
  1418.         /// </summary>
  1419.         /// <param name="channelName">The name of the channel.</param>
  1420.         /// <param name="createdNew"><see langword="true"/> if the channel object was created during the call;
  1421.         /// <see langword="false"/>, otherwise.</param>
  1422.         /// <returns>The channel object that corresponds to the specified name.</returns>
  1423.         protected IrcChannel GetChannelFromName(string channelName, out bool createdNew)
  1424.         {
  1425.             // Search for channel with given name in list of known channel. If it does not exist, add it.
  1426.             var channel = this.channels.SingleOrDefault(c => c.Name == channelName);
  1427.             if (channel == null)
  1428.             {
  1429.                 channel = new IrcChannel(channelName);
  1430.                 this.channels.Add(channel);
  1431.                 createdNew = true;
  1432.             }
  1433.             else
  1434.             {
  1435.                 createdNew = false;
  1436.             }
  1437.             return channel;
  1438.         }
  1439.  
  1440.         /// <inheritdoc cref="GetUserFromNickName(string, bool, out bool)"/>
  1441.         protected IrcUser GetUserFromNickName(string nickName, bool isOnline = true)
  1442.         {
  1443.             bool createdNew;
  1444.             return GetUserFromNickName(nickName, isOnline, out createdNew);
  1445.         }
  1446.  
  1447.         /// <summary>
  1448.         /// Gets the user with the specified nick name, creating it if necessary.
  1449.         /// </summary>
  1450.         /// <param name="nickName">The nick name of the user.</param>
  1451.         /// <param name="isOnline"><see langword="true"/> if the user is currently online;
  1452.         /// <see langword="false"/>, if the user is currently offline.
  1453.         /// The <see cref="IrcUser.IsOnline"/> property of the user object is set to this value.</param>
  1454.         /// <param name="createdNew"><see langword="true"/> if the user object was created during the call;
  1455.         /// <see langword="false"/>, otherwise.</param>
  1456.         /// <returns>The user object that corresponds to the specified nick name.</returns>
  1457.         protected IrcUser GetUserFromNickName(string nickName, bool isOnline, out bool createdNew)
  1458.         {
  1459.             // Search for user with given nick name in list of known users. If it does not exist, add it.
  1460.             var user = this.users.SingleOrDefault(u => u.NickName == nickName);
  1461.             if (user == null)
  1462.             {
  1463.                 user = new IrcUser();
  1464.                 user.NickName = nickName;
  1465.                 this.users.Add(user);
  1466.                 createdNew = true;
  1467.             }
  1468.             else
  1469.             {
  1470.                 createdNew = false;
  1471.             }
  1472.             user.IsOnline = isOnline;
  1473.             return user;
  1474.         }
  1475.  
  1476.         /// <inheritdoc cref="GetUserFromUserName(string, out bool)"/>
  1477.         protected IrcUser GetUserFromUserName(string userName)
  1478.         {
  1479.             bool createdNew;
  1480.             return GetUserFromUserName(userName, out createdNew);
  1481.         }
  1482.  
  1483.         /// <summary>
  1484.         /// Gets the user with the specified user name, creating it if necessary.
  1485.         /// </summary>
  1486.         /// <param name="userName">The user name of the user.</param>
  1487.         /// <param name="createdNew"><see langword="true"/> if the user object was created during the call;
  1488.         /// <see langword="false"/>, otherwise.</param>
  1489.         /// <returns>The user object that corresponds to the specified user name.</returns>
  1490.         protected IrcUser GetUserFromUserName(string userName, out bool createdNew)
  1491.         {
  1492.             // Search for user with given nick name in list of known users. If it does not exist, add it.
  1493.             var user = this.users.SingleOrDefault(u => u.UserName == userName);
  1494.             if (user == null)
  1495.             {
  1496.                 user = new IrcUser();
  1497.                 user.UserName = userName;
  1498.                 this.users.Add(user);
  1499.                 createdNew = true;
  1500.             }
  1501.             else
  1502.             {
  1503.                 createdNew = false;
  1504.             }
  1505.             return user;
  1506.         }
  1507.  
  1508.         private int GetNumericUserMode(ICollection<char> mode)
  1509.         {
  1510.             var value = 0;
  1511.             if (mode == null)
  1512.                 return value;
  1513.             if (mode.Contains('w'))
  1514.                 value |= 0x02;
  1515.             if (mode.Contains('i'))
  1516.                 value |= 0x04;
  1517.             return value;
  1518.         }
  1519.  
  1520.         private void ResetState()
  1521.         {
  1522.             this.servers = new Collection<IrcServer>();
  1523.             this.isRegistered = false;
  1524.             this.localUser = null;
  1525.             this.serverSupportedFeatures = new Dictionary<string, string>();
  1526.             this.serverSupportedFeaturesReadOnly = new ReadOnlyDictionary<string, string>(this.serverSupportedFeatures);
  1527.             this.motdBuilder = new StringBuilder();
  1528.             this.channels = new ObservableCollection<IrcChannel>();
  1529.             this.channelsReadOnly = new IrcChannelCollection(this, this.channels);
  1530.             this.users = new ObservableCollection<IrcUser>();
  1531.             this.usersReadOnly = new IrcUserCollection(this, this.users);
  1532.         }
  1533.  
  1534.         /// <summary>
  1535.         /// Throws an exception if the object has been dispoed; otherwise, simply returns immediately.
  1536.         /// </summary>
  1537.         /// <exception cref="ObjectDisposedException">The object has already been disposed.</exception>
  1538.         protected void CheckDisposed()
  1539.         {
  1540.             if (this.isDisposed)
  1541.                 throw new ObjectDisposedException(GetType().FullName);
  1542.         }
  1543.  
  1544.         /// <summary>
  1545.         /// Raises the <see cref="Connected"/> event.
  1546.         /// </summary>
  1547.         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
  1548.         protected virtual void OnConnected(EventArgs e)
  1549.         {
  1550.             var handler = this.Connected;
  1551.             if (handler != null)
  1552.                 handler(this, e);
  1553.         }
  1554.  
  1555.         /// <summary>
  1556.         /// Raises the <see cref="ConnectFailed"/> event.
  1557.         /// </summary>
  1558.         /// <param name="e">The <see cref="IrcErrorEventArgs"/> instance containing the event data.</param>
  1559.         protected virtual void OnConnectFailed(IrcErrorEventArgs e)
  1560.         {
  1561.             var handler = this.ConnectFailed;
  1562.             if (handler != null)
  1563.                 handler(this, e);
  1564.         }
  1565.  
  1566.         /// <summary>
  1567.         /// Raises the <see cref="Disconnected"/> event.
  1568.         /// </summary>
  1569.         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
  1570.         protected virtual void OnDisconnected(EventArgs e)
  1571.         {
  1572.             var handler = this.Disconnected;
  1573.             if (handler != null)
  1574.                 handler(this, e);
  1575.         }
  1576.  
  1577.         /// <summary>
  1578.         /// Raises the <see cref="Error"/> event.
  1579.         /// </summary>
  1580.         /// <param name="e">The <see cref="IrcErrorEventArgs"/> instance containing the event data.</param>
  1581.         protected virtual void OnError(IrcErrorEventArgs e)
  1582.         {
  1583.             var handler = this.Error;
  1584.             if (handler != null)
  1585.                 handler(this, e);
  1586.         }
  1587.  
  1588.         /// <summary>
  1589.         /// Raises the <see cref="ProtocolError"/> event.
  1590.         /// </summary>
  1591.         /// <param name="e">The <see cref="IrcProtocolErrorEventArgs"/> instance containing the event data.</param>
  1592.         protected virtual void OnProtocolError(IrcProtocolErrorEventArgs e)
  1593.         {
  1594.             var handler = this.ProtocolError;
  1595.             if (handler != null)
  1596.                 handler(this, e);
  1597.         }
  1598.  
  1599.         /// <summary>
  1600.         /// Raises the <see cref="ErrorMessageReceived"/> event.
  1601.         /// </summary>
  1602.         /// <param name="e">The <see cref="IrcErrorMessageEventArgs"/> instance containing the event data.</param>
  1603.         protected virtual void OnErrorMessageReceived(IrcErrorMessageEventArgs e)
  1604.         {
  1605.             var handler = this.ErrorMessageReceived;
  1606.             if (handler != null)
  1607.                 handler(this, e);
  1608.         }
  1609.  
  1610.         /// <summary>
  1611.         /// Raises the <see cref="Registered"/> event.
  1612.         /// </summary>
  1613.         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
  1614.         protected virtual void OnRegistered(EventArgs e)
  1615.         {
  1616.             var handler = this.Registered;
  1617.             if (handler != null)
  1618.                 handler(this, e);
  1619.         }
  1620.  
  1621.         /// <summary>
  1622.         /// Raises the <see cref="ServerBounce"/> event.
  1623.         /// </summary>
  1624.         /// <param name="e">The <see cref="IrcServerInfoEventArgs"/> instance containing the event data.</param>
  1625.         protected virtual void OnServerBounce(IrcServerInfoEventArgs e)
  1626.         {
  1627.             var handler = this.ServerBounce;
  1628.             if (handler != null)
  1629.                 handler(this, e);
  1630.         }
  1631.  
  1632.         /// <summary>
  1633.         /// Raises the <see cref="ServerSupportedFeaturesReceived"/> event.
  1634.         /// </summary>
  1635.         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
  1636.         protected virtual void OnServerSupportedFeaturesReceived(EventArgs e)
  1637.         {
  1638.             var handler = this.ServerSupportedFeaturesReceived;
  1639.             if (handler != null)
  1640.                 handler(this, e);
  1641.         }
  1642.  
  1643.         /// <summary>
  1644.         /// Raises the <see cref="PingReceived"/> event.
  1645.         /// </summary>
  1646.         /// <param name="e">The <see cref="IrcPingOrPongReceivedEventArgs"/> instance containing the event data.</param>
  1647.         protected virtual void OnPingReceived(IrcPingOrPongReceivedEventArgs e)
  1648.         {
  1649.             var handler = this.PingReceived;
  1650.             if (handler != null)
  1651.                 handler(this, e);
  1652.         }
  1653.  
  1654.         /// <summary>
  1655.         /// Raises the <see cref="PongReceived"/> event.
  1656.         /// </summary>
  1657.         /// <param name="e">The <see cref="IrcPingOrPongReceivedEventArgs"/> instance containing the event data.</param>
  1658.         protected virtual void OnPongReceived(IrcPingOrPongReceivedEventArgs e)
  1659.         {
  1660.             var handler = this.PongReceived;
  1661.             if (handler != null)
  1662.                 handler(this, e);
  1663.         }
  1664.  
  1665.         /// <summary>
  1666.         /// Raises the <see cref="MotdReceived"/> event.
  1667.         /// </summary>
  1668.         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
  1669.         protected virtual void OnMotdReceived(EventArgs e)
  1670.         {
  1671.             var handler = this.MotdReceived;
  1672.             if (handler != null)
  1673.                 handler(this, e);
  1674.         }
  1675.  
  1676.         /// <summary>
  1677.         /// Raises the <see cref="WhoReplyReceived"/> event.
  1678.         /// </summary>
  1679.         /// <param name="e">The <see cref="IrcNameEventArgs"/> instance containing the event data.</param>
  1680.         protected virtual void OnWhoReplyReceived(IrcNameEventArgs e)
  1681.         {
  1682.             var handler = this.WhoReplyReceived;
  1683.             if (handler != null)
  1684.                 handler(this, e);
  1685.         }
  1686.  
  1687.         /// <summary>
  1688.         /// Raises the <see cref="WhoIsReplyReceived"/> event.
  1689.         /// </summary>
  1690.         /// <param name="e">The <see cref="IrcUserEventArgs"/> instance containing the event data.</param>
  1691.         protected virtual void OnWhoIsReplyReceived(IrcUserEventArgs e)
  1692.         {
  1693.             var handler = this.WhoIsReplyReceived;
  1694.             if (handler != null)
  1695.                 handler(this, e);
  1696.         }
  1697.  
  1698.         /// <summary>
  1699.         /// Raises the <see cref="WhoWasReplyReceived"/> event.
  1700.         /// </summary>
  1701.         /// <param name="e">The <see cref="IrcUserEventArgs"/> instance containing the event data.</param>
  1702.         protected virtual void OnWhoWasReplyReceived(IrcUserEventArgs e)
  1703.         {
  1704.             var handler = this.WhoWasReplyReceived;
  1705.             if (handler != null)
  1706.                 handler(this, e);
  1707.         }
  1708.  
  1709.         /// <summary>
  1710.         /// Represents a method that processes <see cref="IrcMessage"/>s.
  1711.         /// </summary>
  1712.         /// <param name="message">The message that the method should process.</param>
  1713.         protected delegate void MessageProcessor(IrcMessage message);
  1714.  
  1715.         /// <summary>
  1716.         /// Indicates that a method processes <see cref="IrcMessage"/>s for a given command.
  1717.         /// </summary>
  1718.         protected class MessageProcessorAttribute : Attribute
  1719.         {
  1720.             /// <summary>
  1721.             /// Initializes a new instance of the <see cref="MessageProcessorAttribute"/> class.
  1722.             /// </summary>
  1723.             /// <param name="command">The name of the command for which messages are processed.</param>
  1724.             public MessageProcessorAttribute(string command)
  1725.             {
  1726.                 this.Command = command;
  1727.             }
  1728.  
  1729.             /// <summary>
  1730.             /// Gets the name of the command for which messages are processed.
  1731.             /// </summary>
  1732.             /// <value>The command name.</value>
  1733.             public string Command
  1734.             {
  1735.                 get;
  1736.                 private set;
  1737.             }
  1738.         }
  1739.  
  1740.         /// <summary>
  1741.         /// Represents a message that is sent/received by the client/server. A message contains a prefix (representing
  1742.         /// the source), a command name (a word or three-digit number), and an arbitrary number of parameters (up to a
  1743.         /// maximum of 15).
  1744.         /// </summary>
  1745.         protected struct IrcMessage
  1746.         {
  1747.             /// <summary>
  1748.             /// The source of the message, which is the object represented by <see cref="Prefix"/>.
  1749.             /// </summary>
  1750.             public IIrcMessageSource Source;
  1751.  
  1752.             /// <summary>
  1753.             /// The message prefix.
  1754.             /// </summary>
  1755.             public string Prefix;
  1756.             /// <summary>
  1757.             /// The name of the command.
  1758.             /// </summary>
  1759.             public string Command;
  1760.             /// <summary>
  1761.             /// A list of the parameters to the message.
  1762.             /// </summary>
  1763.             public IList<string> Parameters;
  1764.  
  1765.             /// <summary>
  1766.             /// Initializes a new instance of the <see cref="IrcMessage"/> struct.
  1767.             /// </summary>
  1768.             /// <param name="client">A client object that has sent/will received the message.</param>
  1769.             /// <param name="prefix">The message prefix, which represents the source of the message.</param>
  1770.             /// <param name="command">The command name; either a word or 3-digit number.</param>
  1771.             /// <param name="parameters">A lisit of the parameters to the message, containing a maximum of 15 items.
  1772.             /// </param>
  1773.             public IrcMessage(IrcClient client, string prefix, string command, IList<string> parameters)
  1774.             {
  1775.                 this.Prefix = prefix;
  1776.                 this.Command = command;
  1777.                 this.Parameters = parameters;
  1778.  
  1779.                 this.Source = client.GetSourceFromPrefix(prefix);
  1780.             }
  1781.         }
  1782.  
  1783.         private struct IrcConnectContext
  1784.         {
  1785.             public string Password;
  1786.             public string UserName;
  1787.             public string NickName;
  1788.             public string RealName;
  1789.             public ICollection<char> UserMode;
  1790.         }
  1791.     }
  1792. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement