V10: Fix to allow dragged images in the rich text editor to be correctly uploaded (#13016)
* update string extensions IsFullPath to support more filepaths with new built-in Path.IsPathFullyQualified * resolve TODO to switch to Path.IsPathFullyQualified supported from .NET Standard 2.1 * Use content root instead of web root for uploaded images * Un-break a breaking change * handle special parsing of AngularJS json response * change htmlId selector to support html id's with numbers * remove bad test case * test IsFullPath without tricky UNC paths that are not useful Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -1326,11 +1326,8 @@ public static class StringExtensions
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
// 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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check</param>
|
||||
/// <returns>True if the path is fully qualified, false otherwise</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user