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