diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 0d6ed67767..1de6280531 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -131,6 +131,42 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(11, result.Links[0].Length); } + [Test] + public void TestNewFormatLinkWithEscapedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" }); + + Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(41, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLinkWithBackslashesInside() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" }); + + Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(27, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLinkWithEscapedAndBalancedBrackets() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" }); + + Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + [Test] public void TestMarkdownFormatLink() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index c18e0e3064..056ccafe79 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -127,6 +127,7 @@ namespace osu.Game.Tests.Visual.Online addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); + addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", expectedActions: LinkAction.External); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index db26945ef3..a9fffc196c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace osu.Game.Online.Chat @@ -16,7 +17,7 @@ namespace osu.Game.Online.Chat private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) - private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ((((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]"); // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); @@ -48,7 +49,7 @@ namespace osu.Game.Online.Chat // Unicode emojis private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); - private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) { int captureOffset = 0; @@ -68,6 +69,10 @@ namespace osu.Game.Online.Chat if (displayText.Length == 0 || linkText.Length == 0) continue; + // Remove backslash escapes in front of the characters provided in escapeChars + if (escapeChars != null) + displayText = escapeChars.Aggregate(displayText, (current, c) => current.Replace($"\\{c}", c.ToString())); + // Check for encapsulated links if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null) { @@ -183,7 +188,7 @@ namespace osu.Game.Online.Chat var result = new MessageFormatterResult(toFormat); // handle the [link display] format - handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); + handleMatches(new_link_regex, "{2}", "{1}", result, startIndex, escapeChars: new[] { '[', ']' }); // handle the standard markdown []() format handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex);