diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index 7ecafff8a3..47c6b15871 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -50,4 +50,28 @@ public class WebPath return sb.ToString(); } + + + /// + /// Determines whether the provided web path is well-formed according to the specified UriKind. + /// + /// The web path to check. This can be null. + /// The kind of Uri (Absolute, Relative, or RelativeOrAbsolute). + /// + /// true if is well-formed; otherwise, false. + /// + public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind) + { + if (string.IsNullOrWhiteSpace(webPath)) + { + return false; + } + + if (webPath.StartsWith("//")) + { + return uriKind is not UriKind.Relative; + } + + return Uri.IsWellFormedUriString(webPath, uriKind); + } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 90ef6e6cf4..b70661c0af 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; @@ -122,7 +123,7 @@ public class ImagesController : UmbracoAuthorizedApiController private bool IsAllowed(string encodedImagePath) { - if(Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) + if(WebPath.IsWellFormedWebPath(encodedImagePath, UriKind.Relative)) { return true; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 19ca323d9d..17875c2950 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -152,8 +153,7 @@ public class PreviewController : Controller // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. _cookieManager.ExpireCookie(Constants.Web.AcceptPreviewCookieName); - if (Uri.IsWellFormedUriString(redir, UriKind.Relative) - && redir.StartsWith("//") == false + if (WebPath.IsWellFormedWebPath(redir, UriKind.Relative) && Uri.TryCreate(redir, UriKind.Relative, out Uri? url)) { return Redirect(url.ToString()); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index 69c11a11cc..3d412d34e1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -88,26 +88,27 @@ // If an infinite editor is being closed then we reset the focus to the element that triggered the the overlay if(closingEditor){ - // If there is only one editor open, search for the "editor-info" inside it and set focus on it // This is relevant when a property editor has been selected and the editor where we selected it from // is closed taking us back to the first layer // Otherwise set it to the last element in the lastKnownFocusedElements array - if(infiniteEditors && infiniteEditors.length === 1){ - var editorInfo = infiniteEditors[0].querySelector('.editor-info'); - if(infiniteEditors && infiniteEditors.length === 1 && editorInfo !== null) { - lastKnownElement = editorInfo; - // Clear the array - clearLastKnownFocusedElements(); - } + var editorInfo = (infiniteEditors && infiniteEditors.length === 1) + ? infiniteEditors[0].querySelector('.editor-info') + : null; + + if(editorInfo !== null){ + lastKnownElement = editorInfo; + + // Clear the array + clearLastKnownFocusedElements(); } - else { - var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1; - lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex]; + else{ + var lastIndex = $rootScope.lastKnownFocusableElements.length - 1; + lastKnownElement = $rootScope.lastKnownFocusableElements[lastIndex]; - // Remove the last item from the array so we always set the correct lastKnowFocus for each layer - $rootScope.lastKnownFocusableElements.splice(lastItemIndex, 1); + // Remove the last item from the array so we always set the correct lastKnowFocus for each layer + $rootScope.lastKnownFocusableElements.splice(lastIndex, 1); } // Update the lastknowelement variable here diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs index 4072e3df8b..72ab9150bc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs @@ -30,4 +30,87 @@ public class WebPathTests [Test] public void Combine_must_handle_null() => Assert.Throws(() => WebPath.Combine(null)); + + + [Test] + [TestCase("ftp://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.Absolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.Absolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.Absolute, ExpectedResult = false)] + [TestCase("/test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("test", UriKind.Absolute, ExpectedResult = false)] + [TestCase("", UriKind.Absolute, ExpectedResult = false)] + [TestCase(null, UriKind.Absolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Absolute, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("file:///hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("ws://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("wss://hello.com/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080/path", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com:8080", UriKind.Relative, ExpectedResult = false)] + [TestCase("//hello.com", UriKind.Relative, ExpectedResult = false)] + [TestCase("/test/test.jpg", UriKind.Relative, ExpectedResult = true)] + [TestCase("/test", UriKind.Relative, ExpectedResult = true)] + [TestCase("test", UriKind.Relative, ExpectedResult = true)] + [TestCase("", UriKind.Relative, ExpectedResult = false)] + [TestCase(null, UriKind.Relative, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.Relative, ExpectedResult = false)] + [TestCase("ftp://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("file:///hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("ws://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("wss://hello.com/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("https://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("http://hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path?query=param#fragment", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080/path", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com:8080", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("//hello.com", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test/test.jpg", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("/test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("test", UriKind.RelativeOrAbsolute, ExpectedResult = true)] + [TestCase("", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase(null, UriKind.RelativeOrAbsolute, ExpectedResult = false)] + [TestCase("this is not welformed", UriKind.RelativeOrAbsolute, ExpectedResult = false)] + public bool IsWellFormedWebPath(string? webPath, UriKind uriKind) => WebPath.IsWellFormedWebPath(webPath, uriKind); + }