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));
+ }
+ }
}