Merge remote-tracking branch 'origin/v13/dev' into v14/dev

This commit is contained in:
Bjarke Berg
2024-02-06 14:25:12 +01:00
10 changed files with 130 additions and 27 deletions

View File

@@ -355,8 +355,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<CompiledPackageXmlParser>();
Services.AddUnique<IPreviewService, PreviewService>();
// Register a noop IHtmlSanitizer to be replaced
// Register a noop IHtmlSanitizer & IMarkdownSanitizer to be replaced
Services.AddUnique<IHtmlSanitizer, NoopHtmlSanitizer>();
Services.AddUnique<IMarkdownSanitizer, NoopMarkdownSanitizer>();
Services.AddUnique<IPropertyTypeUsageService, PropertyTypeUsageService>();
Services.AddUnique<IDataTypeUsageService, DataTypeUsageService>();

View File

@@ -1015,6 +1015,8 @@
<key alias="createHeader">Opret header</key>
<key alias="deliveries">Leverancer</key>
<key alias="noHeaders">Der er ikke tilføjet nogen webhook headers</key>
<key alias="toggleDebug">Skift til debug mode for mere information.</key>
<key alias="statusNotOk">Ikke OK status kode.</key>
</area>
<area alias="language">
<key alias="cultureCode">Culture Code</key>

View File

@@ -2032,6 +2032,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="types">Types</key>
<key alias="webhookKey">Webhook key</key>
<key alias="retryCount">Retry count</key>
<key alias="toggleDebug">Toggle debug mode for more information.</key>
<key alias="statusNotOk">Not OK status code</key>
</area>
<area alias="languages">
<key alias="addLanguage">Add language</key>

View File

@@ -7,19 +7,21 @@ namespace Umbraco.Cms.Core.IO;
internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IIOHelper _ioHelper;
private const string ShadowFsPath = "ShadowFs";
private readonly Func<bool?>? _isScoped;
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILoggerFactory _loggerFactory;
private readonly string _shadowPath;
private readonly Func<bool?>? _isScoped;
private string? _shadowDir;
private ShadowFileSystem? _shadowFileSystem;
public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, string shadowPath, Func<bool?>? isScoped = null)
{
InnerFileSystem = innerFileSystem;
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_loggerFactory = loggerFactory;
@@ -35,18 +37,19 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
get
{
if (_isScoped is not null && _shadowFileSystem is not null)
Func<bool?>? isScoped = _isScoped;
if (isScoped is not null && _shadowFileSystem is not null)
{
var isScoped = _isScoped!();
bool? scoped = isScoped();
// if the filesystem is created *after* shadowing starts, it won't be shadowing
// better not ignore that situation and raised a meaningful (?) exception
if (isScoped.HasValue && isScoped.Value && _shadowFileSystem == null)
// better not ignore that situation and raise a meaningful (?) exception
if (scoped.HasValue && scoped.Value && _shadowFileSystem == null)
{
throw new Exception("The filesystems are shadowing, but this filesystem is not.");
}
return isScoped.HasValue && isScoped.Value
return scoped.HasValue && scoped.Value
? _shadowFileSystem
: InnerFileSystem;
}
@@ -56,8 +59,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
}
/// <inheritdoc />
public IFileProvider? Create() =>
InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
public IFileProvider? Create() => InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
public IEnumerable<string> GetDirectories(string path) => FileSystem.GetDirectories(path);
@@ -69,8 +71,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
public void AddFile(string path, Stream stream) => FileSystem.AddFile(path, stream);
public void AddFile(string path, Stream stream, bool overrideExisting) =>
FileSystem.AddFile(path, stream, overrideExisting);
public void AddFile(string path, Stream stream, bool overrideExisting) => FileSystem.AddFile(path, stream, overrideExisting);
public IEnumerable<string> GetFiles(string path) => FileSystem.GetFiles(path);
@@ -107,8 +108,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
var virt = ShadowFsPath + "/" + id;
var shadowDir = hostingEnvironment.MapPathContentRoot(virt);
var shadowDir = Path.Combine(hostingEnvironment.LocalTempPath, ShadowFsPath, id);
if (Directory.Exists(shadowDir))
{
continue;
@@ -129,10 +129,10 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
// on ShadowFileSystemsScope.None - and if None is false then we should be running
// in a single thread anyways
var virt = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = _hostingEnvironment.MapPathContentRoot(virt);
var rootUrl = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = Path.Combine(_hostingEnvironment.LocalTempPath, rootUrl);
Directory.CreateDirectory(_shadowDir);
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, _hostingEnvironment.ToAbsolute(virt));
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, rootUrl);
_shadowFileSystem = new ShadowFileSystem(InnerFileSystem, tempfs);
}
@@ -160,7 +160,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
// shadowPath make be path/to/dir, remove each
dir = dir!.Replace('/', Path.DirectorySeparatorChar);
var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length;
var min = Path.Combine(_hostingEnvironment.LocalTempPath, ShadowFsPath).Length;
var pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
while (pos > min)
{

View File

@@ -0,0 +1,39 @@
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for
/// display in the editor
/// </summary>
internal class MarkDownPropertyValueEditor : DataValueEditor
{
private readonly IMarkdownSanitizer _markdownSanitizer;
public MarkDownPropertyValueEditor(
ILocalizedTextService localizedTextService,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
DataEditorAttribute attribute,
IMarkdownSanitizer markdownSanitizer)
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) => _markdownSanitizer = markdownSanitizer;
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
{
if (string.IsNullOrWhiteSpace(editorValue.Value?.ToString()))
{
return null;
}
var sanitized = _markdownSanitizer.Sanitize(editorValue.Value.ToString()!);
return sanitized.NullOrWhiteSpaceAsNull();
}
}

