Import TechBot
[reactos.git] / irc / TechBot / TechBot.IRCLibrary / IrcMessage.cs
diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs b/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs
new file mode 100644 (file)
index 0000000..bab64de
--- /dev/null
@@ -0,0 +1,503 @@
+using System;\r
+\r
+namespace TechBot.IRCLibrary\r
+{\r
+       /*\r
+       <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>\r
+       <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]\r
+       <command>  ::= <letter> { <letter> } | <number> <number> <number>\r
+       <SPACE>    ::= ' ' { ' ' }\r
+       <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]\r
+\r
+       <middle>   ::= <Any *non-empty* sequence of octets not including SPACE\r
+                                  or NUL or CR or LF, the first of which may not be ':'>\r
+       <trailing> ::= <Any, possibly *empty*, sequence of octets not including\r
+                                        NUL or CR or LF>\r
+\r
+       <crlf>     ::= CR LF\r
+\r
+       NOTES:\r
+\r
+         1)    <SPACE> is consists only of SPACE character(s) (0x20).\r
+                       Specially notice that TABULATION, and all other control\r
+                       characters are considered NON-WHITE-SPACE.\r
+\r
+         2)    After extracting the parameter list, all parameters are equal,\r
+                       whether matched by <middle> or <trailing>. <Trailing> is just\r
+                       a syntactic trick to allow SPACE within parameter.\r
+\r
+         3)    The fact that CR and LF cannot appear in parameter strings is\r
+                       just artifact of the message framing. This might change later.\r
+\r
+         4)    The NUL character is not special in message framing, and\r
+                       basically could end up inside a parameter, but as it would\r
+                       cause extra complexities in normal C string handling. Therefore\r
+                       NUL is not allowed within messages.\r
+\r
+         5)    The last parameter may be an empty string.\r
+\r
+         6)    Use of the extended prefix (['!' <user> ] ['@' <host> ]) must\r
+                       not be used in server to server communications and is only\r
+                       intended for server to client messages in order to provide\r
+                       clients with more useful information about who a message is\r
+                       from without the need for additional queries.\r
+        */\r
+       /*\r
+   NOTICE AUTH :*** Looking up your hostname\r
+   NOTICE AUTH :*** Checking Ident\r
+   NOTICE AUTH :*** Found your hostname\r
+   NOTICE AUTH :*** No ident response\r
+        */\r
+\r
+       /// <summary>\r
+       /// IRC message.\r
+       /// </summary>\r
+       public class IrcMessage\r
+       {\r
+               #region Private fields\r
+               private string line;\r
+               private string prefix;\r
+               private string prefixServername;\r
+               private string prefixNickname;\r
+               private string prefixUser;\r
+               private string prefixHost;\r
+               private string command;\r
+               private string parameters;\r
+               #endregion\r
+\r
+               /// <summary>\r
+               /// Line of text that is to be parsed as an IRC message.\r
+               /// </summary>\r
+               public string Line\r
+               {\r
+                       get\r
+                       {\r
+                               return line;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Does the message have a prefix?\r
+               /// </summary>\r
+               public bool HasPrefix\r
+               {\r
+                       get\r
+                       {\r
+                               return prefix != null;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Prefix or null if none exists.\r
+               /// </summary>\r
+               public string Prefix\r
+               {\r
+                       get\r
+                       {\r
+                               return prefix;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Servername part of prefix or null if no prefix or servername exists.\r
+               /// </summary>\r
+               public string PrefixServername\r
+               {\r
+                       get\r
+                       {\r
+                               return prefixServername;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Nickname part of prefix or null if no prefix or nick exists.\r
+               /// </summary>\r
+               public string PrefixNickname\r
+               {\r
+                       get\r
+                       {\r
+                               return prefixNickname;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// User part of (extended) prefix or null if no (extended) prefix exists.\r
+               /// </summary>\r
+               public string PrefixUser\r
+               {\r
+                       get\r
+                       {\r
+                               return prefixUser;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Host part of (extended) prefix or null if no (extended) prefix exists.\r
+               /// </summary>\r
+               public string PrefixHost\r
+               {\r
+                       get\r
+                       {\r
+                               return prefixHost;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Command part of message.\r
+               /// </summary>\r
+               public string Command\r
+               {\r
+                       get\r
+                       {\r
+                               return command;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Is command numeric?\r
+               /// </summary>\r
+               public bool IsCommandNumeric\r
+               {\r
+                       get\r
+                       {\r
+                               if (command == null || command.Length != 3)\r
+                               {\r
+                                       return false;\r
+                               }\r
+                               try\r
+                               {\r
+                                       Int32.Parse(command);\r
+                                       return true;\r
+                               }\r
+                               catch (Exception)\r
+                               {\r
+                                       return false;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Command part of message as text.\r
+               /// </summary>\r
+               public string CommandText\r
+               {\r
+                       get\r
+                       {\r
+                               return command;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Command part of message as a number.\r
+               /// </summary>\r
+               /// <exception cref="InvalidOperationException">Thrown if IsCommandNumeric returns false.</exception>\r
+               public int CommandNumber\r
+               {\r
+                       get\r
+                       {\r
+                               if (IsCommandNumeric)\r
+                               {\r
+                                       return Int32.Parse(command);\r
+                               }\r
+                               else\r
+                               {\r
+                                       throw new InvalidOperationException();\r
+                               }\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Parameters part of message.\r
+               /// </summary>\r
+               public string Parameters\r
+               {\r
+                       get\r
+                       {\r
+                               return parameters;\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Constructor.\r
+               /// </summary>\r
+               /// <param name="line">Line of text that is to be parsed as an IRC message.</param>\r
+               public IrcMessage(string line)\r
+               {\r
+                       /*\r
+                        * <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>\r
+                        * <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]\r
+                        * :Oslo1.NO.EU.undernet.org 461 MYNICK USER :Not enough parameters\r
+                        */\r
+                       try\r
+                       {\r
+                               this.line = line;\r
+                               int i = 0;\r
+\r
+                               #region Prefix\r
+                               if (line[i].Equals(':'))\r
+                               {\r
+                                       i++;\r
+                                       prefix = "";\r
+                                       /* This message has a prefix */\r
+                                       string s = "";\r
+                                       while (i < line.Length && line[i] != ' ' && line[i] != '!' && line[i] != '@')\r
+                                       {\r
+                                               s += line[i++];\r
+                                       }\r
+                                       if (IsValidIrcNickname(s))\r
+                                       {\r
+                                               prefixNickname = s;\r
+                                               prefix += prefixNickname;\r
+                                               if (line[i] == '!')\r
+                                               {\r
+                                                       /* This message has an extended prefix */\r
+                                                       i++;\r
+                                                       s = "";\r
+                                                       while (i < line.Length && line[i] != ' ' && line[i] != '@')\r
+                                                       {\r
+                                                               s += line[i];\r
+                                                               i++;\r
+                                                       }\r
+                                                       prefixUser = s;\r
+                                                       prefix += "!" + prefixUser;\r
+                                               }\r
+                                               if (line[i] == '@')\r
+                                               {\r
+                                                       /* This message has a host prefix */\r
+                                                       s = "";\r
+                                                       do\r
+                                                       {\r
+                                                               s += line[++i];\r
+                                                       }\r
+                                                       while (i < line.Length && line[i] != ' ');\r
+                                                       prefixHost = s;\r
+                                                       prefix += "@" + prefixHost;\r
+                                               }\r
+                                       }\r
+                                       else /* Assume it is a servername */\r
+                                       {\r
+                                               prefixServername = s;\r
+                                               prefix += prefixServername;\r
+                                       }\r
+\r
+                                       /* Skip spaces */\r
+                                       while (i < line.Length && line[i] == ' ')\r
+                                       {\r
+                                               i++;\r
+                                       }\r
+                               }\r
+                               else\r
+                               {\r
+                                       prefix = null;\r
+                               }\r
+                               #endregion\r
+\r
+                               #region Command\r
+                               if (Char.IsDigit(line[i]))\r
+                               {\r
+                                       if (!Char.IsDigit(line, i + 1) || !Char.IsDigit(line, i + 2))\r
+                                       {\r
+                                               throw new Exception();\r
+                                       }\r
+                                       command = String.Format("{0}{1}{2}", line[i++], line[i++], line[i++]);\r
+                               }\r
+                               else\r
+                               {\r
+                                       command = "";\r
+                                       while (i < line.Length && Char.IsLetter(line[i]))\r
+                                       {\r
+                                               command += line[i];\r
+                                               i++;\r
+                                       }\r
+                               }\r
+                               #endregion\r
+\r
+                               #region Parameters\r
+                               while (true)\r
+                               {\r
+                                       /* Skip spaces */\r
+                                       while (i < line.Length && line[i] == ' ')\r
+                                       {\r
+                                               i++;\r
+                                       }\r
+                                       if (i < line.Length && line[i].Equals(':'))\r
+                                       {\r
+                                               i++;\r
+\r
+                                               /* Trailing */\r
+                                               while (i < line.Length && line[i] != ' ' && line[i] != '\r' && line[i] != '\n' && line[i] != 0)\r
+                                               {\r
+                                                       if (parameters == null)\r
+                                                       {\r
+                                                               parameters = "";\r
+                                                       }\r
+                                                       parameters += line[i];\r
+                                                       i++;\r
+                                               }\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               /* Middle */\r
+                                               while (i < line.Length && line[i] != '\r' && line[i] != '\n' && line[i] != 0)\r
+                                               {\r
+                                                       if (parameters == null)\r
+                                                       {\r
+                                                               parameters = "";\r
+                                                       }\r
+                                                       parameters += line[i];\r
+                                                       i++;\r
+                                               }\r
+                                       }\r
+                                       if (i >= line.Length)\r
+                                       {\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               #endregion\r
+                       }\r
+                       catch (Exception ex)\r
+                       {\r
+                               throw new MalformedMessageException("The message is malformed.", ex);\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Constructor.\r
+               /// </summary>\r
+               /// <param name="prefixServername"></param>\r
+               /// <param name="prefixNickname"></param>\r
+               /// <param name="prefixUser"></param>\r
+               /// <param name="prefixHost"></param>\r
+               /// <param name="command"></param>\r
+               /// <param name="parameters"></param>\r
+               public IrcMessage(string prefixServername,\r
+                       string prefixNickname,\r
+                       string prefixUser,\r
+                       string prefixHost,\r
+                       string command,\r
+                       string parameters)\r
+               {\r
+                       throw new NotImplementedException();\r
+               }\r
+\r
+               /// <summary>\r
+               /// Constructor.\r
+               /// </summary>\r
+               /// <param name="command">IRC command.</param>\r
+               /// <param name="parameters">IRC command parameters. May be null if there are no parameters.</param>\r
+               public IrcMessage(string command,\r
+                       string parameters)\r
+               {\r
+                       if (command == null || !IsValidIrcCommand(command))\r
+                       {\r
+                               throw new ArgumentException("Command is not a valid IRC command.", "command");\r
+                       }\r
+                       /* An IRC message must not be longer than 512 characters (including terminating CRLF) */\r
+                       int parametersLength = (parameters != null) ? 1 + parameters.Length : 0;\r
+                       if (command.Length + parametersLength > 510)\r
+                       {\r
+                               throw new MalformedMessageException("IRC message cannot be longer than 512 characters.");\r
+                       }\r
+                       this.command = command;\r
+                       this.parameters = parameters;\r
+                       if (parameters != null)\r
+                       {\r
+                               this.line = String.Format("{0} {1}\r\n", command, parameters);\r
+                       }\r
+                       else\r
+                       {\r
+                               this.line = String.Format("{0}\r\n", command);\r
+                       }\r
+               }\r
+\r
+               /// <summary>\r
+               /// Returns wether a string of text is a valid IRC command.\r
+               /// </summary>\r
+               /// <param name="command">The IRC command.</param>\r
+               /// <returns>True, if <c ref="command">command</c> is a valid IRC command, false if not.</returns>\r
+               private static bool IsValidIrcCommand(string command)\r
+               {\r
+                       foreach (char c in command)\r
+                       {\r
+                               if (!Char.IsLetter(c))\r
+                               {\r
+                                       return false;\r
+                               }\r
+                       }\r
+                       return true;\r
+               }\r
+\r
+               private const string IrcSpecial = @"-[]\`^{}";\r
+               private const string IrcSpecialNonSpecs = @"_";\r
+\r
+               /// <summary>\r
+               /// Returns wether a character is an IRC special character.\r
+               /// </summary>\r
+               /// <param name="c">Character to test.</param>\r
+               /// <returns>True if the character is an IRC special character, false if not.</returns>\r
+               private static bool IsSpecial(char c)\r
+               {\r
+                       foreach (char validCharacter in IrcSpecial)\r
+                       {\r
+                               if (c.Equals(validCharacter))\r
+                               {\r
+                                       return true;\r
+                               }\r
+                       }\r
+                       foreach (char validCharacter in IrcSpecialNonSpecs)\r
+                       {\r
+                               if (c.Equals(validCharacter))\r
+                               {\r
+                                       return true;\r
+                               }\r
+                       }\r
+                       return false;\r
+               }\r
+\r
+               /// <summary>\r
+               /// Returns wether a string of text is a valid IRC nickname.\r
+               /// </summary>\r
+               /// <param name="nickname">The IRC nickname.</param>\r
+               /// <returns>True, if <c ref="nickname">nickname</c> is a valid IRC nickname, false if not.</returns>\r
+               private static bool IsValidIrcNickname(string nickname)\r
+               {\r
+                       /*\r
+                        * <nick>       ::= <letter> { <letter> | <number> | <special> }\r
+                        * <letter>     ::= 'a' ... 'z' | 'A' ... 'Z'\r
+                        * <number>     ::= '0' ... '9'\r
+                        * <special>    ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'\r
+                        */\r
+                       /* An IRC nicknmame must be 1 - 9 characters in length. We don't care so much if it is larger */\r
+                       if ((nickname.Length < 1) || (nickname.Length > 30))\r
+                       {\r
+                               return false;\r
+                       }\r
+                       /* First character must be a letter. */\r
+                       if (!Char.IsLetter(nickname[0]))\r
+                       {\r
+                               return false;\r
+                       }\r
+                       /* Check the other valid characters for validity. */\r
+                       foreach (char c in nickname)\r
+                       {\r
+                               if (!Char.IsLetter(c) && !Char.IsDigit(c) && !IsSpecial(c))\r
+                               {\r
+                                       return false;\r
+                               }\r
+                       }\r
+                       return true;\r
+               }\r
+\r
+               /// <summary>\r
+               /// Write contents to a string.\r
+               /// </summary>\r
+               /// <returns>Contents as a string.</returns>\r
+               public override string ToString()\r
+               {\r
+                       return String.Format("Line({0})Prefix({1})Command({2})Parameters({3})",\r
+                               line, prefix != null ? prefix : "(null)",\r
+                               command != null ? command : "(null)",\r
+                               parameters != null ? parameters : "(null)");\r
+               }\r
+       }\r
+}\r