From 3ce438399af2db8a245a653da96f46d94fa8a539 Mon Sep 17 00:00:00 2001 From: Matthew-Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:03:42 +0000 Subject: [PATCH] Fix UriUtilityCore's handling of anchors and querystrings, also optimized with Span (#15678) --- src/Umbraco.Core/UriUtilityCore.cs | 30 +++++++++-------- .../CoreThings/UriUtilityCoreTests.cs | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UriUtilityCoreTests.cs diff --git a/src/Umbraco.Core/UriUtilityCore.cs b/src/Umbraco.Core/UriUtilityCore.cs index e85e49df54..67e978b8d5 100644 --- a/src/Umbraco.Core/UriUtilityCore.cs +++ b/src/Umbraco.Core/UriUtilityCore.cs @@ -4,8 +4,6 @@ namespace Umbraco.Cms.Core; public static class UriUtilityCore { - #region Uri string utilities - public static bool HasScheme(string uri) => uri.IndexOf("://", StringComparison.InvariantCulture) > 0; public static string StartWithScheme(string uri) => StartWithScheme(uri, null); @@ -15,16 +13,15 @@ public static class UriUtilityCore public static string EndPathWithSlash(string uri) { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); + ReadOnlySpan uriSpan = uri.AsSpan(); + var pos = IndexOfPathEnd(uriSpan); - var path = pos > 0 ? uri.Substring(0, pos) : uri; + var path = (pos > 0 ? uriSpan[..pos] : uriSpan).ToString(); path = path.EnsureEndsWith('/'); if (pos > 0) { - path += uri.Substring(pos); + return string.Concat(path, uriSpan[pos..]); } return path; @@ -32,20 +29,27 @@ public static class UriUtilityCore public static string TrimPathEndSlash(string uri) { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); + ReadOnlySpan uriSpan = uri.AsSpan(); + var pos = IndexOfPathEnd(uriSpan); - var path = pos > 0 ? uri[..pos] : uri; + var path = (pos > 0 ? uriSpan[..pos] : uriSpan).ToString(); path = path.TrimEnd(Constants.CharArrays.ForwardSlash); if (pos > 0) { - path += uri.Substring(pos); + return string.Concat(path, uriSpan[pos..]); } return path; } - #endregion + private static int IndexOfPathEnd(ReadOnlySpan uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + return pos1 == 0 && pos2 == 0 ? 0 + : pos1 == 0 ? pos2 + : pos2 == 0 ? pos1 + : Math.Min(pos1, pos2); + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UriUtilityCoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UriUtilityCoreTests.cs new file mode 100644 index 0000000000..6045336477 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UriUtilityCoreTests.cs @@ -0,0 +1,33 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.CoreThings; + +[TestFixture] +public class UriUtilityCoreTests +{ + [TestCase("/en", "/en")] + [TestCase("/en#anchor", "/en#anchor")] + [TestCase("/en/", "/en")] + [TestCase("/en/#anchor", "/en#anchor")] + [TestCase("/en/?abc=123", "/en?abc=123")] + [TestCase("/en/#abc?abc=123", "/en#abc?abc=123")] + public void TrimPathEndSlash(string uri, string expected) + { + var result = UriUtilityCore.TrimPathEndSlash(uri); + Assert.AreEqual(expected, result); + } + + + [TestCase("/en/", "/en/")] + [TestCase("/en#anchor", "/en/#anchor")] + [TestCase("/en", "/en/")] + [TestCase("/en/#anchor", "/en/#anchor")] + [TestCase("/en?abc=123", "/en/?abc=123")] + [TestCase("/en#abc?abc=123", "/en/#abc?abc=123")] + public void EndPathWithSlash(string uri, string expected) + { + var result = UriUtilityCore.EndPathWithSlash(uri); + Assert.AreEqual(expected, result); + } +}