From 352940abbd9b0257c614bd9c23a3aa61eb4f5b61 Mon Sep 17 00:00:00 2001 From: Ske Date: Wed, 10 Jul 2019 13:44:03 +0200 Subject: [PATCH] Sanitize user input in response messages --- PluralKit.Bot/Commands/MemberCommands.cs | 10 +++++----- PluralKit.Bot/Commands/MiscCommands.cs | 1 - PluralKit.Bot/Commands/ModCommands.cs | 2 +- PluralKit.Bot/Commands/SwitchCommands.cs | 8 ++++---- PluralKit.Bot/Commands/SystemCommands.cs | 18 ++++++++++++------ PluralKit.Bot/Errors.cs | 20 ++++++++++---------- PluralKit.Bot/Utils.cs | 4 ++++ 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/PluralKit.Bot/Commands/MemberCommands.cs b/PluralKit.Bot/Commands/MemberCommands.cs index ba9568e6..4ade46c4 100644 --- a/PluralKit.Bot/Commands/MemberCommands.cs +++ b/PluralKit.Bot/Commands/MemberCommands.cs @@ -38,7 +38,7 @@ namespace PluralKit.Bot.Commands // Warn if there's already a member by this name var existingMember = await Members.GetByName(Context.SenderSystem, memberName); if (existingMember != null) { - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?"); + var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.Sanitize()}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?"); if (!await Context.PromptYesNo(msg)) throw new PKError("Member creation cancelled."); } @@ -46,7 +46,7 @@ namespace PluralKit.Bot.Commands var member = await Members.Create(Context.SenderSystem, memberName); // Send confirmation and space hint - await Context.Channel.SendMessageAsync($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Type `pk;help member` for a list of commands to edit this member."); + await Context.Channel.SendMessageAsync($"{Emojis.Success} Member \"{memberName.Sanitize()}\" (`{member.Hid}`) registered! Type `pk;help member` for a list of commands to edit this member."); if (memberName.Contains(" ")) await Context.Channel.SendMessageAsync($"{Emojis.Note} Note that this member's name contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it."); } @@ -69,7 +69,7 @@ namespace PluralKit.Bot.Commands // Warn if there's already a member by this name var existingMember = await Members.GetByName(Context.SenderSystem, newName); if (existingMember != null) { - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?"); + var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.Sanitize()}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?"); if (!await Context.PromptYesNo(msg)) throw new PKError("Member renaming cancelled."); } @@ -170,7 +170,7 @@ namespace PluralKit.Bot.Commands ContextEntity.Prefix = prefixAndSuffix[0].Length > 0 ? prefixAndSuffix[0] : null; ContextEntity.Suffix = prefixAndSuffix[1].Length > 0 ? prefixAndSuffix[1] : null; await Members.Save(ContextEntity); - await Context.Channel.SendMessageAsync($"{Emojis.Success} Member proxy tags changed to `{ContextEntity.ProxyString}`. Try proxying now!"); + await Context.Channel.SendMessageAsync($"{Emojis.Success} Member proxy tags changed to `{ContextEntity.ProxyString.Sanitize()}`. Try proxying now!"); } [Command("delete")] @@ -179,7 +179,7 @@ namespace PluralKit.Bot.Commands [MustPassOwnMember] public async Task MemberDelete() { - await Context.Channel.SendMessageAsync($"{Emojis.Warn} Are you sure you want to delete \"{ContextEntity.Name}\"? If so, reply to this message with the member's ID (`{ContextEntity.Hid}`). __***This cannot be undone!***__"); + await Context.Channel.SendMessageAsync($"{Emojis.Warn} Are you sure you want to delete \"{ContextEntity.Name.Sanitize()}\"? If so, reply to this message with the member's ID (`{ContextEntity.Hid}`). __***This cannot be undone!***__"); if (!await Context.ConfirmWithReply(ContextEntity.Hid)) throw Errors.MemberDeleteCancelled; await Members.Delete(ContextEntity); await Context.Channel.SendMessageAsync($"{Emojis.Success} Member deleted."); diff --git a/PluralKit.Bot/Commands/MiscCommands.cs b/PluralKit.Bot/Commands/MiscCommands.cs index 13b1e29c..08890101 100644 --- a/PluralKit.Bot/Commands/MiscCommands.cs +++ b/PluralKit.Bot/Commands/MiscCommands.cs @@ -21,7 +21,6 @@ namespace PluralKit.Bot.Commands { sendMessages: true ); - // TODO: allow customization of invite ID var invite = $"https://discordapp.com/oauth2/authorize?client_id={clientId}&scope=bot&permissions={permissions.RawValue}"; await Context.Channel.SendMessageAsync($"{Emojis.Success} Use this link to add PluralKit to your server:\n<{invite}>"); } diff --git a/PluralKit.Bot/Commands/ModCommands.cs b/PluralKit.Bot/Commands/ModCommands.cs index 9d1071fa..08be09b9 100644 --- a/PluralKit.Bot/Commands/ModCommands.cs +++ b/PluralKit.Bot/Commands/ModCommands.cs @@ -20,7 +20,7 @@ namespace PluralKit.Bot.Commands await LogChannels.SetLogChannel(Context.Guild, channel); if (channel != null) - await Context.Channel.SendMessageAsync($"{Emojis.Success} Proxy logging channel set to #{channel.Name}."); + await Context.Channel.SendMessageAsync($"{Emojis.Success} Proxy logging channel set to #{channel.Name.Sanitize()}."); else await Context.Channel.SendMessageAsync($"{Emojis.Success} Proxy logging channel cleared."); } diff --git a/PluralKit.Bot/Commands/SwitchCommands.cs b/PluralKit.Bot/Commands/SwitchCommands.cs index c2f34418..a118ea61 100644 --- a/PluralKit.Bot/Commands/SwitchCommands.cs +++ b/PluralKit.Bot/Commands/SwitchCommands.cs @@ -52,7 +52,7 @@ namespace PluralKit.Bot.Commands if (members.Count == 0) await Context.Channel.SendMessageAsync($"{Emojis.Success} Switch-out registered."); else - await Context.Channel.SendMessageAsync($"{Emojis.Success} Switch registered. Current fronter is now {string.Join(", ", members.Select(m => m.Name))}."); + await Context.Channel.SendMessageAsync($"{Emojis.Success} Switch registered. Current fronter is now {string.Join(", ", members.Select(m => m.Name)).Sanitize()}."); } [Command("move")] @@ -91,7 +91,7 @@ namespace PluralKit.Bot.Commands var newSwitchDeltaStr = Formats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - time.ToInstant()); // yeet - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr}) from {lastSwitchTimeStr} ({lastSwitchDeltaStr} ago) to {newSwitchTimeStr} ({newSwitchDeltaStr} ago). Is this OK?"); + var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr.Sanitize()}) from {lastSwitchTimeStr} ({lastSwitchDeltaStr} ago) to {newSwitchTimeStr} ({newSwitchDeltaStr} ago). Is this OK?"); if (!await Context.PromptYesNo(msg)) throw Errors.SwitchMoveCancelled; // aaaand *now* we do the move @@ -116,7 +116,7 @@ namespace PluralKit.Bot.Commands if (lastTwoSwitches.Length == 1) { msg = await Context.Channel.SendMessageAsync( - $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago). You have no other switches logged. Is this okay?"); + $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr.Sanitize()}, {lastSwitchDeltaStr} ago). You have no other switches logged. Is this okay?"); } else { @@ -124,7 +124,7 @@ namespace PluralKit.Bot.Commands var secondSwitchMemberStr = string.Join(", ", secondSwitchMembers.Select(m => m.Name)); var secondSwitchDeltaStr = Formats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[1].Timestamp); msg = await Context.Channel.SendMessageAsync( - $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago). The next latest switch is {secondSwitchMemberStr} ({secondSwitchDeltaStr} ago). Is this okay?"); + $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr.Sanitize()}, {lastSwitchDeltaStr} ago). The next latest switch is {secondSwitchMemberStr.Sanitize()} ({secondSwitchDeltaStr} ago). Is this okay?"); } if (!await Context.PromptYesNo(msg)) throw Errors.SwitchDeleteCancelled; diff --git a/PluralKit.Bot/Commands/SystemCommands.cs b/PluralKit.Bot/Commands/SystemCommands.cs index b3326551..d2894def 100644 --- a/PluralKit.Bot/Commands/SystemCommands.cs +++ b/PluralKit.Bot/Commands/SystemCommands.cs @@ -74,15 +74,21 @@ namespace PluralKit.Bot.Commands [Remarks("system tag ")] [MustHaveSystem] public async Task Tag([Remainder] string newTag = null) { - if (newTag.Length > Limits.MaxSystemTagLength) throw Errors.SystemNameTooLongError(newTag.Length); Context.SenderSystem.Tag = newTag; - // Check unproxyable messages *after* changing the tag (so it's seen in the method) but *before* we save to DB (so we can cancel) - var unproxyableMembers = await Members.GetUnproxyableMembers(Context.SenderSystem); - if (unproxyableMembers.Count > 0) { - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} Changing your system tag to '{newTag}' will result in the following members being unproxyable, since the tag would bring their name over 32 characters:\n**{string.Join(", ", unproxyableMembers.Select((m) => m.Name))}**\nDo you want to continue anyway?"); - if (!await Context.PromptYesNo(msg)) throw new PKError("Tag change cancelled."); + if (newTag != null) + { + if (newTag.Length > Limits.MaxSystemTagLength) throw Errors.SystemNameTooLongError(newTag.Length); + + // Check unproxyable messages *after* changing the tag (so it's seen in the method) but *before* we save to DB (so we can cancel) + var unproxyableMembers = await Members.GetUnproxyableMembers(Context.SenderSystem); + if (unproxyableMembers.Count > 0) + { + var msg = await Context.Channel.SendMessageAsync( + $"{Emojis.Warn} Changing your system tag to '{newTag}' will result in the following members being unproxyable, since the tag would bring their name over 32 characters:\n**{string.Join(", ", unproxyableMembers.Select((m) => m.Name))}**\nDo you want to continue anyway?"); + if (!await Context.PromptYesNo(msg)) throw new PKError("Tag change cancelled."); + } } await Systems.Save(Context.SenderSystem); diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index 5ab98ffc..f9004e63 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -22,15 +22,15 @@ namespace PluralKit.Bot { public static PKError MemberNameTooLongError(int length) => new PKError($"Member name too long ({length}/{Limits.MaxMemberNameLength} characters)."); public static PKError MemberPronounsTooLongError(int length) => new PKError($"Member pronouns too long ({length}/{Limits.MaxMemberNameLength} characters)."); - public static PKError InvalidColorError(string color) => new PKError($"\"{color}\" is not a valid color. Color must be in 6-digit RGB hex format (eg. #ff0000)."); - public static PKError BirthdayParseError(string birthday) => new PKError($"\"{birthday}\" could not be parsed as a valid date. Try a format like \"2016-12-24\" or \"May 3 1996\"."); + public static PKError InvalidColorError(string color) => new PKError($"\"{color.Sanitize()}\" is not a valid color. Color must be in 6-digit RGB hex format (eg. #ff0000)."); + public static PKError BirthdayParseError(string birthday) => new PKError($"\"{birthday.Sanitize()}\" could not be parsed as a valid date. Try a format like \"2016-12-24\" or \"May 3 1996\"."); public static PKError ProxyMustHaveText => new PKSyntaxError("Example proxy message must contain the string 'text'."); public static PKError ProxyMultipleText => new PKSyntaxError("Example proxy message must contain the string 'text' exactly once."); public static PKError MemberDeleteCancelled => new PKError($"Member deletion cancelled. Stay safe! {Emojis.ThumbsUp}"); public static PKError AvatarServerError(HttpStatusCode statusCode) => new PKError($"Server responded with status code {(int) statusCode}, are you sure your link is working?"); public static PKError AvatarFileSizeLimit(long size) => new PKError($"File size too large ({size.Bytes().ToString("#.#")} > {Limits.AvatarFileSizeLimit.Bytes().ToString("#.#")}), try shrinking or compressing the image."); - public static PKError AvatarNotAnImage(string mimeType) => new PKError($"The given link does not point to an image{(mimeType != null ? $" ({mimeType})" : "")}. Make sure you're using a direct link (ending in .jpg, .png, .gif)."); + public static PKError AvatarNotAnImage(string mimeType) => new PKError($"The given link does not point to an image{(mimeType != null ? $" ({mimeType.Sanitize()})" : "")}. Make sure you're using a direct link (ending in .jpg, .png, .gif)."); public static PKError AvatarDimensionsTooLarge(int width, int height) => new PKError($"Image too large ({width}x{height} > {Limits.AvatarDimensionLimit}x{Limits.AvatarDimensionLimit}), try resizing the image."); public static PKError InvalidUrl(string url) => new PKError($"The given URL is invalid."); @@ -44,30 +44,30 @@ namespace PluralKit.Bot { public static PKError SameSwitch(ICollection members) { if (members.Count == 0) return new PKError("There's already no one in front."); - if (members.Count == 1) return new PKError($"Member {members.First().Name} is already fronting."); - return new PKError($"Members {string.Join(", ", members.Select(m => m.Name))} are already fronting."); + if (members.Count == 1) return new PKError($"Member {members.First().Name.Sanitize()} is already fronting."); + return new PKError($"Members {string.Join(", ", members.Select(m => m.Name.Sanitize()))} are already fronting."); } public static PKError DuplicateSwitchMembers => new PKError("Duplicate members in member list."); public static PKError SwitchMemberNotInSystem => new PKError("One or more switch members aren't in your own system."); - public static PKError InvalidDateTime(string str) => new PKError($"Could not parse '{str}' as a valid date/time. Try using a syntax such as \"May 21, 12:30 PM\" or \"3d12h\" (ie. 3 days, 12 hours ago)."); + public static PKError InvalidDateTime(string str) => new PKError($"Could not parse '{str.Sanitize()}' as a valid date/time. Try using a syntax such as \"May 21, 12:30 PM\" or \"3d12h\" (ie. 3 days, 12 hours ago)."); public static PKError SwitchTimeInFuture => new PKError("Can't move switch to a time in the future."); public static PKError NoRegisteredSwitches => new PKError("There are no registered switches for this system."); public static PKError SwitchMoveBeforeSecondLast(ZonedDateTime time) => new PKError($"Can't move switch to before last switch time ({Formats.ZonedDateTimeFormat.Format(time)}), as it would cause conflicts."); public static PKError SwitchMoveCancelled => new PKError("Switch move cancelled."); public static PKError SwitchDeleteCancelled => new PKError("Switch deletion cancelled."); - public static PKError TimezoneParseError(string timezone) => new PKError($"Could not parse timezone offset {timezone}. Offset must be a value like 'UTC+5' or 'GMT-4:30'."); + public static PKError TimezoneParseError(string timezone) => new PKError($"Could not parse timezone offset {timezone.Sanitize()}. Offset must be a value like 'UTC+5' or 'GMT-4:30'."); - public static PKError InvalidTimeZone(string zoneStr) => new PKError($"Invalid time zone ID '{zoneStr}'. To find your time zone ID, use the following website: "); + public static PKError InvalidTimeZone(string zoneStr) => new PKError($"Invalid time zone ID '{zoneStr.Sanitize()}'. To find your time zone ID, use the following website: "); public static PKError TimezoneChangeCancelled => new PKError("Time zone change cancelled."); - public static PKError AmbiguousTimeZone(string zoneStr, int count) => new PKError($"The time zone query '{zoneStr}' resulted in **{count}** different time zone regions. Try being more specific - e.g. pass an exact time zone specifier from the following website: "); + public static PKError AmbiguousTimeZone(string zoneStr, int count) => new PKError($"The time zone query '{zoneStr.Sanitize()}' resulted in **{count}** different time zone regions. Try being more specific - e.g. pass an exact time zone specifier from the following website: "); public static PKError NoImportFilePassed => new PKError("You must either pass an URL to a file as a command parameter, or as an attachment to the message containing the command."); public static PKError InvalidImportFile => new PKError("Imported data file invalid. Make sure this is a .json file directly exported from PluralKit or Tupperbox."); public static PKError ImportCancelled => new PKError("Import cancelled."); public static PKError MessageNotFound(ulong id) => new PKError($"Message with ID '{id}' not found. Are you sure it's a message proxied by PluralKit?"); - public static PKError DurationParseError(string durationStr) => new PKError($"Could not parse '{durationStr}' as a valid duration. Try a format such as `30d`, `1d3h` or `20m30s`."); + public static PKError DurationParseError(string durationStr) => new PKError($"Could not parse '{durationStr.Sanitize()}' as a valid duration. Try a format such as `30d`, `1d3h` or `20m30s`."); } } \ No newline at end of file diff --git a/PluralKit.Bot/Utils.cs b/PluralKit.Bot/Utils.cs index f55458ed..341541b6 100644 --- a/PluralKit.Bot/Utils.cs +++ b/PluralKit.Bot/Utils.cs @@ -4,6 +4,7 @@ using System.Data; using System.Globalization; using System.Linq; using System.Net.Http; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Dapper; using Discord; @@ -82,6 +83,9 @@ namespace PluralKit.Bot argPos = num + 2; return true; } + + public static string Sanitize(this string input) => + Regex.Replace(Regex.Replace(input, "<@[!&]?(\\d{17,19})>", "<\\@$1>"), "@(everyone|here)", "@\u200B$1"); } class PKSystemTypeReader : TypeReader