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 e718696ae3..71fa9625a6 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs
@@ -7,6 +7,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;
@@ -123,7 +124,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/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs
index 098e047981..acfa4ffe6f 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs
@@ -31,4 +31,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);
+
}