From 7b96b52217606fdf872e23e3899987940e09afd5 Mon Sep 17 00:00:00 2001 From: Infi Date: Wed, 28 Aug 2024 12:42:01 +0200 Subject: [PATCH] Use QueryParser for redirect and expose method for new clients --- ChatTwo/Http/Processing.cs | 13 ++++++++++++- ChatTwo/Http/RouteController.cs | 23 ++++++++--------------- ChatTwo/Http/SSEConnection.cs | 6 +----- ChatTwo/Http/ServerCore.cs | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs index 7d87ae0..71b5271 100644 --- a/ChatTwo/Http/Processing.cs +++ b/ChatTwo/Http/Processing.cs @@ -16,7 +16,7 @@ public class Processing Plugin = plugin; } - public string ReadChannelName(Chunk[] channelName) + internal string ReadChannelName(Chunk[] channelName) { return string.Join("", channelName.Select(chunk => ProcessChunk(chunk, noColor: true))); } @@ -44,6 +44,17 @@ public class Processing return response; } + internal async Task PrepareNewClient(SSEConnection sse) + { + var messages = await WebserverUtil.FrameworkWrapper(ReadMessageList); + var channels = await Plugin.Framework.RunOnTick(Plugin.ChatLogWindow.GetAvailableChannels); + var channelName = await Plugin.Framework.RunOnTick(() => ReadChannelName(Plugin.ChatLogWindow.PreviousChannel)); + + sse.OutboundQueue.Enqueue(new NewMessageEvent(new Messages(messages))); + sse.OutboundQueue.Enqueue(new SwitchChannelEvent(new SwitchChannel(channelName))); + sse.OutboundQueue.Enqueue(new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value)))); + } + private string ProcessChunk(Chunk chunk, bool noColor = false) { if (chunk is IconChunk { } icon) diff --git a/ChatTwo/Http/RouteController.cs b/ChatTwo/Http/RouteController.cs index 1d8471f..c351e14 100644 --- a/ChatTwo/Http/RouteController.cs +++ b/ChatTwo/Http/RouteController.cs @@ -125,14 +125,14 @@ public class RouteController { var currentTick = Environment.TickCount64; if (RateLimit.TryGetValue(ctx.Request.Source.IpAddress, out var timestamp) && timestamp > currentTick) - return await Redirect(ctx, "/", "message", "Rate limit active."); + return await Redirect(ctx, "/", ("message", "Rate limit active."), ("retry", "12345")); // The next request will be rate limited for 10s RateLimit[ctx.Request.Source.IpAddress] = currentTick + 10_000; var authcode = HttpUtility.ParseQueryString(ctx.Request.DataAsString ?? "").Get("authcode"); if (authcode == null || authcode != Plugin.Config.WebinterfacePassword) - return await Redirect(ctx, "/", "message", "Authentication failed."); + return await Redirect(ctx, "/", ("message", "Authentication failed.")); var token = WebinterfaceUtil.GenerateSimpleToken(); SessionTokens.TryAdd(token, true); @@ -208,16 +208,9 @@ public class RouteController Plugin.Log.Information($"Client connected: {ctx.Guid}"); var sse = new SSEConnection(Core.TokenSource.Token); + await Core.Processing.PrepareNewClient(sse); Core.EventConnections.Add(sse); - // TODO Check if reconnect or new connection - var messages = await WebserverUtil.FrameworkWrapper(Core.Processing.ReadMessageList); - var channels = await Plugin.Framework.RunOnTick(Plugin.ChatLogWindow.GetAvailableChannels); - var channelName = await Plugin.Framework.RunOnTick(() => Core.Processing.ReadChannelName(Plugin.ChatLogWindow.PreviousChannel)); - sse.OutboundQueue.Enqueue(new NewMessageEvent(new Messages(messages))); - sse.OutboundQueue.Enqueue(new SwitchChannelEvent(new SwitchChannel(channelName))); - sse.OutboundQueue.Enqueue(new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value)))); - await sse.HandleEventLoop(ctx); // It should always be done after return @@ -232,13 +225,13 @@ public class RouteController #endregion #region RedirectHelper - public static async Task Redirect(HttpContextBase ctx, string location, params string[] parameter) + public static async Task Redirect(HttpContextBase ctx, string location, params (string, string)[] parameter) { - var query = "?"; - foreach (var (content, index) in parameter.WithIndex()) - query += index % 2 == 0 ? $"{content}=" : Uri.EscapeDataString(content); + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (var (key, value) in parameter) + query.Add(key, value); - ctx.Response.Headers.Add("Location", $"{location}{query}"); + ctx.Response.Headers.Add("Location", $"{location}?{query}"); ctx.Response.StatusCode = 302; return await ctx.Response.Send(); } diff --git a/ChatTwo/Http/SSEConnection.cs b/ChatTwo/Http/SSEConnection.cs index 535e9a2..11e5d66 100644 --- a/ChatTwo/Http/SSEConnection.cs +++ b/ChatTwo/Http/SSEConnection.cs @@ -1,12 +1,10 @@ -using System.Text; -using ChatTwo.Http.MessageProtocol; +using ChatTwo.Http.MessageProtocol; using WatsonWebserver.Core; namespace ChatTwo.Http; public class SSEConnection { - private long Index; private bool Stopping; private readonly CancellationToken Token; @@ -42,8 +40,6 @@ public class SSEConnection Plugin.Log.Information($"Client disconnected: {ctx.Guid}"); return; } - - Index++; } } catch (TaskCanceledException) diff --git a/ChatTwo/Http/ServerCore.cs b/ChatTwo/Http/ServerCore.cs index 3c9d850..5c757ae 100644 --- a/ChatTwo/Http/ServerCore.cs +++ b/ChatTwo/Http/ServerCore.cs @@ -124,13 +124,13 @@ public class ServerCore : IAsyncDisposable { if (RouteController.SessionTokens.IsEmpty) { - await RouteController.Redirect(ctx, "/", "message", "Invalid session token."); + await RouteController.Redirect(ctx, "/", ("message", "Invalid session token.")); return; } var cookies = WebserverUtil.GetCookieData(ctx.Request.Headers.Get("Cookie") ?? ""); if (!cookies.TryGetValue("ChatTwo-token", out var token) || !RouteController.SessionTokens.ContainsKey(token)) - await RouteController.Redirect(ctx, "/", "message", "Invalid session token."); + await RouteController.Redirect(ctx, "/", ("message", "Invalid session token.")); // Do nothing to let auth pass }