Merge remote-tracking branch 'origin/v11/dev' into v12/dev
# Conflicts: # src/Umbraco.Web.BackOffice/Controllers/MediaController.cs # version.json
This commit is contained in:
@@ -88,10 +88,6 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase<DataTypeC
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not sure I like these?
|
||||
TagsValueConverter.ClearCaches();
|
||||
SliderValueConverter.ClearCaches();
|
||||
|
||||
// refresh the models and cache
|
||||
_publishedModelFactory.WithSafeLiveFactoryReset(() =>
|
||||
_publishedSnapshotService.Notify(payloads));
|
||||
|
||||
@@ -326,6 +326,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<ICultureImpactFactory>(provider => new CultureImpactFactory(provider.GetRequiredService<IOptionsMonitor<ContentSettings>>()));
|
||||
Services.AddUnique<IDictionaryService, DictionaryService>();
|
||||
Services.AddUnique<ITemporaryMediaService, TemporaryMediaService>();
|
||||
|
||||
// Register filestream security analyzers
|
||||
Services.AddUnique<IFileStreamSecurityValidator,FileStreamSecurityValidator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +342,7 @@
|
||||
<key alias="createFolderFailed">Failed to create a folder under parent id %0%</key>
|
||||
<key alias="renameFolderFailed">Failed to rename the folder with id %0%</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</key>
|
||||
<key alias="fileSecurityValidationFailure">One or more file security validations have failed</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Create a new member</key>
|
||||
|
||||
@@ -353,6 +353,7 @@
|
||||
<key alias="renameFolderFailed">Failed to rename the folder with id %0%</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</key>
|
||||
<key alias="uploadNotAllowed">Upload is not allowed in this location.</key>
|
||||
<key alias="fileSecurityValidationFailure">One or more file security validations have failed</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Create a new member</key>
|
||||
|
||||
@@ -340,6 +340,7 @@
|
||||
<key alias="renameFolderFailed">Kan de map met id %0% niet hernoemen</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Sleep en zet je bestand(en) neer in dit gebied</key>
|
||||
<key alias="uploadNotAllowed">Upload is niet toegelaten in deze locatie.</key>
|
||||
<key alias="fileSecurityValidationFailure">Een of meerdere veiligheid validaties zijn gefaald voor het bestand</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Maak nieuw lid aan</key>
|
||||
|
||||
@@ -219,96 +219,52 @@ public static class TypeExtensions
|
||||
/// <returns></returns>
|
||||
public static PropertyInfo[] GetAllProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
PropertyInfo[] typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance);
|
||||
|
||||
IEnumerable<PropertyInfo> newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all public properties including inherited properties even for interfaces
|
||||
/// Returns public properties including inherited properties even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// taken from
|
||||
/// http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
public static PropertyInfo[] GetPublicProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
|
||||
}
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns public methods including inherited methods even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo[] GetPublicMethods(this Type type)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetMethods(bindingFlags));
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
PropertyInfo[] typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance);
|
||||
|
||||
IEnumerable<PropertyInfo> newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.Instance);
|
||||
/// <summary>
|
||||
/// Returns all methods including inherited methods even for interfaces
|
||||
/// </summary>
|
||||
/// <remarks>Includes both Public and Non-Public methods</remarks>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo[] GetAllMethods(this Type type)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetMethods(bindingFlags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,4 +468,47 @@ public static class TypeExtensions
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// taken from
|
||||
/// http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
private static T[] GetAllMemberInfos<T>(this Type type, Func<Type, T[]> getMemberInfos)
|
||||
where T : MemberInfo
|
||||
{
|
||||
if (type.IsInterface is false)
|
||||
{
|
||||
return getMemberInfos(type);
|
||||
}
|
||||
|
||||
var memberInfos = new List<T>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
T[] typeMethodInfos = getMemberInfos(subType);
|
||||
|
||||
IEnumerable<T> newMethodInfos = typeMethodInfos
|
||||
.Where(x => !memberInfos.Contains(x));
|
||||
|
||||
memberInfos.InsertRange(0, newMethodInfos);
|
||||
}
|
||||
|
||||
return memberInfos.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -6,74 +6,123 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
/// <summary>
|
||||
/// The slider property value converter.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase" />
|
||||
[DefaultPropertyValueConverter]
|
||||
public class SliderValueConverter : PropertyValueConverterBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, bool> Storages = new();
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SliderValueConverter" /> class.
|
||||
/// </summary>
|
||||
public SliderValueConverter()
|
||||
{ }
|
||||
|
||||
public SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService =
|
||||
dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SliderValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dataTypeService">The data type service.</param>
|
||||
[Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")]
|
||||
public SliderValueConverter(IDataTypeService dataTypeService)
|
||||
{ }
|
||||
|
||||
public static void ClearCaches() => Storages.Clear();
|
||||
/// <summary>
|
||||
/// Clears the data type configuration caches.
|
||||
/// </summary>
|
||||
[Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")]
|
||||
public static void ClearCaches()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> IsRangeDataType(propertyType.DataType.Id) ? typeof(Range<decimal>) : typeof(decimal);
|
||||
=> IsRange(propertyType) ? typeof(Range<decimal>) : typeof(decimal);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview)
|
||||
{
|
||||
if (source == null)
|
||||
bool isRange = IsRange(propertyType);
|
||||
|
||||
var sourceString = source?.ToString();
|
||||
|
||||
return isRange
|
||||
? HandleRange(sourceString)
|
||||
: HandleDecimal(sourceString);
|
||||
}
|
||||
|
||||
private static Range<decimal> HandleRange(string? sourceString)
|
||||
{
|
||||
if (sourceString is null)
|
||||
{
|
||||
return null;
|
||||
return new Range<decimal>();
|
||||
}
|
||||
|
||||
if (IsRangeDataType(propertyType.DataType.Id))
|
||||
{
|
||||
var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma);
|
||||
Attempt<decimal> minimumAttempt = rangeRawValues[0].TryConvertTo<decimal>();
|
||||
Attempt<decimal> maximumAttempt = rangeRawValues[1].TryConvertTo<decimal>();
|
||||
string[] rangeRawValues = sourceString.Split(Constants.CharArrays.Comma);
|
||||
|
||||
if (minimumAttempt.Success && maximumAttempt.Success)
|
||||
if (TryParseDecimal(rangeRawValues[0], out var minimum))
|
||||
{
|
||||
if (rangeRawValues.Length == 1)
|
||||
{
|
||||
return new Range<decimal> { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result };
|
||||
// Configuration is probably changed from single to range, return range with same min/max
|
||||
return new Range<decimal>
|
||||
{
|
||||
Minimum = minimum,
|
||||
Maximum = minimum
|
||||
};
|
||||
}
|
||||
|
||||
if (rangeRawValues.Length == 2 && TryParseDecimal(rangeRawValues[1], out var maximum))
|
||||
{
|
||||
return new Range<decimal>
|
||||
{
|
||||
Minimum = minimum,
|
||||
Maximum = maximum
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Attempt<decimal> valueAttempt = source.ToString().TryConvertTo<decimal>();
|
||||
if (valueAttempt.Success)
|
||||
return new Range<decimal>();
|
||||
}
|
||||
|
||||
private static decimal HandleDecimal(string? sourceString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceString))
|
||||
{
|
||||
return valueAttempt.Result;
|
||||
return default;
|
||||
}
|
||||
|
||||
// Something failed in the conversion of the strings to decimals
|
||||
return null;
|
||||
// This used to be a range slider, so we'll assign the minimum value as the new value
|
||||
if (sourceString.Contains(','))
|
||||
{
|
||||
var minimumValueRepresentation = sourceString.Split(Constants.CharArrays.Comma)[0];
|
||||
|
||||
if (TryParseDecimal(minimumValueRepresentation, out var minimum))
|
||||
{
|
||||
return minimum;
|
||||
}
|
||||
}
|
||||
else if (TryParseDecimal(sourceString, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discovers if the slider is set to range mode.
|
||||
/// Helper method for parsing a double consistently
|
||||
/// </summary>
|
||||
/// <param name="dataTypeId">
|
||||
/// The data type id.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool" />.
|
||||
/// </returns>
|
||||
private bool IsRangeDataType(int dataTypeId) =>
|
||||
private static bool TryParseDecimal(string? representation, out decimal value)
|
||||
=> decimal.TryParse(representation, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
||||
|
||||
// GetPreValuesCollectionByDataTypeId is cached at repository level;
|
||||
// still, the collection is deep-cloned so this is kinda expensive,
|
||||
// better to cache here + trigger refresh in DataTypeCacheRefresher
|
||||
// TODO: this is cheap now, remove the caching
|
||||
Storages.GetOrAdd(dataTypeId, id =>
|
||||
{
|
||||
IDataType? dataType = _dataTypeService.GetDataType(id);
|
||||
SliderConfiguration? configuration = dataType?.ConfigurationAs<SliderConfiguration>();
|
||||
return configuration?.EnableRange ?? false;
|
||||
});
|
||||
private static bool IsRange(IPublishedPropertyType propertyType)
|
||||
=> propertyType.DataType.ConfigurationAs<SliderConfiguration>()?.EnableRange == true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -7,69 +6,66 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
/// <summary>
|
||||
/// The tags property value converter.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase" />
|
||||
[DefaultPropertyValueConverter]
|
||||
public class TagsValueConverter : PropertyValueConverterBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, bool> Storages = new();
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TagsValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
||||
public TagsValueConverter(IJsonSerializer jsonSerializer)
|
||||
=> _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TagsValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dataTypeService">The data type service.</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
[Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")]
|
||||
public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
_jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
|
||||
}
|
||||
: this(jsonSerializer)
|
||||
{ }
|
||||
|
||||
public static void ClearCaches() => Storages.Clear();
|
||||
/// <summary>
|
||||
/// Clears the data type configuration caches.
|
||||
/// </summary>
|
||||
[Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")]
|
||||
public static void ClearCaches()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> typeof(IEnumerable<string>);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
{
|
||||
if (source == null)
|
||||
string? sourceString = source?.ToString();
|
||||
if (string.IsNullOrEmpty(sourceString))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// if Json storage type deserialize and return as string array
|
||||
if (JsonStorageType(propertyType.DataType.Id))
|
||||
{
|
||||
var array = source.ToString() is not null
|
||||
? _jsonSerializer.Deserialize<string[]>(source.ToString()!)
|
||||
: null;
|
||||
return array ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
// Otherwise assume CSV storage type and return as string array
|
||||
return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return IsJson(propertyType)
|
||||
? _jsonSerializer.Deserialize<string[]>(sourceString) ?? Array.Empty<string>()
|
||||
: sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers if the tags data type is storing its data in a Json format
|
||||
/// </summary>
|
||||
/// <param name="dataTypeId">
|
||||
/// The data type id.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool" />.
|
||||
/// </returns>
|
||||
private bool JsonStorageType(int dataTypeId) =>
|
||||
|
||||
// GetDataType(id) is cached at repository level; still, there is some
|
||||
// deep-cloning involved (expensive) - better cache here + trigger
|
||||
// refresh in DataTypeCacheRefresher
|
||||
Storages.GetOrAdd(dataTypeId, id =>
|
||||
{
|
||||
TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs<TagConfiguration>();
|
||||
return configuration?.StorageType == TagsStorageType.Json;
|
||||
});
|
||||
private static bool IsJson(IPublishedPropertyType propertyType)
|
||||
=> propertyType.DataType.ConfigurationAs<TagConfiguration>()?.StorageType == TagsStorageType.Json;
|
||||
}
|
||||
|
||||
38
src/Umbraco.Core/Security/FileStreamSecurityValidator.cs
Normal file
38
src/Umbraco.Core/Security/FileStreamSecurityValidator.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public class FileStreamSecurityValidator : IFileStreamSecurityValidator
|
||||
{
|
||||
private readonly IEnumerable<IFileStreamSecurityAnalyzer> _fileAnalyzers;
|
||||
|
||||
public FileStreamSecurityValidator(IEnumerable<IFileStreamSecurityAnalyzer> fileAnalyzers)
|
||||
{
|
||||
_fileAnalyzers = fileAnalyzers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes whether the file content is considered safe with registered IFileStreamSecurityAnalyzers
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe after running the necessary analyzers</returns>
|
||||
public bool IsConsideredSafe(Stream fileStream)
|
||||
{
|
||||
foreach (var fileAnalyzer in _fileAnalyzers)
|
||||
{
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
if (!fileAnalyzer.ShouldHandle(fileStream))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
if (fileAnalyzer.IsConsideredSafe(fileStream) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
// If no analyzer we consider the file to be safe as the implementer has the possibility to add additional analyzers
|
||||
// Or all analyzers deem te file to be safe
|
||||
return true;
|
||||
}
|
||||
}
|
||||
20
src/Umbraco.Core/Security/IFileStreamSecurityAnalyzer.cs
Normal file
20
src/Umbraco.Core/Security/IFileStreamSecurityAnalyzer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public interface IFileStreamSecurityAnalyzer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the analyzer should process the file
|
||||
/// The implementation should be considerably faster than IsConsideredSafe
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <returns></returns>
|
||||
bool ShouldHandle(Stream fileStream);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes whether the file content is considered safe
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read/Write seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe</returns>
|
||||
bool IsConsideredSafe(Stream fileStream);
|
||||
}
|
||||
11
src/Umbraco.Core/Security/IFileStreamSecurityValidator.cs
Normal file
11
src/Umbraco.Core/Security/IFileStreamSecurityValidator.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public interface IFileStreamSecurityValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes wether the file content is considered safe with registered IFileStreamSecurityAnalyzers
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe after running the necessary analyzers</returns>
|
||||
bool IsConsideredSafe(Stream fileStream);
|
||||
}
|
||||
Reference in New Issue
Block a user