From 513539319fd5a112dcc920db303b2070df3ba169 Mon Sep 17 00:00:00 2001 From: flashwave Date: Mon, 6 Feb 2023 21:14:50 +0100 Subject: [PATCH] Better HttpClient handling. --- LICENSE | 2 +- SharpChat.sln | 9 +++ SharpChat/BanManager.cs | 88 +++++++++++------------ SharpChat/ChatContext.cs | 26 ++++--- SharpChat/Flashii/FlashiiAuth.cs | 7 +- SharpChat/Flashii/FlashiiBan.cs | 4 +- SharpChat/Flashii/FlashiiBump.cs | 20 +++--- SharpChat/Packet/LegacyCommandResponse.cs | 1 + SharpChat/Program.cs | 30 ++++++-- SharpChat/SharpChat.csproj | 2 +- SharpChat/SockChatServer.cs | 78 ++++++++++---------- 11 files changed, 145 insertions(+), 122 deletions(-) diff --git a/LICENSE b/LICENSE index 667de78..d558402 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 flashwave +Copyright (c) 2019-2023 flashwave Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/SharpChat.sln b/SharpChat.sln index 75c6f6e..d98fcb6 100644 --- a/SharpChat.sln +++ b/SharpChat.sln @@ -5,6 +5,15 @@ VisualStudioVersion = 17.2.32630.192 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpChat", "SharpChat\SharpChat.csproj", "{DDB24C19-B802-4C96-AC15-0449C6FC77F2}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DF7A7073-A67A-4D93-92C6-F9D0F95E2359}" + ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore + LICENSE = LICENSE + Protocol.md = Protocol.md + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/SharpChat/BanManager.cs b/SharpChat/BanManager.cs index 6155f21..4d1f164 100644 --- a/SharpChat/BanManager.cs +++ b/SharpChat/BanManager.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; +using System.Threading.Tasks; namespace SharpChat { public interface IBan { @@ -43,25 +45,28 @@ namespace SharpChat { } public class BanManager : IDisposable { - private readonly List BanList = new List(); + private readonly List BanList = new(); + private readonly HttpClient HttpClient; public readonly ChatContext Context; + public bool IsDisposed { get; private set; } - public BanManager(ChatContext context) { + public BanManager(HttpClient httpClient, ChatContext context) { + HttpClient = httpClient; Context = context; - RefreshFlashiiBans(); + RefreshFlashiiBans().Wait(); } public void Add(ChatUser user, DateTimeOffset expires) { - if (expires <= DateTimeOffset.Now) + if(expires <= DateTimeOffset.Now) return; - lock (BanList) { + lock(BanList) { BannedUser ban = BanList.OfType().FirstOrDefault(x => x.UserId == user.UserId); - if (ban == null) + if(ban == null) Add(new BannedUser { UserId = user.UserId, Expires = expires, Username = user.Username }); else ban.Expires = expires; @@ -69,13 +74,13 @@ namespace SharpChat { } public void Add(IPAddress addr, DateTimeOffset expires) { - if (expires <= DateTimeOffset.Now) + if(expires <= DateTimeOffset.Now) return; - lock (BanList) { + lock(BanList) { BannedIPAddress ban = BanList.OfType().FirstOrDefault(x => x.Address.Equals(addr)); - if (ban == null) + if(ban == null) Add(new BannedIPAddress { Address = addr, Expires = expires }); else ban.Expires = expires; @@ -83,11 +88,11 @@ namespace SharpChat { } private void Add(IBan ban) { - if (ban == null) + if(ban == null) return; - lock (BanList) - if (!BanList.Contains(ban)) + lock(BanList) + if(!BanList.Contains(ban)) BanList.Add(ban); } @@ -102,12 +107,12 @@ namespace SharpChat { } public void Remove(IBan ban) { - lock (BanList) + lock(BanList) BanList.Remove(ban); } public DateTimeOffset Check(ChatUser user) { - if (user == null) + if(user == null) return DateTimeOffset.MinValue; lock(BanList) @@ -115,26 +120,26 @@ namespace SharpChat { } public DateTimeOffset Check(IPAddress addr) { - if (addr == null) + if(addr == null) return DateTimeOffset.MinValue; - lock (BanList) + lock(BanList) return BanList.OfType().Where(x => x.Address.Equals(addr)).FirstOrDefault()?.Expires ?? DateTimeOffset.MinValue; } public BannedUser GetUser(string username) { - if (username == null) + if(username == null) return null; - if (!long.TryParse(username, out long userId)) + if(!long.TryParse(username, out long userId)) userId = 0; - lock (BanList) + lock(BanList) return BanList.OfType().FirstOrDefault(x => x.Username.ToLowerInvariant() == username.ToLowerInvariant() || (userId > 0 && x.UserId == userId)); } public BannedIPAddress GetIPAddress(IPAddress addr) { - lock (BanList) + lock(BanList) return BanList.OfType().FirstOrDefault(x => x.Address.Equals(addr)); } @@ -143,47 +148,40 @@ namespace SharpChat { BanList.RemoveAll(x => x.Expires <= DateTimeOffset.Now); } - public void RefreshFlashiiBans() { - FlashiiBan.GetList(SockChatServer.HttpClient).ContinueWith(x => { - if(x.IsFaulted) { - Logger.Write($@"Ban Refresh: {x.Exception}"); - return; - } + public async Task RefreshFlashiiBans() { + IEnumerable bans = await FlashiiBan.GetListAsync(HttpClient); - if(!x.Result.Any()) - return; + if(!bans.Any()) + return; - lock(BanList) { - foreach(FlashiiBan fb in x.Result) { - if(!BanList.OfType().Any(x => x.UserId == fb.UserId)) - Add(new BannedUser(fb)); - if(!BanList.OfType().Any(x => x.Address.ToString() == fb.UserIP)) - Add(new BannedIPAddress(fb)); - } + lock(BanList) + foreach(FlashiiBan fb in bans) { + if(!BanList.OfType().Any(x => x.UserId == fb.UserId)) + Add(new BannedUser(fb)); + if(!BanList.OfType().Any(x => x.Address.ToString() == fb.UserIP)) + Add(new BannedIPAddress(fb)); } - }); } public IEnumerable All() { - lock (BanList) + lock(BanList) return BanList.ToList(); } ~BanManager() - => Dispose(false); + => DoDispose(); - public void Dispose() - => Dispose(true); + public void Dispose() { + DoDispose(); + GC.SuppressFinalize(this); + } - private void Dispose(bool disposing) { - if (IsDisposed) + private void DoDispose() { + if(IsDisposed) return; IsDisposed = true; BanList.Clear(); - - if (disposing) - GC.SuppressFinalize(this); } } } diff --git a/SharpChat/ChatContext.cs b/SharpChat/ChatContext.cs index 4b999c5..73da497 100644 --- a/SharpChat/ChatContext.cs +++ b/SharpChat/ChatContext.cs @@ -4,12 +4,15 @@ using SharpChat.Packet; using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Threading; namespace SharpChat { public class ChatContext : IDisposable, IPacketTarget { public bool IsDisposed { get; private set; } + private readonly HttpClient HttpClient; + public SockChatServer Server { get; } public Timer BumpTimer { get; } public BanManager Bans { get; } @@ -19,14 +22,15 @@ namespace SharpChat { public string TargetName => @"@broadcast"; - public ChatContext(SockChatServer server) { + public ChatContext(HttpClient httpClient, SockChatServer server) { + HttpClient = httpClient; Server = server; - Bans = new BanManager(this); + Bans = new BanManager(httpClient, this); Users = new UserManager(this); Channels = new ChannelManager(this); Events = new ChatEventManager(this); - BumpTimer = new Timer(e => FlashiiBump.Submit(SockChatServer.HttpClient, Users.WithActiveConnections()), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); + BumpTimer = new Timer(e => FlashiiBump.SubmitAsync(HttpClient, Users.WithActiveConnections()).Wait(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } public void Update() { @@ -166,13 +170,16 @@ namespace SharpChat { user.Send(packet); } - ~ChatContext() - => Dispose(false); + ~ChatContext() { + DoDispose(); + } - public void Dispose() - => Dispose(true); + public void Dispose() { + DoDispose(); + GC.SuppressFinalize(this); + } - private void Dispose(bool disposing) { + private void DoDispose() { if (IsDisposed) return; IsDisposed = true; @@ -182,9 +189,6 @@ namespace SharpChat { Channels?.Dispose(); Users?.Dispose(); Bans?.Dispose(); - - if (disposing) - GC.SuppressFinalize(this); } } } diff --git a/SharpChat/Flashii/FlashiiAuth.cs b/SharpChat/Flashii/FlashiiAuth.cs index 2e0f580..c3bdf7a 100644 --- a/SharpChat/Flashii/FlashiiAuth.cs +++ b/SharpChat/Flashii/FlashiiAuth.cs @@ -1,5 +1,4 @@ -using Microsoft.Win32.SafeHandles; -using System; +using System; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; @@ -49,7 +48,7 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"perms")] public ChatUserPermissions Permissions { get; set; } - public static async Task Attempt(HttpClient httpClient, FlashiiAuthRequest authRequest) { + public static async Task AttemptAsync(HttpClient httpClient, FlashiiAuthRequest authRequest) { if(httpClient == null) throw new ArgumentNullException(nameof(httpClient)); if(authRequest == null) @@ -68,7 +67,7 @@ namespace SharpChat.Flashii { }; #endif - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, FlashiiUrls.AuthURL) { + HttpRequestMessage request = new(HttpMethod.Post, FlashiiUrls.AuthURL) { Content = new ByteArrayContent(authRequest.GetJSON()), Headers = { { @"X-SharpChat-Signature", authRequest.Hash }, diff --git a/SharpChat/Flashii/FlashiiBan.cs b/SharpChat/Flashii/FlashiiBan.cs index d32ac66..c2ede72 100644 --- a/SharpChat/Flashii/FlashiiBan.cs +++ b/SharpChat/Flashii/FlashiiBan.cs @@ -21,11 +21,11 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"username")] public string Username { get; set; } - public static async Task> GetList(HttpClient httpClient) { + public static async Task> GetListAsync(HttpClient httpClient) { if(httpClient == null) throw new ArgumentNullException(nameof(httpClient)); - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, FlashiiUrls.BansURL) { + HttpRequestMessage request = new(HttpMethod.Get, FlashiiUrls.BansURL) { Headers = { { @"X-SharpChat-Signature", STRING.GetSignedHash() }, }, diff --git a/SharpChat/Flashii/FlashiiBump.cs b/SharpChat/Flashii/FlashiiBump.cs index 17ff15e..54809e9 100644 --- a/SharpChat/Flashii/FlashiiBump.cs +++ b/SharpChat/Flashii/FlashiiBump.cs @@ -14,14 +14,17 @@ namespace SharpChat.Flashii { [JsonPropertyName(@"ip")] public string UserIP { get; set; } - public static void Submit(HttpClient httpClient, IEnumerable users) { - List bups = users.Where(u => u.HasSessions).Select(x => new FlashiiBump { UserId = x.UserId, UserIP = x.RemoteAddresses.First().ToString() }).ToList(); + public static async Task SubmitAsync(HttpClient httpClient, IEnumerable users) { + FlashiiBump[] bups = users.Where(u => u.HasSessions).Select(x => new FlashiiBump { + UserId = x.UserId, + UserIP = x.RemoteAddresses.First().ToString() + }).ToArray(); - if (bups.Any()) - Submit(httpClient, bups); + if(bups.Any()) + await SubmitAsync(httpClient, bups); } - public static void Submit(HttpClient httpClient, IEnumerable users) { + public static async Task SubmitAsync(HttpClient httpClient, IEnumerable users) { if(httpClient == null) throw new ArgumentNullException(nameof(httpClient)); if(users == null) @@ -31,17 +34,14 @@ namespace SharpChat.Flashii { byte[] data = JsonSerializer.SerializeToUtf8Bytes(users); - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, FlashiiUrls.BumpURL) { + HttpRequestMessage request = new(HttpMethod.Post, FlashiiUrls.BumpURL) { Content = new ByteArrayContent(data), Headers = { { @"X-SharpChat-Signature", data.GetSignedHash() }, } }; - httpClient.SendAsync(request).ContinueWith(x => { - if(x.IsFaulted) - Logger.Write($@"Flashii Bump Error: {x.Exception}"); - }); + await httpClient.SendAsync(request); } } } diff --git a/SharpChat/Packet/LegacyCommandResponse.cs b/SharpChat/Packet/LegacyCommandResponse.cs index c4fb3fa..26f5141 100644 --- a/SharpChat/Packet/LegacyCommandResponse.cs +++ b/SharpChat/Packet/LegacyCommandResponse.cs @@ -68,6 +68,7 @@ namespace SharpChat.Packet { // Abbreviated class name because otherwise shit gets wide public static class LCR { + public const string GENERIC_ERROR = @"generr"; public const string COMMAND_NOT_FOUND = @"nocmd"; public const string COMMAND_NOT_ALLOWED = @"cmdna"; public const string COMMAND_FORMAT_ERROR = @"cmderr"; diff --git a/SharpChat/Program.cs b/SharpChat/Program.cs index b5f250b..d6bdad4 100644 --- a/SharpChat/Program.cs +++ b/SharpChat/Program.cs @@ -1,7 +1,5 @@ -using SharpChat.Flashii; -using System; -using System.Collections.Generic; -using System.Linq; +using System; +using System.Net.Http; using System.Threading; namespace SharpChat { @@ -21,9 +19,27 @@ namespace SharpChat { Database.ReadConfig(); - using ManualResetEvent mre = new ManualResetEvent(false); - using SockChatServer scs = new SockChatServer(mre, PORT); - Console.CancelKeyPress += (s, e) => { e.Cancel = true; mre.Set(); }; + using ManualResetEvent mre = new(false); + bool hasCancelled = false; + + void cancelKeyPressHandler(object sender, ConsoleCancelEventArgs ev) { + Console.CancelKeyPress -= cancelKeyPressHandler; + hasCancelled = true; + ev.Cancel = true; + mre.Set(); + }; + Console.CancelKeyPress += cancelKeyPressHandler; + + if(hasCancelled) return; + + using HttpClient httpClient = new(); + httpClient.DefaultRequestHeaders.Add(@"User-Agent", @"SharpChat/20230206"); + + if(hasCancelled) return; + + using SockChatServer scs = new(httpClient, PORT); + scs.Listen(mre); + mre.WaitOne(); } } diff --git a/SharpChat/SharpChat.csproj b/SharpChat/SharpChat.csproj index a0a87ea..a5c547c 100644 --- a/SharpChat/SharpChat.csproj +++ b/SharpChat/SharpChat.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6.0 diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs index 8398898..d0080da 100644 --- a/SharpChat/SockChatServer.cs +++ b/SharpChat/SockChatServer.cs @@ -9,7 +9,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -39,7 +38,7 @@ namespace SharpChat { public IWebSocketServer Server { get; } public ChatContext Context { get; } - public static HttpClient HttpClient { get; } + private readonly HttpClient HttpClient; private IReadOnlyCollection Commands { get; } = new IChatCommand[] { new AFKCommand(), @@ -53,21 +52,15 @@ namespace SharpChat { return Sessions.FirstOrDefault(x => x.Connection == conn); } - static SockChatServer() { - // "fuck it" - HttpClient = new HttpClient(); - HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(@"SharpChat"); - } - - private ManualResetEvent Shutdown { get; } + private ManualResetEvent Shutdown { get; set; } private bool IsShuttingDown = false; - public SockChatServer(ManualResetEvent mre, ushort port) { + public SockChatServer(HttpClient httpClient, ushort port) { Logger.Write("Starting Sock Chat server..."); - Shutdown = mre ?? throw new ArgumentNullException(nameof(mre)); + HttpClient = httpClient; - Context = new ChatContext(this); + Context = new ChatContext(HttpClient, this); Context.Channels.Add(new ChatChannel(@"Lounge")); #if DEBUG @@ -79,6 +72,10 @@ namespace SharpChat { Context.Channels.Add(new ChatChannel(@"Staff") { Rank = 5 }); Server = new SharpChatWebSocketServer($@"ws://0.0.0.0:{port}"); + } + + public void Listen(ManualResetEvent mre) { + Shutdown = mre; Server.Start(sock => { if(IsShuttingDown || IsDisposed) { @@ -143,7 +140,7 @@ namespace SharpChat { return; } - if(sess.User is ChatUser && sess.User.HasFloodProtection) { + if(sess.User is not null && sess.User.HasFloodProtection) { sess.User.RateLimiter.AddTimePoint(); if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) { @@ -182,7 +179,7 @@ namespace SharpChat { if(args.Length < 3 || !long.TryParse(args[1], out long aUserId)) break; - FlashiiAuth.Attempt(HttpClient, new FlashiiAuthRequest { + FlashiiAuth.AttemptAsync(HttpClient, new FlashiiAuthRequest { UserId = aUserId, Token = args[2], IPAddress = sess.RemoteAddress.ToString(), @@ -276,7 +273,7 @@ namespace SharpChat { } if(messageText.Length > MSG_LENGTH_MAX) - messageText = messageText.Substring(0, MSG_LENGTH_MAX); + messageText = messageText[..MSG_LENGTH_MAX]; messageText = messageText.Trim(); @@ -293,14 +290,13 @@ namespace SharpChat { break; } - if(message == null) - message = new ChatMessage { - Target = mChannel, - TargetName = mChannel.TargetName, - DateTime = DateTimeOffset.UtcNow, - Sender = mUser, - Text = messageText, - }; + message ??= new ChatMessage { + Target = mChannel, + TargetName = mChannel.TargetName, + DateTime = DateTimeOffset.UtcNow, + Sender = mUser, + Text = messageText, + }; Context.Events.Add(message); mChannel.Send(new ChatMessageAddPacket(message)); @@ -335,7 +331,7 @@ namespace SharpChat { } public IChatMessage HandleV1Command(string message, ChatUser user, ChatChannel channel) { - string[] parts = message.Substring(1).Split(' '); + string[] parts = message[1..].Split(' '); string commandName = parts[0].Replace(@".", string.Empty).ToLowerInvariant(); for(int i = 1; i < parts.Length; i++) @@ -370,8 +366,7 @@ namespace SharpChat { offset = 2; } - if(targetUser == null) - targetUser = user; + targetUser ??= user; if(parts.Length < offset) { user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); @@ -389,7 +384,7 @@ namespace SharpChat { if(nickStr == targetUser.Username) nickStr = null; else if(nickStr.Length > 15) - nickStr = nickStr.Substring(0, 15); + nickStr = nickStr[..15]; else if(string.IsNullOrEmpty(nickStr)) nickStr = null; @@ -454,7 +449,7 @@ namespace SharpChat { Flags = ChatMessageFlags.Action, }; case @"who": // gets all online users/online users in a channel if arg - StringBuilder whoChanSB = new StringBuilder(); + StringBuilder whoChanSB = new(); string whoChanStr = parts.Length > 1 && !string.IsNullOrEmpty(parts[1]) ? parts[1] : string.Empty; if(!string.IsNullOrEmpty(whoChanStr)) { @@ -476,7 +471,7 @@ namespace SharpChat { if(whoUser == user) whoChanSB.Append(@" style=""font-weight: bold;"""); - whoChanSB.Append(@">"); + whoChanSB.Append('>'); whoChanSB.Append(whoUser.DisplayName); whoChanSB.Append(@", "); } @@ -492,7 +487,7 @@ namespace SharpChat { if(whoUser == user) whoChanSB.Append(@" style=""font-weight: bold;"""); - whoChanSB.Append(@">"); + whoChanSB.Append('>'); whoChanSB.Append(whoUser.DisplayName); whoChanSB.Append(@", "); } @@ -546,7 +541,8 @@ namespace SharpChat { int createChanHierarchy = 0; if(createChanHasHierarchy) - int.TryParse(parts[1], out createChanHierarchy); + if(!int.TryParse(parts[1], out createChanHierarchy)) + createChanHierarchy = 0; if(createChanHierarchy > user.Rank) { user.Send(new LegacyCommandResponse(LCR.INSUFFICIENT_HIERARCHY)); @@ -554,7 +550,7 @@ namespace SharpChat { } string createChanName = string.Join('_', parts.Skip(createChanHasHierarchy ? 2 : 1)); - ChatChannel createChan = new ChatChannel { + ChatChannel createChan = new() { Name = createChanName, IsTemporary = !user.Can(ChatUserPermissions.SetChannelPermanent), Rank = createChanHierarchy, @@ -848,7 +844,7 @@ namespace SharpChat { Sessions.ForEach(s => s.PrepareForRestart()); Context.Update(); - Shutdown.Set(); + Shutdown?.Set(); break; default: @@ -859,13 +855,16 @@ namespace SharpChat { return null; } - ~SockChatServer() - => Dispose(false); + ~SockChatServer() { + DoDispose(); + } - public void Dispose() - => Dispose(true); + public void Dispose() { + DoDispose(); + GC.SuppressFinalize(this); + } - private void Dispose(bool disposing) { + private void DoDispose() { if(IsDisposed) return; IsDisposed = true; @@ -876,9 +875,6 @@ namespace SharpChat { Server?.Dispose(); Context?.Dispose(); HttpClient?.Dispose(); - - if(disposing) - GC.SuppressFinalize(this); } } }