diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 694b4d05e6..790444c4bc 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1326,11 +1326,8 @@ public static class StringExtensions /// /// // From: http://stackoverflow.com/a/35046453/5018 - public static bool IsFullPath(this string path) => - string.IsNullOrWhiteSpace(path) == false - && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 - && Path.IsPathRooted(path) - && Path.GetPathRoot(path)?.Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + // Updated from .NET 2.1+: https://stackoverflow.com/a/58250915 + public static bool IsFullPath(this string path) => Path.IsPathFullyQualified(path); // FORMAT STRINGS diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index cffd2780da..42e0978b3d 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -53,8 +53,7 @@ public abstract class IOHelper : IIOHelper throw new ArgumentNullException(nameof(path)); } - // Check if the path is already mapped - TODO: This should be switched to Path.IsPathFullyQualified once we are on Net Standard 2.1 - if (IsPathFullyQualified(path)) + if (path.IsFullPath()) { return path; } @@ -231,13 +230,7 @@ public abstract class IOHelper : IIOHelper : CleanFolderResult.Success(); } - /// - /// Returns true if the path has a root, and is considered fully qualified for the OS it is on - /// See - /// https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281 - /// for the .NET Standard 2.1 version of this - /// - /// The path to check - /// True if the path is fully qualified, false otherwise - public abstract bool IsPathFullyQualified(string path); + [Obsolete("Use Path.IsPathFullyQualified instead. This will be removed in Umbraco 13.")] + + public virtual bool IsPathFullyQualified(string path) => Path.IsPathFullyQualified(path); } diff --git a/src/Umbraco.Core/IO/IOHelperLinux.cs b/src/Umbraco.Core/IO/IOHelperLinux.cs index 7d936895a1..239d43a605 100644 --- a/src/Umbraco.Core/IO/IOHelperLinux.cs +++ b/src/Umbraco.Core/IO/IOHelperLinux.cs @@ -9,8 +9,6 @@ public class IOHelperLinux : IOHelper { } - public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); - public override bool PathStartsWith(string path, string root, params char[] separators) { // either it is identical to root, diff --git a/src/Umbraco.Core/IO/IOHelperOSX.cs b/src/Umbraco.Core/IO/IOHelperOSX.cs index 8b8ed20939..d939e0f146 100644 --- a/src/Umbraco.Core/IO/IOHelperOSX.cs +++ b/src/Umbraco.Core/IO/IOHelperOSX.cs @@ -9,8 +9,6 @@ public class IOHelperOSX : IOHelper { } - public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); - public override bool PathStartsWith(string path, string root, params char[] separators) { // either it is identical to root, diff --git a/src/Umbraco.Core/IO/IOHelperWindows.cs b/src/Umbraco.Core/IO/IOHelperWindows.cs index 9dfec76f36..4325b56108 100644 --- a/src/Umbraco.Core/IO/IOHelperWindows.cs +++ b/src/Umbraco.Core/IO/IOHelperWindows.cs @@ -9,35 +9,6 @@ public class IOHelperWindows : IOHelper { } - public override bool IsPathFullyQualified(string path) - { - // TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1 - if (path.Length < 2) - { - // It isn't fixed, it must be relative. There is no way to specify a fixed - // path with one character (or less). - return false; - } - - if (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar) - { - // There is no valid way to specify a relative path with two initial slashes or - // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ - return path[1] == '?' || path[1] == Path.DirectorySeparatorChar || - path[1] == Path.AltDirectorySeparatorChar; - } - - // The only way to specify a fixed path that doesn't begin with two slashes - // is the drive, colon, slash format- i.e. C:\ - return path.Length >= 3 - && path[1] == Path.VolumeSeparatorChar - && (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar) - - // To match old behavior we'll check the drive character for validity as the path is technically - // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. - && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')); - } - public override bool PathStartsWith(string path, string root, params char[] separators) { // either it is identical to root, diff --git a/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs b/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs index 316073d8de..3d93f9af6c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TinyMceController.cs @@ -45,7 +45,7 @@ public class TinyMceController : UmbracoAuthorizedApiController { // Create an unique folder path to help with concurrent users to avoid filename clash var imageTempPath = - _hostingEnvironment.MapPathWebRoot(Constants.SystemDirectories.TempImageUploads + "/" + Guid.NewGuid()); + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempImageUploads + "/" + Guid.NewGuid()); // Ensure image temp path exists if (Directory.Exists(imageTempPath) == false) @@ -81,7 +81,7 @@ public class TinyMceController : UmbracoAuthorizedApiController } var newFilePath = imageTempPath + Path.DirectorySeparatorChar + safeFileName; - var relativeNewFilePath = _ioHelper.GetRelativePath(newFilePath); + var relativeNewFilePath = GetRelativePath(newFilePath); await using (FileStream stream = System.IO.File.Create(newFilePath)) { @@ -90,4 +90,17 @@ public class TinyMceController : UmbracoAuthorizedApiController return Ok(new { tmpLocation = relativeNewFilePath }); } + + // Use private method istead of _ioHelper.GetRelativePath as that is relative for the webroot and not the content root. + private string GetRelativePath(string path) + { + if (path.IsFullPath()) + { + var rootDirectory = _hostingEnvironment.MapPathContentRoot("~"); + var relativePath = _ioHelper.PathStartsWith(path, rootDirectory) ? path[rootDirectory.Length..] : path; + path = relativePath; + } + + return PathUtility.EnsurePathIsApplicationRootPrefixed(path); + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index e4d2b09e94..850a173f8d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -222,9 +222,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } function uploadImageHandler(blobInfo, success, failure, progress){ - let xhr, formData; - - xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); xhr.open('POST', Umbraco.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); xhr.onloadstart = function(e) { @@ -248,18 +246,33 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; xhr.onload = function () { - let json; - if (xhr.status < 200 || xhr.status >= 300) { failure('HTTP Error: ' + xhr.status); return; } - json = JSON.parse(xhr.responseText); + let data = xhr.responseText; + + // The response is fitted as an AngularJS resource response and needs to be cleaned of the AngularJS metadata + data = data.split("\n"); + + if (!data.length > 1) { + failure('Unrecognized text string: ' + data); + return; + } + + let json = {}; + + try { + json = JSON.parse(data[1]); + } catch (e) { + failure('Invalid JSON: ' + data + ' - ' + e.message); + return; + } if (!json || typeof json.tmpLocation !== 'string') { - failure('Invalid JSON: ' + xhr.responseText); - return; + failure('Invalid JSON: ' + data); + return; } // Put temp location into localstorage (used to update the img with data-tmpimg later on) @@ -271,7 +284,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s success(blobInfo.blobUri()); }; - formData = new FormData(); + const formData = new FormData(); formData.append('file', blobInfo.blob(), blobInfo.blob().name); xhr.send(formData); @@ -435,7 +448,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s if (args.htmlId) { - config.selector = "#" + args.htmlId; + config.selector = `[id="${args.htmlId}"]`; } else if (args.target) { config.target = args.target; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs index 7e13b2a06a..01fc57c1d8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using NUnit.Framework; using Umbraco.Cms.Core.Strings; @@ -323,4 +325,49 @@ public class StringExtensionsTests var output = input.ReplaceMany(toReplace.ToArray(), replacement); Assert.AreEqual(expected, output); } + + [Test] + public void IsFullPath() + { + bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + // These are full paths on Windows, but not on Linux + TryIsFullPath(@"C:\dir\file.ext", isWindows); + TryIsFullPath(@"C:\dir\", isWindows); + TryIsFullPath(@"C:\dir", isWindows); + TryIsFullPath(@"C:\", isWindows); + TryIsFullPath(@"\\unc\share\dir\file.ext", isWindows); + TryIsFullPath(@"\\unc\share", isWindows); + + // These are full paths on Linux, but not on Windows + TryIsFullPath(@"/some/file", !isWindows); + TryIsFullPath(@"/dir", !isWindows); + TryIsFullPath(@"/", !isWindows); + + // Not full paths on either Windows or Linux + TryIsFullPath(@"file.ext", false); + TryIsFullPath(@"dir\file.ext", false); + TryIsFullPath(@"\dir\file.ext", false); + TryIsFullPath(@"C:", false); + TryIsFullPath(@"C:dir\file.ext", false); + TryIsFullPath(@"\dir", false); // An "absolute", but not "full" path + + // Invalid on both Windows and Linux + TryIsFullPath("", false, false); + TryIsFullPath(" ", false, false); // technically, a valid filename on Linux + } + + private static void TryIsFullPath(string path, bool expectedIsFull, bool expectedIsValid = true) + { + Assert.AreEqual(expectedIsFull, path.IsFullPath(), "IsFullPath('" + path + "')"); + + if (expectedIsFull) + { + Assert.AreEqual(path, Path.GetFullPath(path)); + } + else if (expectedIsValid) + { + Assert.AreNotEqual(path, Path.GetFullPath(path)); + } + } }