--- /dev/null
+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