diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 0fbd795878..0ebe65c1ad 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -51,7 +51,23 @@ namespace Umbraco.Core.IO return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root); } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] + public static Attempt TryResolveUrl(string virtualPath) + { + try + { + if (virtualPath.StartsWith("~")) + return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/")); + if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) + return Attempt.Succeed(virtualPath); + return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root)); + } + catch (Exception ex) + { + return Attempt.Fail(virtualPath, ex); + } + } + + [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] internal static string ResolveUrlsFromTextString(string text) { if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 33bd83fb26..d71428d301 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Configuration; using System.Web.Security; using Umbraco.Core.Strings; using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.IO; namespace Umbraco.Core { @@ -59,6 +60,50 @@ namespace Umbraco.Core } + /// + /// Based on the input string, this will detect if the strnig is a JS path or a JS snippet. + /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned + /// with an invalid attempt, otherwise a valid attempt is returned with the resolved path + /// + /// + /// + /// + /// This is only used for legacy purposes for the Action.JsSource stuff and shouldn't be needed in v8 + /// + internal static Attempt DetectIsJavaScriptPath(this string input) + { + //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text + //block instead. + var isValid = true; + + if (Uri.IsWellFormedUriString(input, UriKind.RelativeOrAbsolute)) + { + //ok it validates, but so does alert('hello'); ! so we need to do more checks + + //here are the valid chars in a url without escaping + if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) + isValid = false; + + //we'll have to be smarter and just check for certain js patterns now too! + var jsPatterns = new[] { @"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"==" }; + if (jsPatterns.Any(p => Regex.IsMatch(input, p))) + isValid = false; + + if (isValid) + { + var resolvedUrlResult = IOHelper.TryResolveUrl(input); + //if the resolution was success, return it, otherwise just return the path, we've detected + // it's a path but maybe it's relative and resolution has failed, etc... in which case we're just + // returning what was given to us. + return resolvedUrlResult.Success + ? resolvedUrlResult + : Attempt.Succeed(input); + } + } + + return Attempt.Fail(input); + } + /// /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing /// a try/catch when deserializing when it is not json. diff --git a/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs b/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs index 22c0d92979..59626a1b9a 100644 --- a/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs +++ b/src/Umbraco.Tests/Controllers/BackOfficeControllerUnitTests.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Controllers Assert.That(jsBlocks.Count() == 4); Assert.That(jsUrls.Count() == 3); - Assert.That(!jsUrls.Last().StartsWith("~/")); + Assert.That(jsUrls.Last().StartsWith("~/") == false); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs index 43e5d0fcf9..d9c673f179 100644 --- a/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreStrings/StringExtensionsTests.cs @@ -24,6 +24,19 @@ namespace Umbraco.Tests.CoreStrings ShortStringHelperResolver.Reset(); } + [TestCase("alert('hello');", false)] + [TestCase("~/Test.js", true)] + [TestCase("../Test.js", true)] + [TestCase("/Test.js", true)] + [TestCase("Test.js", true)] + [TestCase("Test.js==", false)] + [TestCase("/Test.js function(){return true;}", false)] + public void Detect_Is_JavaScript_Path(string input, bool result) + { + var output = input.DetectIsJavaScriptPath(); + Assert.AreEqual(result, output.Success); + } + [TestCase("hello.txt", "hello")] [TestCase("this.is.a.Txt", "this.is.a")] [TestCase("this.is.not.a. Txt", "this.is.not.a. Txt")] diff --git a/src/Umbraco.Tests/IO/IOHelperTest.cs b/src/Umbraco.Tests/IO/IOHelperTest.cs index 9f8822fbff..559ba1a2f3 100644 --- a/src/Umbraco.Tests/IO/IOHelperTest.cs +++ b/src/Umbraco.Tests/IO/IOHelperTest.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using Umbraco.Core.IO; namespace Umbraco.Tests.IO @@ -13,11 +14,20 @@ namespace Umbraco.Tests.IO public class IOHelperTest { - [Test] - public void IOHelper_ResolveUrl() + [TestCase("~/Scripts", "/Scripts", null)] + [TestCase("/Scripts", "/Scripts", null)] + [TestCase("../Scripts", "/Scripts", typeof(ArgumentException))] + public void IOHelper_ResolveUrl(string input, string expected, Type expectedExceptionType) { - var result = IOHelper.ResolveUrl("~/Scripts"); - Assert.AreEqual("/Scripts", result); + if (expectedExceptionType != null) + { + Assert.Throws(expectedExceptionType, () => IOHelper.ResolveUrl(input)); + } + else + { + var result = IOHelper.ResolveUrl(input); + Assert.AreEqual(expected, result); + } } /// diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index d0e762c54f..4eb536d302 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -451,39 +451,14 @@ namespace Umbraco.Web.Editors var urlList = new List(); foreach (var jsFile in values) { - //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text - //block instead. - var isValid = true; - - if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute)) + var isJsPath = jsFile.DetectIsJavaScriptPath(); + if (isJsPath.Success) { - //ok it validates, but so does alert('hello'); ! so we need to do more checks - - //here are the valid chars in a url without escaping - if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) - isValid = false; - - //we'll have to be smarter and just check for certain js patterns now too! - var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="}; - if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p))) - { - isValid = false; - } - if (isValid) - { - //it is a valid URL add to Url list - urlList.Add(jsFile.StartsWith("~/") ? IOHelper.ResolveUrl(jsFile) : jsFile); - } + urlList.Add(isJsPath.Result); } else { - isValid = false; - } - - if (isValid == false) - { - //it isn't a valid URL, must be a js block - blockList.Add(jsFile); + blockList.Add(isJsPath.Result); } }