diff --git a/DNN Platform/Modules/HTML/Components/HtmlTextController.cs b/DNN Platform/Modules/HTML/Components/HtmlTextController.cs index 12e9ff1410d..b12161b77f0 100644 --- a/DNN Platform/Modules/HTML/Components/HtmlTextController.cs +++ b/DNN Platform/Modules/HTML/Components/HtmlTextController.cs @@ -98,82 +98,88 @@ public static string FormatHtmlText(int moduleId, string content, HtmlModuleSett return content; } - public static string ManageRelativePaths(string strHTML, string strUploadDirectory, string strToken, int intPortalID) + [Obsolete("Deprecated in Platform 9.11.0. Use overload without int. Scheduled removal in v11.0.0.")] + public static string ManageRelativePaths(string htmlContent, string strUploadDirectory, string strToken, int intPortalID) { - int P = 0; - int R = 0; - int S = 0; - int tLen = 0; - string strURL = null; + return ManageRelativePaths(htmlContent, strUploadDirectory, strToken); + } + + public static string ManageRelativePaths(string htmlContent, string uploadDirectory, string token) + { + var htmlContentIndex = 0; var sbBuff = new StringBuilder(string.Empty); - if (!string.IsNullOrEmpty(strHTML)) + if (string.IsNullOrEmpty(htmlContent)) { - tLen = strToken.Length + 2; - string uploadDirectory = strUploadDirectory.ToLowerInvariant(); + return string.Empty; + } - // find position of first occurrance: - P = strHTML.IndexOf(strToken + "=\"", StringComparison.InvariantCultureIgnoreCase); - while (P != -1) - { - sbBuff.Append(strHTML.Substring(S, P - S + tLen)); + token = token + "=\""; + var tokenLength = token.Length; + uploadDirectory = uploadDirectory.ToLowerInvariant(); - // keep charactes left of URL - S = P + tLen; + // find position of first occurrence: + var tokenIndex = htmlContent.IndexOf(token, StringComparison.InvariantCultureIgnoreCase); + while (tokenIndex != -1) + { + sbBuff.Append(htmlContent.Substring(htmlContentIndex, tokenIndex - htmlContentIndex + tokenLength)); - // save startpos of URL - R = strHTML.IndexOf("\"", S); + // keep characters left of URL + htmlContentIndex = tokenIndex + tokenLength; - // end of URL - if (R >= 0) - { - strURL = strHTML.Substring(S, R - S).ToLowerInvariant(); - } - else + // save start position of URL + var urlEndIndex = htmlContent.IndexOf('\"', htmlContentIndex); + + // end of URL + string strURL; + if (urlEndIndex >= 0 && urlEndIndex < htmlContent.Length - 2) + { + strURL = htmlContent.Substring(htmlContentIndex, urlEndIndex - htmlContentIndex).ToLowerInvariant(); + } + else + { + tokenIndex = -1; + continue; + } + + if (htmlContent.Substring(tokenIndex + tokenLength, 10).Equals("data:image", StringComparison.InvariantCultureIgnoreCase)) + { + tokenIndex = htmlContent.IndexOf(token, htmlContentIndex + strURL.Length + 2, StringComparison.InvariantCultureIgnoreCase); + continue; + } + + // if we are linking internally + if (!strURL.Contains("://")) + { + // remove the leading portion of the path if the URL contains the upload directory structure + var strDirectory = uploadDirectory; + if (!strDirectory.EndsWith("/", StringComparison.Ordinal)) { - strURL = strHTML.Substring(S).ToLowerInvariant(); + strDirectory += "/"; } - if (strHTML.Substring(P + tLen, 10).Equals("data:image", StringComparison.InvariantCultureIgnoreCase)) + if (strURL.IndexOf(strDirectory, StringComparison.InvariantCultureIgnoreCase) != -1) { - P = strHTML.IndexOf(strToken + "=\"", S + strURL.Length + 2, StringComparison.InvariantCultureIgnoreCase); - continue; + htmlContentIndex = htmlContentIndex + strURL.IndexOf(strDirectory, StringComparison.InvariantCultureIgnoreCase) + strDirectory.Length; + strURL = strURL.Substring(strURL.IndexOf(strDirectory, StringComparison.InvariantCultureIgnoreCase) + strDirectory.Length); } - // if we are linking internally - if (!strURL.Contains("://")) + // add upload directory + // We don't write the UploadDirectory if the token/attribute has not value. Therefore we will avoid an unnecessary request + if (!strURL.StartsWith("/", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(strURL)) { - // remove the leading portion of the path if the URL contains the upload directory structure - string strDirectory = uploadDirectory; - if (!strDirectory.EndsWith("/")) - { - strDirectory += "/"; - } - - if (strURL.IndexOf(strDirectory) != -1) - { - S = S + strURL.IndexOf(strDirectory) + strDirectory.Length; - strURL = strURL.Substring(strURL.IndexOf(strDirectory) + strDirectory.Length); - } - - // add upload directory - if (!strURL.StartsWith("/") - && !string.IsNullOrEmpty(strURL.Trim())) // We don't write the UploadDirectory if the token/attribute has not value. Therefore we will avoid an unnecessary request - { - sbBuff.Append(uploadDirectory); - } + sbBuff.Append(uploadDirectory); } - - // find position of next occurrance - P = strHTML.IndexOf(strToken + "=\"", S + strURL.Length + 2, StringComparison.InvariantCultureIgnoreCase); } - if (S > -1) - { - sbBuff.Append(strHTML.Substring(S)); - } + // find position of next occurrence + tokenIndex = htmlContent.IndexOf(token, htmlContentIndex + strURL.Length + 2, StringComparison.InvariantCultureIgnoreCase); + } - // append characters of last URL and behind + // append characters of last URL and behind + if (htmlContentIndex > -1) + { + sbBuff.Append(htmlContent.Substring(htmlContentIndex)); } return sbBuff.ToString(); diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Modules/AssemblyInfo.cs b/DNN Platform/Tests/DotNetNuke.Tests.Modules/AssemblyInfo.cs new file mode 100644 index 00000000000..f067ac87c0c --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Modules/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetNuke.Library.Tests.Modules")] +[assembly: AssemblyDescription("Open Source Web Application Framework - Test Project")] + +[assembly: AssemblyCompany(".NET Foundation")] +[assembly: AssemblyProduct("https://dnncommunity.org")] +[assembly: AssemblyCopyright("DNN Platform is copyright 2002-2022 by .NET Foundation. All Rights Reserved.")] +[assembly: AssemblyTrademark("DNN")] + +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Modules/DotNetNuke.Tests.Modules.csproj b/DNN Platform/Tests/DotNetNuke.Tests.Modules/DotNetNuke.Tests.Modules.csproj new file mode 100644 index 00000000000..d868a9dec1d --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Modules/DotNetNuke.Tests.Modules.csproj @@ -0,0 +1,130 @@ + + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43} + Library + Properties + DotNetNuke.Tests.Modules + DotNetNuke.Tests.Modules + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + 4.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + true + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + 1591, 0618,SA0001 + 7 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + 1591, 0618,SA0001 + 7 + + + + + ..\..\..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll + + + ..\..\..\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll + + + + + + + + + + + + + + + + {6b29aded-7b56-4484-bea5-c0e09079535b} + DotNetNuke.Library + + + {cd8732d8-b4dd-435d-bf21-a90c2964aba4} + DotNetNuke.Modules.Html + + + + + + + + stylecop.json + + + App.config + Designer + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Modules/Html/HtmlTextControllerTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Modules/Html/HtmlTextControllerTests.cs new file mode 100644 index 00000000000..ad7e795e4e1 --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Modules/Html/HtmlTextControllerTests.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information +namespace DotNetNuke.Tests.Modules.Html +{ + using DotNetNuke.Modules.Html; + using NUnit.Framework; + + [TestFixture] + public class HtmlTextControllerTests + { + [Test] + public void ManageRelativePaths_DoesNotChangePlainHtml() + { + var actual = HtmlTextController.ManageRelativePaths( + "

Hello

", + "/portals/0/", + "src", + 0); + Assert.AreEqual("

Hello

", actual); + } + + [Test] + public void ManageRelativePaths_AdjustsRelativeImgSrc() + { + var actual = HtmlTextController.ManageRelativePaths( + "", + "/portals/0/", + "src", + 0); + Assert.AreEqual("", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustImgSrcWithCorrectPathCaseInsensitive() + { + var actual = HtmlTextController.ManageRelativePaths( + "", + "/portals/0/", + "src", + 0); + Assert.AreEqual("", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustImgSrcWithAbsoluteUrl() + { + var actual = HtmlTextController.ManageRelativePaths( + "", + "/portals/0/", + "src", + 0); + Assert.AreEqual("", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustImgSrcWithAbsoluteUrlInContent() + { + var actual = HtmlTextController.ManageRelativePaths( + "src=\"https://example.com/image.jpg\" is how you indicate a URL", + "/portals/0/", + "src", + 0); + Assert.AreEqual("src=\"https://example.com/image.jpg\" is how you indicate a URL", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustContentEndingInImgSrc() + { + var actual = HtmlTextController.ManageRelativePaths( + "src=\"image.jpg\"", + "/portals/0/", + "src", + 0); + Assert.AreEqual("src=\"image.jpg\"", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustContentEndingInUnclosedImgSrc() + { + var actual = HtmlTextController.ManageRelativePaths( + "src=\"image.jpg", + "/portals/0/", + "src", + 0); + Assert.AreEqual("src=\"image.jpg", actual); + } + + [Test] + public void ManageRelativePaths_DoesAdjustImgSrcWithRelativeUrlInContent() + { + // TODO: should we attempt to avoid making this change? + var actual = HtmlTextController.ManageRelativePaths( + "src=\"image.jpg\" is how you indicate a URL", + "/portals/0/", + "src", + 0); + Assert.AreEqual("src=\"/portals/0/image.jpg\" is how you indicate a URL", actual); + } + + [Test] + public void ManageRelativePaths_DoesNotAdjustImgSrcWithDataUrl() + { + var actual = HtmlTextController.ManageRelativePaths( + "", + "/portals/0/", + "src", + 0); + Assert.AreEqual("", actual); + } + + [Test] + public void ManageRelativePaths_AdjustsNonRootedRelativePathsAndDoesNotAdjustOtherPaths() + { + const string HtmlContent = @" + + + + + + + + +"; + var actual = HtmlTextController.ManageRelativePaths( + HtmlContent, + "/portals/0/", + "src", + 0); + + const string Expected = @" + + + + + + + + +"; + Assert.AreEqual(Expected, actual); + } + } +} diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Modules/TestSetup.cs b/DNN Platform/Tests/DotNetNuke.Tests.Modules/TestSetup.cs new file mode 100644 index 00000000000..468db55bb9d --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Modules/TestSetup.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// +using DotNetNuke.Tests.Utilities.Mocks; + +using NUnit.Framework; + +namespace DotNetNuke.Tests.Core +{ + [SetUpFixture] + internal class TestSetup + { + [SetUp] + public void SetUp() + { + } + + [TearDown] + public void TearDown() + { + MockComponentProvider.ResetContainer(); + } + } +} diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Modules/packages.config b/DNN Platform/Tests/DotNetNuke.Tests.Modules/packages.config new file mode 100644 index 00000000000..8e8938b1592 --- /dev/null +++ b/DNN Platform/Tests/DotNetNuke.Tests.Modules/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DNN_Platform.sln b/DNN_Platform.sln index 5f0368ee67e..e3a66eba61a 100644 --- a/DNN_Platform.sln +++ b/DNN_Platform.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.352 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Support Projects", "Support Projects", "{1DFA65CE-5978-49F9-83BA-CFBD0C7A1814}" EndProject @@ -117,8 +117,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build.ps1 = build.ps1 CONTRIBUTING.md = CONTRIBUTING.md Directory.Build.props = Directory.Build.props - DNN_Platform.build = DNN_Platform.build dnnplatform.png = dnnplatform.png + DNN_Platform.build = DNN_Platform.build gitversion.yml = gitversion.yml LICENSE = LICENSE NOTICE.md = NOTICE.md @@ -570,6 +570,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Extensions.FileSy DNN Platform\Components\Microsoft.Extensions.FileSystemGlobbing\Microsoft.Extensions.FileSystemGlobbing.dnn = DNN Platform\Components\Microsoft.Extensions.FileSystemGlobbing\Microsoft.Extensions.FileSystemGlobbing.dnn EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Tests.Modules", "DNN Platform\Tests\DotNetNuke.Tests.Modules\DotNetNuke.Tests.Modules.csproj", "{EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Cloud_Debug|Any CPU = Cloud_Debug|Any CPU @@ -2082,6 +2084,30 @@ Global {12583A7E-7BEF-4F79-9CEA-3736D28C3241}.Release-Net45|Any CPU.Build.0 = Release|Any CPU {12583A7E-7BEF-4F79-9CEA-3736D28C3241}.Release-Net45|x86.ActiveCfg = Release|Any CPU {12583A7E-7BEF-4F79-9CEA-3736D28C3241}.Release-Net45|x86.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Debug|Any CPU.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Debug|x86.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Debug|x86.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Release|Any CPU.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Release|Any CPU.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Release|x86.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Cloud_Release|x86.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug|x86.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug|x86.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug-Net45|Any CPU.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug-Net45|Any CPU.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug-Net45|x86.ActiveCfg = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Debug-Net45|x86.Build.0 = Debug|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release|Any CPU.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release|x86.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release|x86.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release-Net45|Any CPU.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release-Net45|Any CPU.Build.0 = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release-Net45|x86.ActiveCfg = Release|Any CPU + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43}.Release-Net45|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2202,6 +2228,7 @@ Global {CBBA26D2-68FF-4FDB-BD9E-17D4C2CE717F} = {A193D0F9-C722-44BD-B209-2AA2F25C9816} {6395573E-D778-4DBE-BD22-A39273DBF0C9} = {A193D0F9-C722-44BD-B209-2AA2F25C9816} {890623E0-F6C8-4011-9FAF-00939601EBE8} = {A193D0F9-C722-44BD-B209-2AA2F25C9816} + {EEFFEA77-4FA5-4498-9A9C-1BDBD6436E43} = {88E649D7-1379-430F-B794-1F3B9B442C80} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46B6A641-57EB-4B19-B199-23E6FC2AB40B}