diff --git a/Examples/States/Client.cs b/Examples/States/Client.cs index e98457d..0cd7722 100644 --- a/Examples/States/Client.cs +++ b/Examples/States/Client.cs @@ -45,9 +45,8 @@ namespace StatesSample { while (_encoder.PendingBytes.Any()) { - var bytesSent = _socket.Send(_encoder.PendingBytes); - var sentLines = _encoder.Pop(bytesSent); - foreach (var line in sentLines) Console.WriteLine($"> {line.Format()}"); + foreach (var line in _encoder.Pop(_socket.Send(_encoder.PendingBytes))) + Console.WriteLine($"> {line.Format()}"); } var bytesReceived = _socket.Receive(_bytes); diff --git a/IRCStates/Channel.cs b/IRCStates/Channel.cs index 60ca3fb..a9d2302 100644 --- a/IRCStates/Channel.cs +++ b/IRCStates/Channel.cs @@ -13,15 +13,15 @@ namespace IRCStates Modes = new Dictionary(); } - public string Name { get; set; } - public string NameLower { get; set; } - public Dictionary Users { get; set; } + public string Name { get; private set; } + public string NameLower { get; private set; } + public Dictionary Users { get; private set; } public string Topic { get; set; } public string TopicSetter { get; set; } public DateTime TopicTime { get; set; } public DateTime Created { get; set; } - public Dictionary> ListModes { get; set; } - public Dictionary Modes { get; set; } + public Dictionary> ListModes { get; private set; } + public Dictionary Modes { get; private set; } public override string ToString() { diff --git a/IRCStates/ChannelUser.cs b/IRCStates/ChannelUser.cs index 8c2298b..bc5e99b 100644 --- a/IRCStates/ChannelUser.cs +++ b/IRCStates/ChannelUser.cs @@ -9,9 +9,9 @@ namespace IRCStates Modes = new List(); } - public List Modes { get; set; } + public List Modes { get; } - protected bool Equals(ChannelUser other) + private bool Equals(ChannelUser other) { return other != null && Equals(Modes, other.Modes); } @@ -20,8 +20,7 @@ namespace IRCStates { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((ChannelUser) obj); + return obj.GetType() == GetType() && Equals((ChannelUser) obj); } public override int GetHashCode() diff --git a/IRCStates/Extensions.cs b/IRCStates/Extensions.cs index c807dbb..6cce3d4 100644 --- a/IRCStates/Extensions.cs +++ b/IRCStates/Extensions.cs @@ -5,6 +5,13 @@ namespace IRCStates { public static class Extensions { + /// + /// Update the dictionary with 's keys and values + /// + /// + /// + /// + /// public static void UpdateWith(this Dictionary dict, Dictionary other) { if (dict == null || other == null || !other.Any()) return; diff --git a/IRCStates/IRCStates.csproj b/IRCStates/IRCStates.csproj index 76c4bf4..162f592 100644 --- a/IRCStates/IRCStates.csproj +++ b/IRCStates/IRCStates.csproj @@ -13,6 +13,7 @@ https://tildegit.org/ben/ircsharp/src/branch/master/IRCStates git irc + 1.0.1 diff --git a/IRCStates/ISupport.cs b/IRCStates/ISupport.cs index b8a5651..8dfcb0c 100644 --- a/IRCStates/ISupport.cs +++ b/IRCStates/ISupport.cs @@ -21,21 +21,25 @@ namespace IRCStates Whox = false; } - public Dictionary Raw { get; set; } - public string Network { get; set; } - public ISupportChanModes ChanModes { get; set; } - public ISupportPrefix Prefix { get; set; } - public int? Modes { get; set; } - public Casemap.CaseMapping CaseMapping { get; set; } - public List ChanTypes { get; set; } - public List StatusMsg { get; set; } - public string CallerId { get; set; } - public string Excepts { get; set; } - public string Invex { get; set; } - public int? Monitor { get; set; } - public int? Watch { get; set; } - public bool Whox { get; set; } + public Dictionary Raw { get; } + public string Network { get; private set; } + public ISupportChanModes ChanModes { get; private set; } + public ISupportPrefix Prefix { get; private set; } + public int? Modes { get; private set; } + public Casemap.CaseMapping CaseMapping { get; private set; } + public List ChanTypes { get; private set; } + public List StatusMsg { get; private set; } + public string CallerId { get; private set; } + public string Excepts { get; private set; } + public string Invex { get; private set; } + public int? Monitor { get; private set; } + public int? Watch { get; private set; } + public bool Whox { get; private set; } + /// + /// Parse the ISupport values from the line's parameters + /// + /// public void Parse(IEnumerable tokens) { if (tokens == null) return; diff --git a/IRCStates/ISupportChanModes.cs b/IRCStates/ISupportChanModes.cs index e63c15f..5a175da 100644 --- a/IRCStates/ISupportChanModes.cs +++ b/IRCStates/ISupportChanModes.cs @@ -6,6 +6,10 @@ namespace IRCStates { public class ISupportChanModes { + /// + /// Split the chanmodes and add to our known + /// + /// public ISupportChanModes(string splitVal) { if (splitVal == null) return; @@ -25,9 +29,9 @@ namespace IRCStates SettingDModes.AddRange(split[3].Select(c => c.ToString(CultureInfo.InvariantCulture))); } - public List ListModes { get; set; } - public List SettingBModes { get; set; } - public List SettingCModes { get; set; } - public List SettingDModes { get; set; } + public List ListModes { get; } + public List SettingBModes { get; } + public List SettingCModes { get; } + public List SettingDModes { get; } } } diff --git a/IRCStates/ISupportPrefix.cs b/IRCStates/ISupportPrefix.cs index 35c5344..146bdcc 100644 --- a/IRCStates/ISupportPrefix.cs +++ b/IRCStates/ISupportPrefix.cs @@ -7,6 +7,11 @@ namespace IRCStates { public class ISupportPrefix { + /// + /// Split the prefix value and add them to our known and + /// + /// + /// public ISupportPrefix(string splitVal) { if (splitVal == null) throw new ArgumentNullException(nameof(splitVal)); @@ -18,8 +23,8 @@ namespace IRCStates Prefixes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture))); } - public List Modes { get; set; } - public List Prefixes { get; set; } + public List Modes { get; } + public List Prefixes { get; } public string FromMode(char mode) { diff --git a/IRCStates/Numeric.cs b/IRCStates/Numeric.cs index 1ccbd76..674e313 100644 --- a/IRCStates/Numeric.cs +++ b/IRCStates/Numeric.cs @@ -2,6 +2,9 @@ namespace IRCStates { + /// + /// Known numeric response codes + /// public static class Numeric { #pragma warning disable CA1707 // Identifiers should not contain underscores diff --git a/IRCStates/Server.cs b/IRCStates/Server.cs index b4a0145..2cbbb92 100644 --- a/IRCStates/Server.cs +++ b/IRCStates/Server.cs @@ -11,7 +11,7 @@ namespace IRCStates public const string WhoType = "525"; // randomly generated private readonly StatefulDecoder _decoder; - private readonly Dictionary TempCaps; + private readonly Dictionary _tempCaps; public Server(string name) { @@ -24,7 +24,7 @@ namespace IRCStates Channels = new Dictionary(); ISupport = new ISupport(); HasCap = false; - TempCaps = new Dictionary(); + _tempCaps = new Dictionary(); AvailableCaps = new Dictionary(); AgreedCaps = new List(); } @@ -54,56 +54,107 @@ namespace IRCStates return $"Server(name={Name})"; } + /// + /// Use 's case mapping to convert to lowercase + /// + /// + /// public string CaseFold(string str) { return Casemap.CaseFold(ISupport.CaseMapping, str); } - private bool CaseFoldEquals(string s1, string s2) - { - return CaseFold(s1) == CaseFold(s2); - } - + /// + /// Is the current nickname this client? + /// + /// + /// private bool IsMe(string nickname) { return CaseFold(nickname) == NickNameLower; } + /// + /// Check for a user - not case sensitive + /// + /// + /// private bool HasUser(string nickname) { return Users.ContainsKey(CaseFold(nickname)); } - private User AddUser(string nickname, string nicknameLower) + /// + /// Get existing user by case-insensitive nickname + /// + /// + /// + private User GetUser(string nickname) { - var user = CreateUser(nickname, nicknameLower); - Users[nicknameLower] = user; + return HasUser(nickname) ? Users[CaseFold(nickname)] : null; + } + + /// + /// Create and add user + /// + /// + /// + private User AddUser(string nickname) + { + var user = CreateUser(nickname); + Users[CaseFold(nickname)] = user; return user; } - private User CreateUser(string nickname, string nicknameLower) + /// + /// Build a new and update correct case-mapped nick + /// + /// + /// + private User CreateUser(string nickname) { var user = new User(); - user.SetNickName(nickname, nicknameLower); + user.SetNickName(nickname, CaseFold(nickname)); return user; } + /// + /// Is the channel a valid ISupport type? + /// + /// + /// private bool IsChannel(string target) { return !string.IsNullOrEmpty(target) && ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture)); } + /// + /// Is the channel known to this client? + /// + /// + /// public bool HasChannel(string name) { - return Channels.ContainsKey(CaseFold(name)); + return IsChannel(name) && Channels.ContainsKey(CaseFold(name)); } + /// + /// Get the channel if it's known to us + /// + /// + /// private Channel GetChannel(string name) { - return HasChannel(name) ? Channels[name] : null; + return HasChannel(name) ? Channels[CaseFold(name)] : null; } + /// + /// Add a to a + /// + /// + /// + /// the that was added private ChannelUser UserJoin(Channel channel, User user) { var channelUser = new ChannelUser(); @@ -112,6 +163,11 @@ namespace IRCStates return channelUser; } + /// + /// Set own , , and + /// from a given + /// + /// private void SelfHostmask(Hostmask hostmask) { NickName = hostmask.NickName; @@ -119,6 +175,19 @@ namespace IRCStates if (hostmask.HostName != null) HostName = hostmask.HostName; } + private void SelfHostmask(string raw) + { + SelfHostmask(new Hostmask(raw)); + } + + /// + /// Remove a user from a channel. Used to handle PART and KICK + /// + /// + /// + /// + /// + /// private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex) { var emit = new Emit(); @@ -128,7 +197,7 @@ namespace IRCStates User user = null; if (HasChannel(channelName)) { - var channel = Channels[channelLower]; + var channel = GetChannel(channelName); emit.Channel = channel; var nickLower = CaseFold(nickName); if (HasUser(nickLower)) @@ -153,6 +222,12 @@ namespace IRCStates return (emit, user); } + /// + /// Update modes on a given modes and parameters + /// + /// + /// + /// private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList parameters) { foreach (var (add, c) in modes) @@ -197,6 +272,13 @@ namespace IRCStates } } + /// + /// Handle incoming bytes + /// + /// + /// + /// parsed lines and emits + /// public IEnumerable<(Line, Emit)> Receive(byte[] data, int length) { if (data == null) return null; @@ -204,9 +286,14 @@ namespace IRCStates var lines = _decoder.Push(data, length); if (lines == null) throw new ServerDisconnectedException(); - return lines.Select(l => (l, Parse(l))).ToList(); + return lines.Select(l => (l, Parse(l))); } + /// + /// Delegate a to the correct handler + /// + /// + /// public Emit Parse(Line line) { if (line == null) return null; @@ -256,6 +343,11 @@ namespace IRCStates return emit; } + /// + /// Handles SETNAME command + /// + /// + /// private Emit HandleSetname(Line line) { var emit = new Emit(); @@ -278,6 +370,11 @@ namespace IRCStates return emit; } + /// + /// Handles AWAY command + /// + /// + /// private Emit HandleAway(Line line) { var emit = new Emit(); @@ -300,6 +397,11 @@ namespace IRCStates return emit; } + /// + /// Handles ACCOUNT command + /// + /// + /// private Emit HandleAccount(Line line) { var emit = new Emit(); @@ -322,6 +424,11 @@ namespace IRCStates return emit; } + /// + /// Handles CAP command + /// + /// + /// private Emit HandleCap(Line line) { HasCap = true; @@ -343,11 +450,11 @@ namespace IRCStates switch (subcommand) { case "LS": - TempCaps.UpdateWith(tokens); + _tempCaps.UpdateWith(tokens); if (!multiline) { - AvailableCaps.UpdateWith(TempCaps); - TempCaps.Clear(); + AvailableCaps.UpdateWith(_tempCaps); + _tempCaps.Clear(); } break; @@ -380,6 +487,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_LOGGEDIN numeric + /// + /// + /// private Emit HandleLoggedIn(Line line) { SelfHostmask(new Hostmask(line.Params[1])); @@ -387,6 +499,11 @@ namespace IRCStates return new Emit(); } + /// + /// Handles CHGHOST command + /// + /// + /// private Emit HandleChghost(Line line) { var emit = new Emit(); @@ -412,6 +529,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_WHOISUSER numeric + /// + /// + /// private Emit HandleWhoIsUser(Line line) { var emit = new Emit(); @@ -440,6 +562,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_WHOSPCRPL numeric + /// + /// + /// private Emit HandleWhox(Line line) { var emit = new Emit(); @@ -474,6 +601,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_WHOREPLY numeric + /// + /// + /// private Emit HandleWhoReply(Line line) { var emit = new Emit {Target = line.Params[1]}; @@ -502,6 +634,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_VISIBLEHOST numeric + /// + /// + /// private Emit HandleVisibleHost(Line line) { var split = line.Params[1].Split('@', 2); @@ -519,22 +656,25 @@ namespace IRCStates return new Emit(); } + /// + /// Handles PRIVMSG, NOTICE, and TAGMSG commands + /// + /// + /// private Emit HandleMessage(Line line) { var emit = new Emit(); var message = line.Params.Count > 1 ? line.Params[1] : null; if (message != null) emit.Text = message; - var nickLower = CaseFold(line.Hostmask.NickName); - if (IsMe(nickLower)) + var nick = CaseFold(line.Hostmask.NickName); + if (IsMe(nick)) { emit.SelfSource = true; SelfHostmask(line.Hostmask); } - var user = HasUser(nickLower) - ? Users[nickLower] - : AddUser(line.Hostmask.NickName, nickLower); + var user = GetUser(nick) ?? AddUser(nick); emit.User = user; if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName; @@ -559,12 +699,17 @@ namespace IRCStates emit.Target = line.Params[0]; if (IsChannel(target) && HasChannel(target)) - emit.Channel = Channels[CaseFold(target)]; + emit.Channel = GetChannel(target); else if (IsMe(target)) emit.SelfTarget = true; return emit; } + /// + /// Handles RPL_UMODEIS numeric + /// + /// + /// private Emit HandleUModeIs(Line line) { foreach (var c in line.Params[1] @@ -576,12 +721,17 @@ namespace IRCStates return new Emit(); } + /// + /// Handles RPL_CHANNELMODEIS numeric + /// + /// + /// private Emit HandleChannelModeIs(Line line) { var emit = new Emit(); if (HasChannel(line.Params[1])) { - var channel = Channels[CaseFold(line.Params[1])]; + var channel = GetChannel(line.Params[1]); emit.Channel = channel; var modes = line.Params[2] .TrimStart('+') @@ -593,6 +743,11 @@ namespace IRCStates return emit; } + /// + /// Handles MODE command + /// + /// + /// private Emit HandleMode(Line line) { var emit = new Emit(); @@ -635,13 +790,17 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_TOPICWHOTIME numeric + /// + /// + /// private Emit HandleTopicTime(Line line) { - var emit = new Emit(); - var channelLower = CaseFold(line.Params[1]); - if (Channels.ContainsKey(channelLower)) + var emit = new Emit(); + if (HasChannel(line.Params[1])) { - var channel = Channels[channelLower]; + var channel = GetChannel(line.Params[1]); emit.Channel = channel; channel.TopicSetter = line.Params[2]; channel.TopicTime = DateTimeOffset @@ -651,27 +810,35 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_TOPIC numeric + /// + /// + /// private Emit HandleTopicNumeric(Line line) { - var emit = new Emit(); - var channelLower = CaseFold(line.Params[1]); - if (Channels.ContainsKey(channelLower)) + var emit = new Emit(); + if (HasChannel(line.Params[1])) { - var channel = Channels[channelLower]; - emit.Channel = channel; - Channels[channelLower].Topic = line.Params[2]; + var channel = GetChannel(line.Params[1]); + emit.Channel = channel; + channel.Topic = line.Params[2]; } return emit; } + /// + /// Handles TOPIC command + /// + /// + /// private Emit HandleTopic(Line line) { - var emit = new Emit(); - var channelLower = CaseFold(line.Params[0]); - if (Channels.ContainsKey(channelLower)) + var emit = new Emit(); + if (HasChannel(line.Params[0])) { - var channel = Channels[channelLower]; + var channel = GetChannel(line.Params[0]); emit.Channel = channel; channel.Topic = line.Params[1]; channel.TopicSetter = line.Hostmask.ToString(); @@ -681,13 +848,17 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_CREATIONTIME numeric + /// + /// + /// private Emit HandleCreationTime(Line line) { - var emit = new Emit(); - var channelLower = CaseFold(line.Params[1]); - if (Channels.ContainsKey(channelLower)) + var emit = new Emit(); + if (HasChannel(line.Params[1])) { - var channel = Channels[channelLower]; + var channel = GetChannel(line.Params[1]); emit.Channel = channel; channel.Created = DateTimeOffset .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime; @@ -696,13 +867,17 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_NAMREPLY numeric + /// + /// + /// private Emit HandleNames(Line line) { - var emit = new Emit(); - var channelLower = CaseFold(line.Params[2]); + var emit = new Emit(); + if (!HasChannel(line.Params[2])) return emit; - if (!Channels.ContainsKey(channelLower)) return emit; - var channel = Channels[channelLower]; + var channel = GetChannel(line.Params[2]); emit.Channel = channel; var nicknames = line.Params[3].Split(' ', StringSplitOptions.RemoveEmptyEntries); var users = new List(); @@ -721,17 +896,15 @@ namespace IRCStates } var hostmask = new Hostmask(nick.Substring(modes.Length)); - var nickLower = CaseFold(hostmask.NickName); - if (!Users.ContainsKey(nickLower)) AddUser(hostmask.NickName, nickLower); + var user = GetUser(hostmask.NickName) ?? AddUser(hostmask.NickName); - var user = Users[nickLower]; users.Add(user); var channelUser = UserJoin(channel, user); if (hostmask.UserName != null) user.UserName = hostmask.UserName; if (hostmask.HostName != null) user.HostName = hostmask.HostName; - if (IsMe(nickLower)) SelfHostmask(hostmask); + if (IsMe(hostmask.NickName)) SelfHostmask(hostmask); foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture))) if (!channelUser.Modes.Contains(mode)) @@ -741,6 +914,11 @@ namespace IRCStates return emit; } + /// + /// Handles ERROR command + /// + /// + /// private Emit HandleError(Line line) { Users.Clear(); @@ -748,22 +926,27 @@ namespace IRCStates return new Emit(); } + /// + /// Handles QUIT command + /// + /// + /// private Emit HandleQuit(Line line) { var emit = new Emit(); - var nickLower = CaseFold(line.Hostmask.NickName); + var nick = line.Hostmask.NickName; if (line.Params.Any()) emit.Text = line.Params[0]; - if (IsMe(nickLower) || line.Source == null) + if (IsMe(nick) || line.Source == null) { emit.Self = true; Users.Clear(); Channels.Clear(); } - else if (Users.ContainsKey(nickLower)) + else if (HasUser(nick)) { - var user = Users[nickLower]; - Users.Remove(nickLower); + var user = GetUser(nick); + Users.Remove(user.NickNameLower); emit.User = user; foreach (var channel in user.Channels.Select(c => Channels[c])) channel.Users.Remove(user.NickNameLower); @@ -772,13 +955,23 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_LOGGEDOUT numeric + /// + /// + /// private Emit HandleLoggedOut(Line line) { Account = null; - SelfHostmask(new Hostmask(line.Params[1])); + SelfHostmask(line.Params[1]); return new Emit(); } + /// + /// Handles KICK command + /// + /// + /// private Emit HandleKick(Line line) { var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2); @@ -787,30 +980,37 @@ namespace IRCStates emit.UserTarget = kicked; if (IsMe(kicked.NickName)) emit.Self = true; - var kickerLower = CaseFold(line.Hostmask.NickName); - if (IsMe(kickerLower)) emit.SelfSource = true; + var kicker = line.Hostmask.NickName; + if (IsMe(kicker)) emit.SelfSource = true; - emit.UserSource = Users.ContainsKey(kickerLower) - ? Users[kickerLower] - : CreateUser(line.Hostmask.NickName, kickerLower); + emit.UserSource = GetUser(kicker) ?? CreateUser(kicker); } return emit; } + /// + /// Handles PART command + /// + /// + /// private Emit HandlePart(Line line) { var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1); if (user != null) { emit.User = user; - if (IsMe(user.NickName)) emit.Self = true; + emit.Self = IsMe(user.NickName); } return emit; } - + /// + /// Handles JOIN command + /// + /// + /// private Emit HandleJoin(Line line) { var extended = line.Params.Count == 3; @@ -818,18 +1018,18 @@ namespace IRCStates var realname = extended ? line.Params[2] : null; var emit = new Emit(); - var channelLower = CaseFold(line.Params[0]); - var nickLower = CaseFold(line.Hostmask.NickName); + var channelName = line.Params[0]; + var nick = line.Hostmask.NickName; // handle own join - if (IsMe(nickLower)) + if (IsMe(nick)) { emit.Self = true; - if (!HasChannel(channelLower)) + if (!HasChannel(channelName)) { var channel = new Channel(); - channel.SetName(line.Params[0], channelLower); - Channels[channelLower] = channel; + channel.SetName(channelName, CaseFold(channelName)); + Channels[CaseFold(channelName)] = channel; } SelfHostmask(line.Hostmask); @@ -840,14 +1040,14 @@ namespace IRCStates } } - if (HasChannel(channelLower)) + if (HasChannel(channelName)) { - var channel = Channels[channelLower]; + var channel = GetChannel(channelName); emit.Channel = channel; - if (!HasUser(nickLower)) AddUser(line.Hostmask.NickName, nickLower); + if (!HasUser(nick)) AddUser(nick); - var user = Users[nickLower]; + var user = GetUser(nick); emit.User = user; if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName; if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName; @@ -863,43 +1063,53 @@ namespace IRCStates return emit; } - + /// + /// Handles NICK command + /// + /// + /// private Emit HandleNick(Line line) { - var nick = line.Params[0]; - var nickLower = CaseFold(line.Hostmask.NickName); + var newNick = line.Params[0]; + var oldNick = line.Hostmask.NickName; var emit = new Emit(); - if (Users.ContainsKey(nickLower)) + if (HasUser(oldNick)) { - var user = Users[nickLower]; - Users.Remove(nickLower); - emit.User = user; - + var user = GetUser(oldNick); var oldNickLower = user.NickNameLower; - var newNickLower = CaseFold(nick); - user.SetNickName(nick, newNickLower); + var newNickLower = CaseFold(newNick); + + emit.User = user; + Users.Remove(oldNickLower); Users[newNickLower] = user; + user.SetNickName(newNick, newNickLower); + foreach (var channelLower in user.Channels) { - var channel = Channels[channelLower]; + var channel = GetChannel(channelLower); var channelUser = channel.Users[oldNickLower]; channel.Users.Remove(oldNickLower); channel.Users[newNickLower] = channelUser; } } - if (IsMe(nickLower)) + if (IsMe(oldNick)) { emit.Self = true; - NickName = nick; - NickNameLower = CaseFold(nick); + NickName = newNick; + NickNameLower = CaseFold(newNick); } return emit; } + /// + /// Handles RPL_MOTDSTART and RPL_MOTD numerics + /// + /// + /// private Emit HandleMotd(Line line) { if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear(); @@ -909,6 +1119,11 @@ namespace IRCStates return emit; } + /// + /// Handles RPL_ISUPPORT numeric + /// + /// + /// private Emit HandleISupport(Line line) { ISupport = new ISupport(); @@ -916,7 +1131,11 @@ namespace IRCStates return new Emit(); } - + /// + /// Handles RPL_WELCOME numeric + /// + /// + /// private Emit HandleWelcome(Line line) { NickName = line.Params[0]; diff --git a/IRCStates/User.cs b/IRCStates/User.cs index 5e18443..97abb15 100644 --- a/IRCStates/User.cs +++ b/IRCStates/User.cs @@ -9,15 +9,15 @@ namespace IRCStates Channels = new HashSet(); } - public string NickName { get; set; } - public string NickNameLower { get; set; } + public string NickName { get; private set; } + public string NickNameLower { get; private set; } public string UserName { get; set; } public string HostName { get; set; } public string RealName { get; set; } public string Account { get; set; } public string Away { get; set; } - public HashSet Channels { get; set; } + public HashSet Channels { get; private set; } public override string ToString() { diff --git a/IRCTokens/IRCTokens.csproj b/IRCTokens/IRCTokens.csproj index ed2e0c2..cbee30e 100644 --- a/IRCTokens/IRCTokens.csproj +++ b/IRCTokens/IRCTokens.csproj @@ -13,6 +13,7 @@ https://tildegit.org/ben/ircsharp/src/branch/master/IRCTokens git irc + 1.0.1 diff --git a/IRCTokens/StatefulDecoder.cs b/IRCTokens/StatefulDecoder.cs index 82630f6..619253b 100644 --- a/IRCTokens/StatefulDecoder.cs +++ b/IRCTokens/StatefulDecoder.cs @@ -59,6 +59,15 @@ namespace IRCTokens _buffer = _buffer == null ? Array.Empty() : _buffer.Concat(data.Take(bytesReceived)).ToArray(); + // truncate message at NUL if found + if (_buffer.Contains((byte) 0)) + { + _buffer = _buffer + .Take(Array.IndexOf(_buffer, (byte) 0)) + .Concat(new []{(byte) '\n'}) + .ToArray(); + } + var listLines = _buffer.Split((byte) '\n').Select(l => l.Trim((byte) '\r')).ToList(); _buffer = listLines.LastOrDefault() ?? Array.Empty(); diff --git a/IRCTokens/Tests/Format.cs b/IRCTokens/Tests/Format.cs index 8319069..7224f97 100644 --- a/IRCTokens/Tests/Format.cs +++ b/IRCTokens/Tests/Format.cs @@ -8,7 +8,7 @@ namespace IRCTokens.Tests public class Format { [TestMethod] - public void TestTags() + public void Tags() { var line = new Line("PRIVMSG", "#channel", "hello") { @@ -19,7 +19,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestMissingTag() + public void MissingTag() { var line = new Line("PRIVMSG", "#channel", "hello").Format(); @@ -27,7 +27,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestNullTag() + public void NullTag() { var line = new Line("PRIVMSG", "#channel", "hello") {Tags = new Dictionary {{"a", null}}} .Format(); @@ -36,7 +36,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEmptyTag() + public void EmptyTag() { var line = new Line("PRIVMSG", "#channel", "hello") {Tags = new Dictionary {{"a", ""}}} .Format(); @@ -45,7 +45,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestSource() + public void Source() { var line = new Line("PRIVMSG", "#channel", "hello") {Source = "nick!user@host"}.Format(); @@ -53,21 +53,21 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestCommandLowercase() + public void CommandLowercase() { var line = new Line {Command = "privmsg"}.Format(); Assert.AreEqual("privmsg", line); } [TestMethod] - public void TestCommandUppercase() + public void CommandUppercase() { var line = new Line {Command = "PRIVMSG"}.Format(); Assert.AreEqual("PRIVMSG", line); } [TestMethod] - public void TestTrailingSpace() + public void TrailingSpace() { var line = new Line("PRIVMSG", "#channel", "hello world").Format(); @@ -75,7 +75,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestTrailingNoSpace() + public void TrailingNoSpace() { var line = new Line("PRIVMSG", "#channel", "helloworld").Format(); @@ -83,7 +83,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestTrailingDoubleColon() + public void TrailingDoubleColon() { var line = new Line("PRIVMSG", "#channel", ":helloworld").Format(); @@ -91,13 +91,13 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestInvalidNonLastSpace() + public void InvalidNonLastSpace() { Assert.ThrowsException(() => { new Line("USER", "user", "0 *", "real name").Format(); }); } [TestMethod] - public void TestInvalidNonLastColon() + public void InvalidNonLastColon() { Assert.ThrowsException(() => { new Line("PRIVMSG", ":#channel", "hello").Format(); }); } diff --git a/IRCTokens/Tests/Hostmask.cs b/IRCTokens/Tests/Hostmask.cs index 2446013..17c5ad7 100644 --- a/IRCTokens/Tests/Hostmask.cs +++ b/IRCTokens/Tests/Hostmask.cs @@ -6,7 +6,7 @@ namespace IRCTokens.Tests public class Hostmask { [TestMethod] - public void TestHostmask() + public void FullHostmask() { var hostmask = new IRCTokens.Hostmask("nick!user@host"); Assert.AreEqual("nick", hostmask.NickName); @@ -15,7 +15,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestNoHostName() + public void NoHostName() { var hostmask = new IRCTokens.Hostmask("nick!user"); Assert.AreEqual("nick", hostmask.NickName); @@ -24,7 +24,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestNoUserName() + public void NoUserName() { var hostmask = new IRCTokens.Hostmask("nick@host"); Assert.AreEqual("nick", hostmask.NickName); @@ -33,7 +33,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestOnlyNickName() + public void OnlyNickName() { var hostmask = new IRCTokens.Hostmask("nick"); Assert.AreEqual("nick", hostmask.NickName); @@ -42,7 +42,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestHostmaskFromLine() + public void HostmaskFromLine() { var line = new Line(":nick!user@host PRIVMSG #channel hello"); var hostmask = new IRCTokens.Hostmask("nick!user@host"); @@ -53,7 +53,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEmptyHostmaskFromLine() + public void EmptyHostmaskFromLine() { var line = new Line("PRIVMSG #channel hello"); Assert.IsNull(line.Hostmask.HostName); diff --git a/IRCTokens/Tests/Parser.cs b/IRCTokens/Tests/Parser.cs index bd0a92d..a560793 100644 --- a/IRCTokens/Tests/Parser.cs +++ b/IRCTokens/Tests/Parser.cs @@ -21,7 +21,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestSplit() + public void Split() { foreach (var test in LoadYaml("Tests/Data/msg-split.yaml").Tests) { @@ -38,7 +38,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestJoin() + public void Join() { foreach (var test in LoadYaml("Tests/Data/msg-join.yaml").Tests) { diff --git a/IRCTokens/Tests/StatefulDecoder.cs b/IRCTokens/Tests/StatefulDecoder.cs index d37310f..4da7690 100644 --- a/IRCTokens/Tests/StatefulDecoder.cs +++ b/IRCTokens/Tests/StatefulDecoder.cs @@ -10,13 +10,13 @@ namespace IRCTokens.Tests private IRCTokens.StatefulDecoder _decoder; [TestInitialize] - public void TestInitialize() + public void Initialize() { _decoder = new IRCTokens.StatefulDecoder(); } [TestMethod] - public void TestPartial() + public void Partial() { var lines = _decoder.Push("PRIVMSG "); Assert.AreEqual(0, lines.Count); @@ -29,7 +29,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestMultiple() + public void Multiple() { var lines = _decoder.Push("PRIVMSG #channel1 hello\r\nPRIVMSG #channel2 hello\r\n"); Assert.AreEqual(2, lines.Count); @@ -41,7 +41,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEncoding() + public void EncodingIso8859() { var iso8859 = Encoding.GetEncoding("iso-8859-1"); _decoder = new IRCTokens.StatefulDecoder {Encoding = iso8859}; @@ -52,7 +52,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEncodingFallback() + public void EncodingFallback() { var latin1 = Encoding.GetEncoding("iso-8859-1"); _decoder = new IRCTokens.StatefulDecoder {Encoding = null, Fallback = latin1}; @@ -63,14 +63,14 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEmpty() + public void Empty() { var lines = _decoder.Push(string.Empty); Assert.AreEqual(0, lines.Count); } [TestMethod] - public void TestBufferUnfinished() + public void BufferUnfinished() { _decoder.Push("PRIVMSG #channel hello"); var lines = _decoder.Push(string.Empty); @@ -78,7 +78,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestClear() + public void Clear() { _decoder.Push("PRIVMSG "); _decoder.Clear(); diff --git a/IRCTokens/Tests/StatefulEncoder.cs b/IRCTokens/Tests/StatefulEncoder.cs index 5ced4d2..d1e1e3e 100644 --- a/IRCTokens/Tests/StatefulEncoder.cs +++ b/IRCTokens/Tests/StatefulEncoder.cs @@ -9,13 +9,13 @@ namespace IRCTokens.Tests private IRCTokens.StatefulEncoder _encoder; [TestInitialize] - public void TestInitialize() + public void Initialize() { _encoder = new IRCTokens.StatefulEncoder(); } [TestMethod] - public void TestPush() + public void Push() { var line = new Line("PRIVMSG #channel hello"); _encoder.Push(line); @@ -23,7 +23,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestPopPartial() + public void PopPartial() { var line = new Line("PRIVMSG #channel hello"); _encoder.Push(line); @@ -43,7 +43,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestPopNoneReturned() + public void PopNoneReturned() { var line = new Line("PRIVMSG #channel hello"); _encoder.Push(line); @@ -52,7 +52,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestPopMultipleLines() + public void PopMultipleLines() { var line1 = new Line("PRIVMSG #channel1 hello"); _encoder.Push(line1); @@ -65,7 +65,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestClear() + public void Clear() { _encoder.Push(new Line("PRIVMSG #channel hello")); _encoder.Clear(); @@ -73,7 +73,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestEncoding() + public void EncodingIso8859() { var iso8859 = Encoding.GetEncoding("iso-8859-1"); _encoder = new IRCTokens.StatefulEncoder {Encoding = iso8859}; diff --git a/IRCTokens/Tests/Tokenization.cs b/IRCTokens/Tests/Tokenization.cs index 03959de..c4c5c5a 100644 --- a/IRCTokens/Tests/Tokenization.cs +++ b/IRCTokens/Tests/Tokenization.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace IRCTokens.Tests @@ -7,98 +9,98 @@ namespace IRCTokens.Tests public class Tokenization { [TestMethod] - public void TestTagsMissing() + public void TagsMissing() { var line = new Line("PRIVMSG #channel"); Assert.IsNull(line.Tags); } [TestMethod] - public void TestTagsMissingValue() + public void TagsMissingValue() { var line = new Line("@id= PRIVMSG #channel"); Assert.AreEqual(string.Empty, line.Tags["id"]); } [TestMethod] - public void TestTagsMissingEqual() + public void TagsMissingEqual() { var line = new Line("@id PRIVMSG #channel"); Assert.AreEqual(string.Empty, line.Tags["id"]); } [TestMethod] - public void TestTagsUnescape() + public void TagsUnescape() { var line = new Line(@"@id=1\\\:\r\n\s2 PRIVMSG #channel"); Assert.AreEqual("1\\;\r\n 2", line.Tags["id"]); } [TestMethod] - public void TestTagsOverlap() + public void TagsOverlap() { var line = new Line(@"@id=1\\\s\\s PRIVMSG #channel"); Assert.AreEqual("1\\ \\s", line.Tags["id"]); } [TestMethod] - public void TestTagsLoneEndSlash() + public void TagsLoneEndSlash() { var line = new Line("@id=1\\ PRIVMSG #channel"); Assert.AreEqual("1", line.Tags["id"]); } [TestMethod] - public void TestSourceWithoutTags() + public void SourceWithoutTags() { var line = new Line(":nick!user@host PRIVMSG #channel"); Assert.AreEqual("nick!user@host", line.Source); } [TestMethod] - public void TestSourceWithTags() + public void SourceWithTags() { var line = new Line("@id=123 :nick!user@host PRIVMSG #channel"); Assert.AreEqual("nick!user@host", line.Source); } [TestMethod] - public void TestSourceMissingWithoutTags() + public void SourceMissingWithoutTags() { var line = new Line("PRIVMSG #channel"); Assert.IsNull(line.Source); } [TestMethod] - public void TestSourceMissingWithTags() + public void SourceMissingWithTags() { var line = new Line("@id=123 PRIVMSG #channel"); Assert.IsNull(line.Source); } [TestMethod] - public void TestCommand() + public void Command() { var line = new Line("privmsg #channel"); Assert.AreEqual("PRIVMSG", line.Command); } [TestMethod] - public void TestParamsTrailing() + public void ParamsTrailing() { var line = new Line("PRIVMSG #channel :hello world"); CollectionAssert.AreEqual(new List {"#channel", "hello world"}, line.Params); } [TestMethod] - public void TestParamsOnlyTrailing() + public void ParamsOnlyTrailing() { var line = new Line("PRIVMSG :hello world"); CollectionAssert.AreEqual(new List {"hello world"}, line.Params); } [TestMethod] - public void TestParamsMissing() + public void ParamsMissing() { var line = new Line("PRIVMSG"); Assert.AreEqual("PRIVMSG", line.Command); @@ -106,7 +108,7 @@ namespace IRCTokens.Tests } [TestMethod] - public void TestAllTokens() + public void AllTokens() { var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world"); CollectionAssert.AreEqual(new Dictionary {{"id", "123"}}, line.Tags); @@ -114,5 +116,18 @@ namespace IRCTokens.Tests Assert.AreEqual("PRIVMSG", line.Command); CollectionAssert.AreEqual(new List {"#channel", "hello world"}, line.Params); } + + [TestMethod] + public void NulByte() + { + var decoder = new IRCTokens.StatefulDecoder(); + var bytes = Encoding.UTF8.GetBytes(":nick!user@host PRIVMSG #channel :hello") + .Concat(Encoding.UTF8.GetBytes("\0")) + .Concat(Encoding.UTF8.GetBytes("world")) + .ToArray(); + var line = decoder.Push(bytes, bytes.Length).First(); + + CollectionAssert.AreEqual(new List {"#channel", "hello"}, line.Params); + } } }