Merge remote-tracking branch 'origin/v10/dev' into v11/dev

This commit is contained in:
Bjarke Berg
2022-09-19 10:51:18 +02:00
24 changed files with 1917 additions and 1874 deletions

View File

@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Umbraco.Cms.Core;
using Umbraco.Cms.ManagementApi.Filters;
using Umbraco.New.Cms.Web.Common.Routing;
namespace Umbraco.Cms.ManagementApi.Controllers.Install;
[ApiController]
[BackOfficeRoute("api/v{version:apiVersion}/install")]
[OpenApiTag("Install")]
[RequireRuntimeLevel(RuntimeLevel.Install)]
public abstract class InstallControllerBase : Controller
{
}

View File

@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Infrastructure.Install;
using Umbraco.Cms.ManagementApi.ViewModels.Installer;
using Umbraco.New.Cms.Core.Factories;
using Umbraco.New.Cms.Core.Models.Installer;
namespace Umbraco.Cms.ManagementApi.Controllers.Install;
[ApiVersion("1.0")]
public class SettingsInstallController : InstallControllerBase
{
private readonly InstallHelper _installHelper;
private readonly IInstallSettingsFactory _installSettingsFactory;
private readonly IUmbracoMapper _mapper;
public SettingsInstallController(
InstallHelper installHelper,
IInstallSettingsFactory installSettingsFactory,
IUmbracoMapper mapper)
{
_installHelper = installHelper;
_installSettingsFactory = installSettingsFactory;
_mapper = mapper;
}
[HttpGet("settings")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(typeof(InstallSettingsViewModel), StatusCodes.Status200OK)]
public async Task<ActionResult<InstallSettingsViewModel>> Settings()
{
// Register that the install has started
await _installHelper.SetInstallStatusAsync(false, string.Empty);
InstallSettingsModel installSettings = _installSettingsFactory.GetInstallSettings();
InstallSettingsViewModel viewModel = _mapper.Map<InstallSettingsViewModel>(installSettings)!;
return viewModel;
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.ManagementApi.ViewModels.Installer;
using Umbraco.Extensions;
using Umbraco.New.Cms.Core.Models.Installer;
using Umbraco.New.Cms.Core.Services.Installer;
namespace Umbraco.Cms.ManagementApi.Controllers.Install;
[ApiVersion("1.0")]
public class SetupInstallController : InstallControllerBase
{
private readonly IUmbracoMapper _mapper;
private readonly IInstallService _installService;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly GlobalSettings _globalSettings;
public SetupInstallController(
IUmbracoMapper mapper,
IInstallService installService,
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment)
{
_mapper = mapper;
_installService = installService;
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings.Value;
}
[HttpPost("setup")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Setup(InstallViewModel installData)
{
InstallData data = _mapper.Map<InstallData>(installData)!;
await _installService.Install(data);
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
return Created(backOfficePath, null);
}
}

View File

@@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.ManagementApi.ViewModels.Installer;
namespace Umbraco.Cms.ManagementApi.Controllers.Install;
[ApiVersion("1.0")]
public class ValidateDatabaseInstallController : InstallControllerBase
{
private readonly DatabaseBuilder _databaseBuilder;
private readonly IUmbracoMapper _mapper;
public ValidateDatabaseInstallController(
DatabaseBuilder databaseBuilder,
IUmbracoMapper mapper)
{
_databaseBuilder = databaseBuilder;
_mapper = mapper;
}
[HttpPost("validateDatabase")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> ValidateDatabase(DatabaseInstallViewModel viewModel)
{
// TODO: Async - We need to figure out what we want to do with async endpoints that doesn't do anything async
// We want these to be async for future use (Ideally we'll have more async things),
// But we need to figure out how we want to handle it in the meantime? use Task.FromResult or?
DatabaseModel databaseModel = _mapper.Map<DatabaseModel>(viewModel)!;
var success = _databaseBuilder.ConfigureDatabaseConnection(databaseModel, true);
if (success)
{
return Ok();
}
var invalidModelProblem = new ProblemDetails
{
Title = "Invalid database configuration",
Detail = "The provided database configuration is invalid",
Status = StatusCodes.Status400BadRequest,
Type = "Error",
};
return BadRequest(invalidModelProblem);
}
}

View File

@@ -1,111 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Infrastructure.Install;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.ManagementApi.Filters;
using Umbraco.Cms.ManagementApi.ViewModels.Installer;
using Umbraco.Extensions;
using Umbraco.New.Cms.Core.Factories;
using Umbraco.New.Cms.Core.Models.Installer;
using Umbraco.New.Cms.Core.Services.Installer;
using Umbraco.New.Cms.Web.Common.Routing;
namespace Umbraco.Cms.ManagementApi.Controllers;
[ApiController]
[ApiVersion("1.0")]
[BackOfficeRoute("api/v{version:apiVersion}/install")]
[RequireRuntimeLevel(RuntimeLevel.Install)]
public class NewInstallController : Controller
{
private readonly IUmbracoMapper _mapper;
private readonly IInstallSettingsFactory _installSettingsFactory;
private readonly IInstallService _installService;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly InstallHelper _installHelper;
private readonly DatabaseBuilder _databaseBuilder;
public NewInstallController(
IUmbracoMapper mapper,
IInstallSettingsFactory installSettingsFactory,
IInstallService installService,
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
InstallHelper installHelper,
DatabaseBuilder databaseBuilder)
{
_mapper = mapper;
_installSettingsFactory = installSettingsFactory;
_installService = installService;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
_installHelper = installHelper;
_databaseBuilder = databaseBuilder;
}
[HttpGet("settings")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(typeof(InstallSettingsViewModel), StatusCodes.Status200OK)]
public async Task<ActionResult<InstallSettingsViewModel>> Settings()
{
// Register that the install has started
await _installHelper.SetInstallStatusAsync(false, string.Empty);
InstallSettingsModel installSettings = _installSettingsFactory.GetInstallSettings();
InstallSettingsViewModel viewModel = _mapper.Map<InstallSettingsViewModel>(installSettings)!;
return viewModel;
}
[HttpPost("setup")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Setup(InstallViewModel installData)
{
InstallData data = _mapper.Map<InstallData>(installData)!;
await _installService.Install(data);
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
return Created(backOfficePath, null);
}
[HttpPost("validateDatabase")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> ValidateDatabase(DatabaseInstallViewModel viewModel)
{
// TODO: Async - We need to figure out what we want to do with async endpoints that doesn't do anything async
// We want these to be async for future use (Ideally we'll have more async things),
// But we need to figure out how we want to handle it in the meantime? use Task.FromResult or?
DatabaseModel databaseModel = _mapper.Map<DatabaseModel>(viewModel)!;
var success = _databaseBuilder.ConfigureDatabaseConnection(databaseModel, true);
if (success)
{
return Ok();
}
var invalidModelProblem = new ProblemDetails
{
Title = "Invalid database configuration",
Detail = "The provided database configuration is invalid",
Status = StatusCodes.Status400BadRequest,
Type = "Error",
};
return BadRequest(invalidModelProblem);
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.New.Cms.Core.Services.Installer;
namespace Umbraco.Cms.ManagementApi.Controllers.Upgrade;
[ApiVersion("1.0")]
public class AuthorizeUpgradeController : UpgradeControllerBase
{
private readonly IUpgradeService _upgradeService;
public AuthorizeUpgradeController(IUpgradeService upgradeService) => _upgradeService = upgradeService;
[HttpPost("authorize")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> Authorize()
{
await _upgradeService.Upgrade();
return Ok();
}
}

View File

@@ -1,48 +1,26 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.ManagementApi.Filters;
using Umbraco.Cms.ManagementApi.ViewModels.Installer;
using Umbraco.New.Cms.Core.Factories;
using Umbraco.New.Cms.Core.Models.Installer;
using Umbraco.New.Cms.Core.Services.Installer;
using Umbraco.New.Cms.Web.Common.Routing;
namespace Umbraco.Cms.ManagementApi.Controllers;
namespace Umbraco.Cms.ManagementApi.Controllers.Upgrade;
// TODO: This needs to be an authorized controller.
[ApiController]
[ApiVersion("1.0")]
[RequireRuntimeLevel(RuntimeLevel.Upgrade)]
[BackOfficeRoute("api/v{version:apiVersion}/upgrade")]
public class UpgradeController : Controller
public class SettingsUpgradeController : UpgradeControllerBase
{
private readonly IUpgradeSettingsFactory _upgradeSettingsFactory;
private readonly IUpgradeService _upgradeService;
private readonly IUmbracoMapper _mapper;
public UpgradeController(
public SettingsUpgradeController(
IUpgradeSettingsFactory upgradeSettingsFactory,
IUpgradeService upgradeService,
IUmbracoMapper mapper)
{
_upgradeSettingsFactory = upgradeSettingsFactory;
_upgradeService = upgradeService;
_mapper = mapper;
}
[HttpPost("authorize")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status428PreconditionRequired)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> Authorize()
{
await _upgradeService.Upgrade();
return Ok();
}
[HttpGet("settings")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(UpgradeSettingsViewModel), StatusCodes.Status200OK)]

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Umbraco.Cms.Core;
using Umbraco.Cms.ManagementApi.Filters;
using Umbraco.New.Cms.Web.Common.Routing;
namespace Umbraco.Cms.ManagementApi.Controllers.Upgrade;
// TODO: This needs to be an authorized controller.
[ApiController]
[RequireRuntimeLevel(RuntimeLevel.Upgrade)]
[BackOfficeRoute("api/v{version:apiVersion}/upgrade")]
[OpenApiTag("Upgrade")]
public abstract class UpgradeControllerBase : Controller
{
}

View File

@@ -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

View File

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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -21,7 +21,7 @@
<None Remove="obj\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Examine" Version="3.0.0" />
<PackageReference Include="Examine" Version="3.0.1" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -60,7 +60,7 @@
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.1" />
<PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" /> <!-- Explicit updated this nested dependency due to this https://github.com/dotnet/announcements/issues/178-->
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
<PackageReference Include="Examine.Core" Version="3.0.0" />
<PackageReference Include="Examine.Core" Version="3.0.1" />
<PackageReference Include="Umbraco.Code" Version="2.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -20,8 +20,6 @@ public class SignInUserStep : IInstallStep
_backOfficeUserManager = backOfficeUserManager;
}
public InstallationType InstallationTypeTarget => InstallationType.NewInstall;
public async Task ExecuteAsync(InstallData model)
{
BackOfficeIdentityUser? identityUser = await _backOfficeUserManager.FindByIdAsync(Constants.Security.SuperUserIdAsString);

View File

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

View File

@@ -1,4 +1,3 @@
using Dazinator.Extensions.FileProviders.PrependBasePath;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@@ -8,6 +7,7 @@ using SixLabors.ImageSharp.Web.DependencyInjection;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Media;
using Umbraco.Extensions;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
@@ -96,7 +96,7 @@ public class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoEnd
{
webHostEnvironment.WebRootFileProvider =
webHostEnvironment.WebRootFileProvider.ConcatComposite(
new PrependBasePathFileProvider(mediaRequestPath, mediaFileProvider));
new MediaPrependBasePathFileProvider(mediaRequestPath, mediaFileProvider));
}
}

View File

@@ -0,0 +1,94 @@
using Dazinator.Extensions.FileProviders;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
namespace Umbraco.Cms.Web.Common.Media;
/// <summary>
/// Prepends a base path to files / directories from an underlying file provider.
/// </summary>
/// <remarks>
/// This is a clone-and-own of PrependBasePathFileProvider from the Dazinator project, cleaned up and tweaked to work
/// for serving media files with special characters.
/// Reference issue: https://github.com/umbraco/Umbraco-CMS/issues/12903
/// A PR has been submitted to the Dazinator project: https://github.com/dazinator/Dazinator.Extensions.FileProviders/pull/53
/// If that PR is accepted, the Dazinator dependency should be updated and this class should be removed.
/// </remarks>
internal class MediaPrependBasePathFileProvider : IFileProvider
{
private readonly PathString _basePath;
private readonly IFileProvider _underlyingFileProvider;
private readonly IFileInfo _baseDirectoryFileInfo;
private static readonly char[] _splitChar = { '/' };
public MediaPrependBasePathFileProvider(string? basePath, IFileProvider underlyingFileProvider)
{
_basePath = new PathString(basePath);
_baseDirectoryFileInfo = new DirectoryFileInfo(_basePath.ToString().TrimStart(_splitChar));
_underlyingFileProvider = underlyingFileProvider;
}
protected virtual bool TryMapSubPath(string originalSubPath, out PathString newSubPath)
{
if (!string.IsNullOrEmpty(originalSubPath))
{
PathString originalPathString;
originalPathString = originalSubPath[0] != '/' ? new PathString('/' + originalSubPath) : new PathString(originalSubPath);
if (originalPathString.HasValue && originalPathString.StartsWithSegments(_basePath, out PathString remaining))
{
// var childPath = originalPathString.Remove(0, _basePath.Value.Length);
newSubPath = remaining;
return true;
}
}
newSubPath = null;
return false;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
// return root / base directory.
return new EnumerableDirectoryContents(_baseDirectoryFileInfo);
}
if (TryMapSubPath(subpath, out PathString newPath))
{
IDirectoryContents? contents = _underlyingFileProvider.GetDirectoryContents(newPath);
return contents;
}
return new NotFoundDirectoryContents();
}
public IFileInfo GetFileInfo(string subpath)
{
if (TryMapSubPath(subpath, out PathString newPath))
{
// KJA changed: use explicit newPath.Value instead of implicit newPath string operator (which calls ToString())
IFileInfo? result = _underlyingFileProvider.GetFileInfo(newPath.Value);
return result;
}
return new NotFoundFileInfo(subpath);
}
public IChangeToken Watch(string filter)
{
// We check if the pattern starts with the base path, and remove it if necessary.
// otherwise we just pass the pattern through unaltered.
if (TryMapSubPath(filter, out PathString newPath))
{
// KJA changed: use explicit newPath.Value instead of implicit newPath string operator (which calls ToString())
IChangeToken? result = _underlyingFileProvider.Watch(newPath.Value);
return result;
}
return _underlyingFileProvider.Watch(newPath);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@
"npm": ">=8.0.0 < 9"
},
"dependencies": {
"@microsoft/signalr": "6.0.4",
"ace-builds": "1.4.2",
"@microsoft/signalr": "6.0.9",
"ace-builds": "1.10.1",
"angular": "1.8.3",
"angular-animate": "1.8.3",
"angular-aria": "1.8.3",
@@ -36,28 +36,28 @@
"animejs": "3.2.1",
"bootstrap-social": "5.1.1",
"chart.js": "^2.9.3",
"clipboard": "2.0.10",
"clipboard": "2.0.11",
"diff": "5.0.0",
"flatpickr": "4.6.13",
"font-awesome": "4.7.0",
"jquery": "3.6.0",
"jquery-ui-dist": "1.13.1",
"jquery": "3.6.1",
"jquery-ui-dist": "1.13.2",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
"moment": "2.29.3",
"moment": "2.29.4",
"ng-file-upload": "12.2.13",
"nouislider": "15.6.0",
"nouislider": "15.6.1",
"spectrum-colorpicker2": "2.0.9",
"tinymce": "4.9.11",
"typeahead.js": "0.11.1",
"underscore": "1.13.2",
"wicg-inert": "3.1.1"
"underscore": "1.13.4",
"wicg-inert": "3.1.2"
},
"devDependencies": {
"@babel/core": "7.17.9",
"@babel/preset-env": "7.16.11",
"@babel/core": "7.19.1",
"@babel/preset-env": "7.19.1",
"autoprefixer": "10.4.4",
"cssnano": "5.1.7",
"cssnano": "5.1.13",
"gulp": "4.0.2",
"gulp-angular-embed-templates": "2.3.0",
"gulp-babel": "8.0.0",
@@ -76,17 +76,17 @@
"gulp-watch": "5.0.1",
"gulp-wrap": "0.15.0",
"gulp-wrap-js": "0.4.1",
"jasmine-core": "4.1.0",
"jsdom": "19.0.0",
"karma": "6.3.19",
"karma-jasmine": "5.0.0",
"karma-jsdom-launcher": "12.0.0",
"jasmine-core": "4.4.0",
"jsdom": "20.0.0",
"karma": "6.4.0",
"karma-jasmine": "5.1.0",
"karma-jsdom-launcher": "13.0.0",
"karma-junit-reporter": "2.0.1",
"karma-spec-reporter": "0.0.34",
"less": "4.1.2",
"less": "4.1.3",
"lodash": "4.17.21",
"merge-stream": "2.0.0",
"postcss": "8.4.12",
"postcss": "8.4.16",
"run-sequence": "2.2.1"
}
}

View File

@@ -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;
}

View File

@@ -87,6 +87,7 @@
<ItemGroup>
<PackageReference Include="Examine.Lucene" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.0-preview.7.22375.6" />
<PackageReference Include="Examine.Lucene" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Bogus" Version="34.0.2" />
<PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />

View File

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