View File

@@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -50,4 +51,11 @@ public class MarkdownPropertyEditor : DataEditor
/// <inheritdoc />
protected override IConfigurationEditor CreateConfigurationEditor() =>
new MarkdownConfigurationEditor(_ioHelper, _editorConfigurationParser);
/// <summary>
/// Create a custom value editor
/// </summary>
/// <returns></returns>
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<MarkDownPropertyValueEditor>(Attribute!);
}

View File

@@ -0,0 +1,14 @@
namespace Umbraco.Cms.Core.Security;
/// <summary>
/// Sanitizer service for the markdown editor.
/// </summary>
public interface IMarkdownSanitizer
{
/// <summary>
/// Sanitizes Markdown
/// </summary>
/// <param name="markdown">Markdown to be sanitized</param>
/// <returns>Sanitized Markdown</returns>
string Sanitize(string markdown);
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Core.Security;
/// <inheritdoc />
public class NoopMarkdownSanitizer : IMarkdownSanitizer
{
/// <inheritdoc />
public string Sanitize(string markdown) => markdown;
}

View File

@@ -1,5 +1,9 @@
using Umbraco.Cms.Core.Mapping;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Webhooks;
using Umbraco.Cms.Web.Common.Models;
@@ -7,6 +11,24 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping;
public class WebhookMapDefinition : IMapDefinition
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILocalizedTextService _localizedTextService;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public WebhookMapDefinition() : this(
StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>(),
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>()
)
{
}
public WebhookMapDefinition(IHostingEnvironment hostingEnvironment, ILocalizedTextService localizedTextService)
{
_hostingEnvironment = hostingEnvironment;
_localizedTextService = localizedTextService;
}
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<WebhookViewModel, IWebhook>((_, _) => new Webhook(string.Empty), Map);
@@ -40,13 +62,22 @@ public class WebhookMapDefinition : IMapDefinition
target.EventAlias = source.EventAlias;
target.Key = source.Key;
target.RequestBody = source.RequestBody ?? string.Empty;
target.ResponseBody = source.ResponseBody;
target.RetryCount = source.RetryCount;
target.StatusCode = source.StatusCode;
target.Url = source.Url;
target.RequestHeaders = source.RequestHeaders;
target.ResponseHeaders = source.ResponseHeaders;
target.WebhookKey = source.WebhookKey;
target.ExceptionOccured = source.ExceptionOccured;
if (_hostingEnvironment.IsDebugMode)
{
target.ExceptionOccured = source.ExceptionOccured;
target.ResponseBody = source.ResponseBody;
target.ResponseHeaders = source.ResponseHeaders;
target.StatusCode = source.StatusCode;
}
else
{
target.ResponseBody = _localizedTextService.Localize("webhooks", "toggleDebug", Thread.CurrentThread.CurrentUICulture);
target.StatusCode = source.StatusCode is "OK (200)" ? source.StatusCode : _localizedTextService.Localize("webhooks", "statusNotOk", Thread.CurrentThread.CurrentUICulture);
}
}
}

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;