reactos/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs
Pierre Schweitzer 555a6a340f Allow spaces in commands
svn path=/trunk/; revision=35090
2008-08-03 20:49:33 +00:00

504 lines
12 KiB
C#

using System;
namespace TechBot.IRCLibrary
{
/*
<message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
<prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
<command> ::= <letter> { <letter> } | <number> <number> <number>
<SPACE> ::= ' ' { ' ' }
<params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
<middle> ::= <Any *non-empty* sequence of octets not including SPACE
or NUL or CR or LF, the first of which may not be ':'>
<trailing> ::= <Any, possibly *empty*, sequence of octets not including
NUL or CR or LF>
<crlf> ::= CR LF
NOTES:
1) <SPACE> 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 <middle> or <trailing>. <Trailing> 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 (['!' <user> ] ['@' <host> ]) 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
*/
/// <summary>
/// IRC message.
/// </summary>
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
/// <summary>
/// Line of text that is to be parsed as an IRC message.
/// </summary>
public string Line
{
get
{
return line;
}
}
/// <summary>
/// Does the message have a prefix?
/// </summary>
public bool HasPrefix
{
get
{
return prefix != null;
}
}
/// <summary>
/// Prefix or null if none exists.
/// </summary>
public string Prefix
{
get
{
return prefix;
}
}
/// <summary>
/// Servername part of prefix or null if no prefix or servername exists.
/// </summary>
public string PrefixServername
{
get
{
return prefixServername;
}
}
/// <summary>
/// Nickname part of prefix or null if no prefix or nick exists.
/// </summary>
public string PrefixNickname
{
get
{
return prefixNickname;
}
}
/// <summary>
/// User part of (extended) prefix or null if no (extended) prefix exists.
/// </summary>
public string PrefixUser
{
get
{
return prefixUser;
}
}
/// <summary>
/// Host part of (extended) prefix or null if no (extended) prefix exists.
/// </summary>
public string PrefixHost
{
get
{
return prefixHost;
}
}
/// <summary>
/// Command part of message.
/// </summary>
public string Command
{
get
{
return command;
}
}
/// <summary>
/// Is command numeric?
/// </summary>
public bool IsCommandNumeric
{
get
{
if (command == null || command.Length != 3)
{
return false;
}
try
{
Int32.Parse(command);
return true;
}
catch (Exception)
{
return false;
}
}
}
/// <summary>
/// Command part of message as text.
/// </summary>
public string CommandText
{
get
{
return command;
}
}
/// <summary>
/// Command part of message as a number.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if IsCommandNumeric returns false.</exception>
public int CommandNumber
{
get
{
if (IsCommandNumeric)
{
return Int32.Parse(command);
}
else
{
throw new InvalidOperationException();
}
}
}
/// <summary>
/// Parameters part of message.
/// </summary>
public string Parameters
{
get
{
return parameters;
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="line">Line of text that is to be parsed as an IRC message.</param>
public IrcMessage(string line)
{
/*
* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
* <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
* :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);
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="prefixServername"></param>
/// <param name="prefixNickname"></param>
/// <param name="prefixUser"></param>
/// <param name="prefixHost"></param>
/// <param name="command"></param>
/// <param name="parameters"></param>
public IrcMessage(string prefixServername,
string prefixNickname,
string prefixUser,
string prefixHost,
string command,
string parameters)
{
throw new NotImplementedException();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="command">IRC command.</param>
/// <param name="parameters">IRC command parameters. May be null if there are no parameters.</param>
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);
}
}
/// <summary>
/// Returns wether a string of text is a valid IRC command.
/// </summary>
/// <param name="command">The IRC command.</param>
/// <returns>True, if <c ref="command">command</c> is a valid IRC command, false if not.</returns>
private static bool IsValidIrcCommand(string command)
{
foreach (char c in command)
{
if (!Char.IsLetter(c) && !Char.IsWhiteSpace(c))
{
return false;
}
}
return true;
}
private const string IrcSpecial = @"-[]\`^{}";
private const string IrcSpecialNonSpecs = @"_|";
/// <summary>
/// Returns wether a character is an IRC special character.
/// </summary>
/// <param name="c">Character to test.</param>
/// <returns>True if the character is an IRC special character, false if not.</returns>
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;
}
/// <summary>
/// Returns wether a string of text is a valid IRC nickname.
/// </summary>
/// <param name="nickname">The IRC nickname.</param>
/// <returns>True, if <c ref="nickname">nickname</c> is a valid IRC nickname, false if not.</returns>
private static bool IsValidIrcNickname(string nickname)
{
/*
* <nick> ::= <letter> { <letter> | <number> | <special> }
* <letter> ::= 'a' ... 'z' | 'A' ... 'Z'
* <number> ::= '0' ... '9'
* <special> ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'
*/
/* 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;
}
/// <summary>
/// Write contents to a string.
/// </summary>
/// <returns>Contents as a string.</returns>
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)");
}
}
}