mirror of
https://github.com/reactos/reactos.git
synced 2024-08-21 02:43:48 +00:00
504 lines
12 KiB
C#
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)");
|
|
}
|
|
}
|
|
}
|