diff --git a/Masa.Framework.sln b/Masa.Framework.sln index 01f683fea..4903b4213 100644 --- a/Masa.Framework.sln +++ b/Masa.Framework.sln @@ -639,6 +639,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Service.Caller EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Extensions.Validations.FluentValidation.Tests", "src\Utils\Extensions\Validations\Tests\Masa.Utils.Extensions.Validations.FluentValidation.Tests\Masa.Utils.Extensions.Validations.FluentValidation.Tests.csproj", "{351755B9-4C7F-4B51-B813-3203DC9A8284}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.Extensions.Enums.Tests", "src\Utils\Extensions\Tests\Masa.Utils.Extensions.Enums.Tests\Masa.Utils.Extensions.Enums.Tests.csproj", "{3AAD9D87-BC2D-446C-B56B-60F8A1492C30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.Extensions.Expressions.Tests", "src\Utils\Extensions\Tests\Masa.Utils.Extensions.Expressions.Tests\Masa.Utils.Extensions.Expressions.Tests.csproj", "{FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2263,6 +2267,14 @@ Global {BC1E63D5-C997-4269-BCE9-A5FBE7BA7CA1}.Release|Any CPU.Build.0 = Release|Any CPU {BC1E63D5-C997-4269-BCE9-A5FBE7BA7CA1}.Release|x64.ActiveCfg = Release|Any CPU {BC1E63D5-C997-4269-BCE9-A5FBE7BA7CA1}.Release|x64.Build.0 = Release|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Debug|x64.ActiveCfg = Debug|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Debug|x64.Build.0 = Debug|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Release|Any CPU.Build.0 = Release|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Release|x64.ActiveCfg = Release|Any CPU + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30}.Release|x64.Build.0 = Release|Any CPU {351755B9-4C7F-4B51-B813-3203DC9A8284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {351755B9-4C7F-4B51-B813-3203DC9A8284}.Debug|Any CPU.Build.0 = Debug|Any CPU {351755B9-4C7F-4B51-B813-3203DC9A8284}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -2271,6 +2283,14 @@ Global {351755B9-4C7F-4B51-B813-3203DC9A8284}.Release|Any CPU.Build.0 = Release|Any CPU {351755B9-4C7F-4B51-B813-3203DC9A8284}.Release|x64.ActiveCfg = Release|Any CPU {351755B9-4C7F-4B51-B813-3203DC9A8284}.Release|x64.Build.0 = Release|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Debug|x64.Build.0 = Debug|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Release|Any CPU.Build.0 = Release|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Release|x64.ActiveCfg = Release|Any CPU + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2586,6 +2606,8 @@ Global {9FEE5178-557E-4694-A088-F157267C989C} = {B1B09684-A842-44FA-9D35-68BE66838A5C} {BC1E63D5-C997-4269-BCE9-A5FBE7BA7CA1} = {E8681596-D4BF-484A-A428-06D749FD4C5D} {351755B9-4C7F-4B51-B813-3203DC9A8284} = {9FEE5178-557E-4694-A088-F157267C989C} + {3AAD9D87-BC2D-446C-B56B-60F8A1492C30} = {C2FAC276-9D6E-498A-BBA2-F3F14ADF4D0D} + {FCD6DF86-4E3B-4CDC-9849-28884B3B40D8} = {C2FAC276-9D6E-498A-BBA2-F3F14ADF4D0D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Options/MasaAppConfigureOptions.cs b/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Options/MasaAppConfigureOptions.cs index 96d03082e..46990bfe4 100644 --- a/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Options/MasaAppConfigureOptions.cs +++ b/src/BuildingBlocks/Configuration/Masa.BuildingBlocks.Configuration/Options/MasaAppConfigureOptions.cs @@ -54,7 +54,7 @@ public bool TrySetVariable(string key, string variable, string defaultValue) return true; } - public void SetVariableAndDefaultValue(string key, string variable, string defaultValue) + public void SetVariable(string key, string variable, string defaultValue) { var variableInfo = VariableInfos.FirstOrDefault(v => v.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); if (variableInfo != null) VariableInfos.Remove(variableInfo); diff --git a/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs index 86cbd151d..a91f83222 100644 --- a/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs +++ b/src/BuildingBlocks/Data/Masa.BuildingBlocks.Data/Constants/ErrorCode.cs @@ -235,7 +235,7 @@ static ErrorCode() var fields = classType.GetFields(BindingFlags.Static | BindingFlags.Public); foreach (var field in fields) { - var errorMessage = AttributeUtils.GetDescriptionByField(field); + var errorMessage = AttributeUtils.GetDescriptionValueByField(field); _errorCodeMessageDictionary.Add(field.GetRawConstantValue()!.ToString()!, errorMessage); } diff --git a/src/Contrib/Configuration/Tests/Scenes/Masa.Contrib.Configuration.Tests.Scenes.DisableAutoMap/ServiceCollectionExtensionsTest.cs b/src/Contrib/Configuration/Tests/Scenes/Masa.Contrib.Configuration.Tests.Scenes.DisableAutoMap/ServiceCollectionExtensionsTest.cs index 7f9282039..22c39c138 100644 --- a/src/Contrib/Configuration/Tests/Scenes/Masa.Contrib.Configuration.Tests.Scenes.DisableAutoMap/ServiceCollectionExtensionsTest.cs +++ b/src/Contrib/Configuration/Tests/Scenes/Masa.Contrib.Configuration.Tests.Scenes.DisableAutoMap/ServiceCollectionExtensionsTest.cs @@ -12,7 +12,7 @@ public void TestInitializeAppConfiguration() var services = new ServiceCollection(); services.Configure(options => { - options.SetVariableAndDefaultValue(nameof(MasaAppConfigureOptions.Environment), "Env", "test1"); + options.SetVariable(nameof(MasaAppConfigureOptions.Environment), "Env", "test1"); }); services.AddMasaConfiguration(masaConfigurationBuilder => { @@ -34,7 +34,7 @@ public void TestInitializeAppConfiguration2() var services = new ServiceCollection(); services.Configure(options => { - options.SetVariableAndDefaultValue(nameof(MasaAppConfigureOptions.Environment), "Env", "test1"); + options.SetVariable(nameof(MasaAppConfigureOptions.Environment), "Env", "test1"); }); services.AddMasaConfiguration(); var serviceProvider = services.BuildServiceProvider(); diff --git a/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMapper.cs b/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMapper.cs index ed893e326..657e788c8 100644 --- a/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMapper.cs +++ b/src/Contrib/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMapper.cs @@ -12,21 +12,21 @@ public DefaultMapper(IMappingConfigProvider provider) public TDestination Map(TSource source, MapOptions? options = null) { - ArgumentNullException.ThrowIfNull(source, nameof(source)); + MasaArgumentException.ThrowIfNull(source); return source.Adapt(_provider.GetConfig(source.GetType(), typeof(TDestination), options)); } public TDestination Map(object source, MapOptions? options = null) { - ArgumentNullException.ThrowIfNull(source, nameof(source)); + MasaArgumentException.ThrowIfNull(source); return source.Adapt(_provider.GetConfig(source.GetType(), typeof(TDestination), options)); } public TDestination Map(TSource source, TDestination destination, MapOptions? options = null) { - ArgumentNullException.ThrowIfNull(source, nameof(source)); + MasaArgumentException.ThrowIfNull(source); Type destinationType = destination?.GetType() ?? typeof(TDestination); return source.Adapt(destination, _provider.GetConfig(source.GetType(), destinationType, options)); diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ByteExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ByteExtensions.cs new file mode 100644 index 000000000..b717e538a --- /dev/null +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ByteExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +public static class ByteExtensions +{ + public static string ToBase64String(this byte[] inArray) + => Convert.ToBase64String(inArray); + + public static string ConvertToString(this byte[] inArray, Encoding encoding) + => encoding.GetString(inArray); +} diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/FillType.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/FillType.cs similarity index 84% rename from src/Utils/Security/Masa.Utils.Security.Cryptography/FillType.cs rename to src/Utils/Extensions/Masa.Utils.Extensions.DotNet/FillType.cs index 2709d93ce..6b00b92d6 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/FillType.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/FillType.cs @@ -1,15 +1,19 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Security.Cryptography; +// ReSharper disable once CheckNamespace + +namespace System; public enum FillType { NoFile = 1, + /// /// left fill /// Left = 2, + /// /// right fill /// diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/JsonSerializerExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/JsonSerializerExtensions.cs index 8ad969428..73d16cfe1 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/JsonSerializerExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/JsonSerializerExtensions.cs @@ -14,7 +14,7 @@ namespace System.Text.Json; /// /// It requires a reference to the "System.Linq.Expressions" assembly. /// -[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +[ExcludeFromCodeCoverage] public static class JsonSerializerExtensions { /// diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/MethodInfoExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/MethodInfoExtensions.cs index 92a18e1f7..c94a92b72 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/MethodInfoExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/MethodInfoExtensions.cs @@ -3,7 +3,7 @@ // ReSharper disable once CheckNamespace -namespace System; +namespace System.Reflection; public static class MethodInfoExtensions { diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ObjectExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ObjectExtensions.cs new file mode 100644 index 000000000..2391cf5b2 --- /dev/null +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/ObjectExtensions.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System; + +public static class ObjectExtensions +{ + public static string GetGenericTypeName(this object @object) + => @object.GetType().GetGenericTypeName(); +} diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StreamExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StreamExtensions.cs new file mode 100644 index 000000000..fa61e3f1c --- /dev/null +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StreamExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace System.IO; + +public static class StreamExtensions +{ + public static byte[] ConvertToBytes(this Stream stream) + { + if (!stream.CanRead) return Array.Empty(); + + if (!stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var bytes = new byte[stream.Length]; + _ = stream.Read(bytes, 0, bytes.Length); + stream.Seek(0, SeekOrigin.Begin); + return bytes; + } + + public static async Task ConvertToBytesAsync(this Stream stream) + { + if (!stream.CanRead) return Array.Empty(); + + if (!stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var bytes = new Memory(new byte[stream.Length]); + _ = await stream.ReadAsync(bytes); + stream.Seek(0, SeekOrigin.Begin); + return bytes.ToArray(); + } +} diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs index d7d538eb6..731a9143e 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/StringExtensions.cs @@ -13,18 +13,6 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value) public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) => string.IsNullOrEmpty(value); - public static void CheckIsNullOrWhiteSpace(this string? value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (value.IsNullOrWhiteSpace()) - throw new ArgumentException($"{paramName} cannot be WhiteSpace or Null"); - } - - public static void CheckIsNullOrEmpty(this string? value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (value.IsNullOrEmpty()) - throw new ArgumentException($"{paramName} cannot be Empty or Null"); - } - public static string TrimStart(this string value, string trimParameter) => value.TrimStart(trimParameter, StringComparison.CurrentCulture); @@ -50,4 +38,32 @@ public static string TrimEnd(this string value, return value.Substring(0, value.Length - trimParameter.Length); } + + public static byte[] ConvertToBytes(this string value, Encoding encoding) + => encoding.GetBytes(value); + + public static byte[] FromBase64String(this string value) + => Convert.FromBase64String(value); + + public static string GetSpecifiedLengthString( + this string value, + int length, + Action action, + FillType fillType = FillType.NoFile, + char fillCharacter = ' ') + { + if (fillType == FillType.NoFile && value.Length < length) + action.Invoke(); + + var keyLength = value.Length; + if (keyLength == length) return value; + + if (keyLength > length) return value.Substring(0, length); + + if (fillType == FillType.Left) return value.PadLeft(length, fillCharacter); + + if (fillType == FillType.Right) return value.PadRight(length, fillCharacter); + + throw new NotSupportedException($"... Unsupported {nameof(fillType)}"); + } } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/TypeExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/TypeExtensions.cs index f7dfa661d..aa9f67209 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/TypeExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/TypeExtensions.cs @@ -24,24 +24,28 @@ public static string GetGenericTypeName(this Type type) return typeName; } - public static string GetGenericTypeName(this object @object) - => @object.GetType().GetGenericTypeName(); - public static bool IsNullableType(this Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); /// /// Determine if a class is derived from a generic class - /// todo: The restrictions on type and genericType will be removed in the future /// - /// common class, Generic classes are not supported - /// generic class, Only interface generics are supported + /// + /// base class type, Only interface generics are supported /// public static bool IsImplementerOfGeneric(this Type type, Type genericType) { - if (!genericType.GetTypeInfo().IsGenericType || !type.IsClass) + if (!genericType.GetTypeInfo().IsGenericType) return false; + if (genericType.IsInterface) + return type.IsImplementerOfGenericInterface(genericType); + + return type.IsImplementerOfGenericClass(genericType); + } + + private static bool IsImplementerOfGenericInterface(this Type type, Type genericType) + { return type.GetInterfaces().Any(interfaceType => { var current = interfaceType.GetTypeInfo().IsGenericType ? @@ -49,4 +53,18 @@ public static bool IsImplementerOfGeneric(this Type type, Type genericType) return current == genericType; }); } + + private static bool IsImplementerOfGenericClass(this Type type, Type genericType) + { + var currentType = type.GetTypeInfo().IsGenericType ? + type.GetGenericTypeDefinition() : type; + if (currentType == genericType) + return true; + + var baseType = currentType.BaseType; + if (baseType == null) + return false; + + return baseType.IsImplementerOfGenericClass(genericType); + } } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs index fb603a7ec..2a9f9e9c4 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.DotNet/Utils/AttributeUtils.cs @@ -7,26 +7,114 @@ namespace System; public static class AttributeUtils { + [Obsolete("Use GetDescriptionValueByField instead")] public static string? GetDescriptionByConst(string fieldName, BindingFlags? bindingFlags = null) - => GetDescriptionByConst(typeof(TClass), fieldName, bindingFlags); + => GetDescriptionValueByField(typeof(TClass), fieldName, bindingFlags); - public static string? GetDescriptionByConst(Type type, string fieldName, BindingFlags? bindingFlags = null) + [Obsolete("Use GetDescriptionValueByField instead")] + public static string? GetDescriptionByConst(Type classType, string fieldName, BindingFlags? bindingFlags = null) + => GetDescriptionValueByField(classType, fieldName, bindingFlags); + + public static string? GetDescriptionByField(string fieldName, BindingFlags? bindingFlags = null) + => GetDescriptionValueByField(typeof(TClass), fieldName, bindingFlags); + + public static string? GetDescriptionValueByField(Type classType, string fieldName, BindingFlags? bindingFlags = null) { - var fieldInfo = type.GetField(fieldName, bindingFlags ?? BindingFlags.Public | BindingFlags.Static); - if (fieldInfo == null) - return null; + var fieldInfo = GetFieldInfo(classType, fieldName, bindingFlags); + if (fieldInfo == null) return null; - return GetDescriptionByField(fieldInfo); + return GetDescriptionValueByField(fieldInfo); } - public static string? GetDescriptionByField(FieldInfo fieldInfo) - => GetFieldAttributeValue(fieldInfo, attribute => attribute.Description); + public static string? GetDescriptionValueByField(FieldInfo fieldInfo) + => GetCustomAttributeValue(fieldInfo, attribute => attribute.Description); + + public static IEnumerable GetCustomAttributes( + string fieldName, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttributes(typeof(TClass), fieldName, bindingFlags, inherit); + + public static IEnumerable GetCustomAttributes( + Type classType, + string fieldName, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttributes(classType, fieldName, out _, bindingFlags, inherit); + + public static IEnumerable GetCustomAttributes( + string fieldName, + out bool existFieldInfo, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttributes(typeof(TClass), fieldName,out existFieldInfo, bindingFlags, inherit); + + public static IEnumerable GetCustomAttributes( + Type classType, + string fieldName, + out bool existFieldInfo, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + { + var fieldInfo = GetFieldInfo(classType, fieldName, bindingFlags); + existFieldInfo = fieldInfo != null; + if (!existFieldInfo) return new List(); + + return fieldInfo!.GetCustomAttributes(inherit); + } - public static TOpt? GetFieldAttributeValue( + public static TAttribute? GetCustomAttribute( + string fieldName, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttribute(typeof(TClass), fieldName, bindingFlags, inherit); + + public static TAttribute? GetCustomAttribute( + Type classType, + string fieldName, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttribute(classType, fieldName, out _, bindingFlags, inherit); + + public static TAttribute? GetCustomAttribute( + string fieldName, + out bool existFieldInfo, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + => GetCustomAttribute(typeof(TClass), fieldName,out existFieldInfo, bindingFlags, inherit); + + public static TAttribute? GetCustomAttribute( + Type classType, + string fieldName, + out bool existFieldInfo, + BindingFlags? bindingFlags = null, + bool inherit = true) + where TAttribute : Attribute + { + var fieldInfo = GetFieldInfo(classType, fieldName, bindingFlags); + existFieldInfo = fieldInfo != null; + if (!existFieldInfo) return null; + + return fieldInfo!.GetCustomAttribute(inherit); + } + + public static TOpt? GetCustomAttributeValue( FieldInfo fieldInfo, - Func valueSelector) + Func valueSelector, + bool inherit = true) where TAttribute : Attribute { - return fieldInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute att ? valueSelector(att) : default; + var attribute = fieldInfo.GetCustomAttribute(inherit); + return attribute == null ? default : valueSelector(attribute); } + + private static FieldInfo? GetFieldInfo(Type type, string fieldName, BindingFlags? bindingFlags = null) + => type.GetField(fieldName, bindingFlags ?? BindingFlags.Public | BindingFlags.Static); } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumExtensions.cs index 21882d7ae..ab347cd37 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumExtensions.cs @@ -1,36 +1,27 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; public static class EnumExtensions { - public static DescriptionAttribute GetDescription(this Enum enumSubitem) - { - string value = enumSubitem.ToString(); + public static string GetDescriptionValue(this Enum enumValue) + => enumValue.GetCustomAttribute(() => new DescriptionAttribute(enumValue.ToString()))!.Description; - var fieldInfo = enumSubitem.GetType().GetField(value); + public static DescriptionAttribute GetDescription(this Enum enumValue) + => enumValue.GetCustomAttribute(() => new DescriptionAttribute(enumValue.ToString()))!; - if (fieldInfo != null) - { - var attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + public static TAttribute GetCustomAttribute(this Enum enumValue) + where TAttribute : Attribute, new() + => enumValue.GetCustomAttribute(() => new TAttribute())!; - if (attributes == null || attributes.Length == 0) - { - return new DescriptionAttribute(value); - } - else - { - return (DescriptionAttribute)attributes[0]; - } - } - else - { - return new DescriptionAttribute(); - } - } + public static TAttribute? GetCustomAttribute(this Enum enumValue, Func defaultFunc) + where TAttribute : Attribute + { + var attribute = AttributeUtils.GetCustomAttribute(enumValue.GetType(), enumValue.ToString(), inherit: false); - public static T? GetAttribute(this Enum enumSubitem) - where T : Attribute, new() - => EnumUtil.GetSubitemAttribute(enumSubitem); + return attribute ?? defaultFunc(); + } } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumUtil.cs b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumUtil.cs index 28373e5f9..3a399ea61 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumUtil.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/EnumUtil.cs @@ -1,36 +1,134 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; public static class EnumUtil { - public static T? GetSubitemAttribute(object enumSubitem) - where T : Attribute, new() + [Obsolete("Use GetCustomAttribute instead")] + public static TAttribute? GetSubitemAttribute(object? enumSubitem) + where TAttribute : Attribute, new() + { + if (enumSubitem == null) return null; + + return GetCustomAttribute(enumSubitem.GetType(), enumSubitem); + } + + public static string? GetDescriptionValue(object? enumValue) + where TEnum : Enum + => GetDescriptionValue(typeof(TEnum), enumValue); + + public static string? GetDescriptionValue(Type enumType, object? enumValue) { - if (enumSubitem == null) + if (enumValue == null) return null; + + return GetCustomAttribute(enumType, enumValue, name => new DescriptionAttribute(name))? + .Description; + } + + public static DescriptionAttribute? GetDescription(object? enumValue) + where TEnum : Enum + => GetDescription(typeof(TEnum), enumValue); + + public static DescriptionAttribute? GetDescription(Type enumType, object? enumValue) + { + if (enumValue == null) return null; + + return GetCustomAttribute(enumType, enumValue, name => new DescriptionAttribute(name)); + } + + public static TAttribute? GetCustomAttribute(object? enumValue) + where TAttribute : Attribute, new() + where TEnum : Enum + => GetCustomAttribute(typeof(TEnum), enumValue, _ => new TAttribute()); + + public static TAttribute? GetCustomAttribute(Type enumType, object? enumValue) + where TAttribute : Attribute, new() + => GetCustomAttribute(enumType, enumValue, name => new TAttribute()); + + public static TAttribute? GetCustomAttribute(object? enumValue, Func defaultFunc) + where TAttribute : Attribute + where TEnum : Enum + => GetCustomAttribute(typeof(TEnum), enumValue, defaultFunc); + + public static TAttribute? GetCustomAttribute(Type enumType, object? enumValue, Func defaultFunc) + where TAttribute : Attribute + { + if (!enumType.IsEnum) throw new NotSupportedException(); + + var value = enumValue?.ToString(); + if (value == null) return null; - string value = enumSubitem.ToString() ?? ""; + var name = Enum.GetName(enumType, enumValue!); + if (name == null) return null; - var fieldInfo = enumSubitem.GetType().GetField(value); + var attribute = AttributeUtils.GetCustomAttribute(enumType, name, inherit: false); + return attribute ?? defaultFunc(name); + } - if (fieldInfo != null) - { - var attributes = fieldInfo.GetCustomAttributes(typeof(T), false); + public static Dictionary GetCustomAttributeDictionary() + where TAttribute : Attribute, new() + where TEnum : Enum + { + var enumType = typeof(TEnum); - if (attributes == null || attributes.Length == 0) - { - return new T(); - } - else + return Enum.GetNames(enumType) + .Select(name => (TEnum)Enum.Parse(enumType, name)) + .ToDictionary(e => e, e => e.GetCustomAttribute()); + } + + public static IEnumerable GetItems() + where TEnum : Enum + { + var enumType = typeof(TEnum); + return Enum.GetNames(enumType).Select(name => (TEnum)Enum.Parse(enumType, name)); + } + + public static List> GetList(bool withAll = false, string allName = "所有", int allValue = default) + where TEnum : Enum + => GetListCore(typeof(TEnum), withAll, allName, allValue); + + public static List> GetList(Type enumType, bool withAll = false, string allName = "所有", int allValue = default) + => GetListCore(enumType, withAll, allName, allValue); + + public static List> GetEnumList( + bool withAll = false, + string allName = "所有", + TEnum? allValue = default) + => GetListCore(typeof(TEnum), withAll, allName, allValue); + + private static List> GetListCore( + Type enumType, + bool withAll = false, + string allName = "所有", + TValue? allValue = default) + { + if (!enumType.IsEnum) throw new NotSupportedException(); + + var list = Enum + .GetNames(enumType) + .Select(name => { - return attributes[0] as T; - } - } - else + var @enum = Enum.Parse(enumType, name); + return new EnumObject() + { + Name = GetDescriptionValue(enumType, @enum)!, + Value = (TValue)@enum + }; + }).ToList(); + + if (withAll) { - return new T(); + list.Insert(0, new EnumObject + { + Name = allName, + Value = allValue + }); } + + return list; } } diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Masa.Utils.Extensions.Enums.csproj b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Masa.Utils.Extensions.Enums.csproj index 132c02c59..f13e36b82 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Masa.Utils.Extensions.Enums.csproj +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Masa.Utils.Extensions.Enums.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Model/EnumObject.cs b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Model/EnumObject.cs index 88ae47fe1..1ef10e347 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Model/EnumObject.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/Model/EnumObject.cs @@ -1,6 +1,8 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; public class EnumObject diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/_Enum.cs b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/_Enum.cs index 7303f79d3..24aa51c38 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Enums/_Enum.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Enums/_Enum.cs @@ -1,43 +1,44 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +// ReSharper disable once CheckNamespace + namespace System; public static class Enum where TEnum : Enum { + [Obsolete("Has no effect and will be deleted")] public static List GetAttributes() where TAttribute : Attribute, new() { var enumType = typeof(TEnum); - var names = Enum.GetNames(enumType); - var result = names.Select(name => EnumUtil.GetSubitemAttribute(Enum.Parse(enumType, name))!).ToList(); + var values = Enum.GetValues(enumType); - return result; + var list = new List(); + foreach (var value in values) + { + list.Add(EnumUtil.GetCustomAttribute(enumType, value)!); + } + + return list; } + [Obsolete("Use EnumUtil.GetCustomAttributeDictionary() instead")] public static Dictionary GetDictionary() where TAttribute : Attribute, new() - { - var enumType = typeof(TEnum); - - return Enum.GetNames(enumType) - .Select(name => (TEnum)Enum.Parse(enumType, name)) - .ToDictionary(@enum => @enum, @enum => EnumUtil.GetSubitemAttribute(@enum)!); - } - - public static List GetItems() - { - var enumType = typeof(TEnum); + => EnumUtil.GetCustomAttributeDictionary(); - return Enum.GetNames(enumType) - .Select(name => (TEnum)Enum.Parse(enumType, name)).ToList(); - } + [Obsolete("Use EnumUtil.GetItems() instead")] + public static List GetItems() => EnumUtil.GetItems().ToList(); + [Obsolete("Use EnumUtil.GetEnumList() or EnumUtil.GetList() instead")] public static List> GetEnumObjectList(bool withAll = false, string allName = "所有", int allValue = 0) => GetEnumObjectList(withAll, allName, allValue); - public static List> GetEnumObjectList(bool withAll = false, string allName = "所有", TValue? allValue = default) + [Obsolete("Use EnumUtil.GetEnumList() or EnumUtil.GetList() instead")] + public static List> GetEnumObjectList(bool withAll = false, string allName = "所有", + TValue? allValue = default) { var enumType = typeof(TEnum); @@ -47,15 +48,7 @@ public static List> GetEnumObjectList(bool withAll = if (fieldInfo != null) { var attribute = fieldInfo.GetCustomAttribute(false); - if (attribute == null) - { - return new EnumObject() - { - Name = name, - Value = (TValue)Enum.Parse(enumType, name) - }; - } - else + if (attribute != null) { return new EnumObject() { @@ -64,15 +57,12 @@ public static List> GetEnumObjectList(bool withAll = }; } } - else + return new EnumObject() { - return new EnumObject() - { - Name = name, - Value = (TValue)Enum.Parse(enumType, name) - }; - } - }).Where(p => p != null).ToList(); + Name = name, + Value = (TValue)Enum.Parse(enumType, name) + }; + }).ToList(); if (withAll) { @@ -86,12 +76,15 @@ public static List> GetEnumObjectList(bool withAll = return lstResult; } + [Obsolete("Use EnumUtil.GetList() instead")] public static Dictionary GetEnumObjectDictionary(bool withAll = false, string allName = "所有", int allValue = 0) { return GetEnumObjectDictionary(withAll, allName, allValue); } - public static Dictionary GetEnumObjectDictionary(bool withAll = false, string allName = "所有", TValue allValue = default!) + [Obsolete("Use EnumUtil.GetEnumList() or EnumUtil.GetList() instead")] + public static Dictionary GetEnumObjectDictionary(bool withAll = false, string allName = "所有", + TValue allValue = default!) where TValue : notnull { Dictionary keyValues = new Dictionary(); diff --git a/src/Utils/Extensions/Masa.Utils.Extensions.Expressions/ExpressionExtensions.cs b/src/Utils/Extensions/Masa.Utils.Extensions.Expressions/ExpressionExtensions.cs index 859dbff34..3e0465320 100644 --- a/src/Utils/Extensions/Masa.Utils.Extensions.Expressions/ExpressionExtensions.cs +++ b/src/Utils/Extensions/Masa.Utils.Extensions.Expressions/ExpressionExtensions.cs @@ -97,7 +97,7 @@ public class ParameterRebinder : ExpressionVisitor public ParameterRebinder(Dictionary map) { - this.map = map ?? new Dictionary(); + this.map = map; } public static Expression ReplaceParameters(Dictionary map, Expression exp) diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs index 14f2b141a..bda69199f 100644 --- a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/AttributeTest.cs @@ -3,6 +3,7 @@ namespace Masa.Utils.Extensions.DotNet.Tests; +#pragma warning disable CS0618 [TestClass] public class AttributeTest { @@ -11,6 +12,28 @@ public void TestGetDescriptionByConst() { var value = AttributeUtils.GetDescriptionByConst(typeof(ErrorCode), nameof(ErrorCode.FRAMEWORK_PREFIX)); Assert.AreEqual("Framework Prefix", value); + + value = AttributeUtils.GetDescriptionByConst(nameof(ErrorCode.FRAMEWORK_PREFIX)); + Assert.AreEqual("Test Framework Prefix", value); + + value = AttributeUtils.GetDescriptionByField("masa"); + Assert.AreEqual(null, value); + } + + [TestMethod] + public void TestGetCustomAttribute() + { + var attribute = AttributeUtils.GetCustomAttribute(nameof(TestErrorCode.FRAMEWORK_PREFIX)); + Assert.IsNotNull(attribute); + Assert.AreEqual("Test Framework Prefix", attribute.Description); + + attribute = AttributeUtils.GetCustomAttribute(nameof(TestErrorCode.FRAMEWORK_SUFFIX),out bool existFieldInfo); + Assert.IsTrue(existFieldInfo); + Assert.IsNull(attribute); + + attribute = AttributeUtils.GetCustomAttribute("test",out existFieldInfo); + Assert.IsFalse(existFieldInfo); + Assert.IsNull(attribute); } } @@ -19,3 +42,12 @@ public static class ErrorCode [System.ComponentModel.Description("Framework Prefix")] public const string FRAMEWORK_PREFIX = "MF"; } + +public class TestErrorCode +{ + [System.ComponentModel.Description("Test Framework Prefix")] + public const string FRAMEWORK_PREFIX = "MF"; + + public const string FRAMEWORK_SUFFIX = "MF_Suffix"; +} +#pragma warning restore CS0618 diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/IRepository.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/IRepository.cs new file mode 100644 index 000000000..5b57d5dfc --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/IRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.DotNet.Tests.Infrastructure; + +public interface IRepository where TEntity : class +{ + +} + +public interface IRepository : IRepository + where TEntity : class +{ + +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/Repository.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/Repository.cs new file mode 100644 index 000000000..63ee02bdc --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/Repository.cs @@ -0,0 +1,16 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.DotNet.Tests.Infrastructure; + +public class Repository : IRepository + where TEntity : class +{ + +} + +public class Repository : Repository + where TEntity : class +{ + +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/UserRepository.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/UserRepository.cs new file mode 100644 index 000000000..1b256b166 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/Infrastructure/UserRepository.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.DotNet.Tests.Infrastructure; + +public class UserRepository : Repository +{ + +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StreamExtensionsTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StreamExtensionsTest.cs new file mode 100644 index 000000000..7dcb964c9 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/StreamExtensionsTest.cs @@ -0,0 +1,28 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.DotNet.Tests; + +[TestClass] +public class StreamExtensionsTest +{ + [TestMethod] + public void TestConvertToBytes() + { + var str = "Hello MASA Stack"; + var dataBuffer = str.ConvertToBytes(Encoding.UTF8); + var memory = new MemoryStream(dataBuffer); + var actualBuffer = memory.ConvertToBytes(); + Assert.AreEqual(str, actualBuffer.ConvertToString(Encoding.UTF8)); + } + + [TestMethod] + public async Task TestConvertToBytesAsync() + { + var str = "Hello MASA Stack"; + var dataBuffer = str.ConvertToBytes(Encoding.UTF8); + var memory = new MemoryStream(dataBuffer); + var actualBuffer = await memory.ConvertToBytesAsync(); + Assert.AreEqual(str, actualBuffer.ConvertToString(Encoding.UTF8)); + } +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/TypeTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/TypeTest.cs index 803c9fe99..bfdc78306 100644 --- a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/TypeTest.cs +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/TypeTest.cs @@ -10,9 +10,28 @@ public class TypeTest public void TestIsImplementerOfGeneric() { var res = typeof(Demo).IsImplementerOfGeneric(typeof(IEnumerable<>)); - var res2 = typeof(Demo).IsImplementerOfGeneric(typeof(IList<>)); Assert.IsTrue(res); - Assert.IsTrue(res2); + + res = typeof(Demo).IsImplementerOfGeneric(typeof(IList<>)); + Assert.IsTrue(res); + + res = typeof(Repository<>).IsImplementerOfGeneric(typeof(IRepository<>)); + Assert.IsTrue(res); + + res = typeof(Repository<,>).IsImplementerOfGeneric(typeof(IRepository<>)); + Assert.IsTrue(res); + + res = typeof(IRepository<,>).IsImplementerOfGeneric(typeof(IRepository<>)); + Assert.IsTrue(res); + + res = typeof(UserRepository).IsImplementerOfGeneric(typeof(Repository<>)); + Assert.IsTrue(res); + + res = typeof(Repository<,>).IsImplementerOfGeneric(typeof(Repository<>)); + Assert.IsTrue(res); + + res = typeof(String).IsImplementerOfGeneric(typeof(IEquatable<>)); + Assert.IsTrue(res); } public class Demo : List diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs index e59416640..77ee159ef 100644 --- a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.DotNet.Tests/_Imports.cs @@ -1,5 +1,7 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +global using Masa.Utils.Extensions.DotNet.Tests.Infrastructure; global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System.Text; global using System.Text.Json; diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Attributes/ENameAttribute.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Attributes/ENameAttribute.cs new file mode 100644 index 000000000..cb31db39c --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Attributes/ENameAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Enums.Tests.Attributes; + +[AttributeUsage(AttributeTargets.All)] +public class ENameAttribute : Attribute +{ + public string Name { get; set; } + + public ENameAttribute() : this(string.Empty) + { + } + + public ENameAttribute(string name) + { + Name = name; + } +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumExtensionsTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumExtensionsTest.cs new file mode 100644 index 000000000..5ca6edf62 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumExtensionsTest.cs @@ -0,0 +1,57 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Enums.Tests; + +[TestClass] +public class EnumExtensionsTest +{ + [TestMethod] + public void TestGetDescription() + { + var attribute = Human.Boy.GetDescription(); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + attribute = Human.Girl.GetDescription(); + Assert.IsNotNull(attribute); + Assert.AreEqual(nameof(Human.Girl), attribute.Description); + } + + [TestMethod] + public void TestGetDescriptionValue() + { + var value = Human.Boy.GetDescriptionValue(); + Assert.IsNotNull(value); + Assert.AreEqual("BOY", value); + + value = Human.Girl.GetDescriptionValue(); + Assert.IsNotNull(value); + Assert.AreEqual(nameof(Human.Girl), value); + } + + [TestMethod] + public void TestGetAttribute() + { + var attribute = Human.Boy.GetCustomAttribute(); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + attribute = Human.Girl.GetCustomAttribute(); + Assert.IsNotNull(attribute); //若枚举未增加特性会默认初始化 + Assert.AreEqual(string.Empty, attribute.Description); + } + + + [TestMethod] + public void TestGetAttributeByFunc() + { + var attribute = Human.Boy.GetCustomAttribute(() => new DescriptionAttribute("男")); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + attribute = Human.Girl.GetCustomAttribute(() => new DescriptionAttribute("女")); + Assert.IsNotNull(attribute); + Assert.AreEqual("女", attribute.Description); + } +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumTest.cs new file mode 100644 index 000000000..e36ad98df --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumTest.cs @@ -0,0 +1,18 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Enums.Tests; + +#pragma warning disable CS0618 +[TestClass] +public class EnumTest +{ + [TestMethod] + public void TestGetAttributes() + { + var list = Enum.GetAttributes(); + Assert.IsNotNull(list); + Assert.AreEqual(2, list.Count); + } +} +#pragma warning restore CS0618 diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumUtilsTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumUtilsTest.cs new file mode 100644 index 000000000..ea118d82e --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/EnumUtilsTest.cs @@ -0,0 +1,169 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Enums.Tests; + +#pragma warning disable CS0618 +[TestClass] +public class EnumUtilsTest +{ + [TestMethod] + public void TestGetSubitemAttribute() + { + var attribute = EnumUtil.GetSubitemAttribute(Human.Boy); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + attribute = EnumUtil.GetSubitemAttribute(Human.Girl); + Assert.IsNotNull(attribute); + Assert.AreEqual("", attribute.Description); + } + + [TestMethod] + public void TestGetDescriptionValue() + { + var value = EnumUtil.GetDescriptionValue(1); + Assert.AreEqual("BOY", value); + + value = EnumUtil.GetDescriptionValue(2); + Assert.AreEqual(nameof(Human.Girl), value); + + value = EnumUtil.GetDescriptionValue(3); + Assert.AreEqual(null, value); + } + + [TestMethod] + public void TestGetDescription() + { + var attribute = EnumUtil.GetDescription(1); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + attribute = EnumUtil.GetDescription(2); + Assert.IsNotNull(attribute); + Assert.AreEqual(nameof(Human.Girl), attribute.Description); + + attribute = EnumUtil.GetDescription(3); + Assert.IsNull(attribute); + } + + [TestMethod] + public void TestGetCustomAttribute() + { + var attribute = EnumUtil.GetCustomAttribute(1); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + var eName = EnumUtil.GetCustomAttribute(1); + Assert.IsNotNull(eName); + Assert.AreEqual("男", eName.Name); + + attribute = EnumUtil.GetCustomAttribute(2); + Assert.IsNotNull(attribute); + Assert.AreEqual(string.Empty, attribute.Description); + + eName = EnumUtil.GetCustomAttribute(2); + Assert.IsNotNull(eName); + Assert.AreEqual(string.Empty, eName.Name); + + attribute = EnumUtil.GetCustomAttribute(3); + Assert.IsNull(attribute); + + eName = EnumUtil.GetCustomAttribute(3); + Assert.IsNull(eName); + + attribute = EnumUtil.GetCustomAttribute(null); + Assert.IsNull(attribute); + + eName = EnumUtil.GetCustomAttribute(null); + Assert.IsNull(eName); + } + + [TestMethod] + public void TestGetCustomAttributeByFund() + { + var attribute = EnumUtil.GetCustomAttribute(1, name => new DescriptionAttribute(name + "t")); + Assert.IsNotNull(attribute); + Assert.AreEqual("BOY", attribute.Description); + + var eName = EnumUtil.GetCustomAttribute(1, name => new ENameAttribute(name + "t")); + Assert.IsNotNull(eName); + Assert.AreEqual("男", eName.Name); + + attribute = EnumUtil.GetCustomAttribute(2, name => new DescriptionAttribute(name + "t")); + Assert.IsNotNull(attribute); + Assert.AreEqual(nameof(Human.Girl) + "t", attribute.Description); + + eName = EnumUtil.GetCustomAttribute(2, name => new ENameAttribute(name + "t")); + Assert.IsNotNull(eName); + Assert.AreEqual(nameof(Human.Girl) + "t", eName.Name); + + attribute = EnumUtil.GetCustomAttribute(3, name => new DescriptionAttribute(name + "t")); + Assert.IsNull(attribute); + + eName = EnumUtil.GetCustomAttribute(3, name => new ENameAttribute(name + "t")); + Assert.IsNull(eName); + } + + [TestMethod] + public void TestGetCustomAttributeDictionary() + { + var dictionary = EnumUtil.GetCustomAttributeDictionary(); + Assert.IsNotNull(dictionary); + Assert.AreEqual(2, dictionary.Count); + + Assert.IsTrue(dictionary.ContainsKey(Human.Boy)); + Assert.AreEqual("BOY", dictionary[Human.Boy].Description); + Assert.IsTrue(dictionary.ContainsKey(Human.Girl)); + Assert.AreEqual(string.Empty, dictionary[Human.Girl].Description); + } + + [TestMethod] + public void TestGetItems() + { + var list = EnumUtil.GetItems().ToList(); + Assert.AreEqual(2, list.Count); + Assert.IsTrue(list.Contains(Human.Boy)); + Assert.IsTrue(list.Contains(Human.Girl)); + } + + [TestMethod] + public void TestGetList() + { + var list = EnumUtil.GetList().ToList(); + Assert.AreEqual(2, list.Count); + Assert.AreEqual(1, list[0].Value); + Assert.AreEqual(2, list[1].Value); + Assert.AreEqual("BOY", list[0].Name); + Assert.AreEqual(nameof(Human.Girl), list[1].Name); + + list = EnumUtil.GetList(true).ToList(); + Assert.AreEqual(3, list.Count); + Assert.AreEqual(1, list[1].Value); + Assert.AreEqual(2, list[2].Value); + Assert.AreEqual("BOY", list[1].Name); + Assert.AreEqual(nameof(Human.Girl), list[2].Name); + + Assert.ThrowsException(() => EnumUtil.GetList(typeof(int))); + } + + [TestMethod] + public void TestGetEnumList() + { + var list = EnumUtil.GetEnumList().ToList(); + Assert.AreEqual(2, list.Count); + Assert.AreEqual(Human.Boy, list[0].Value); + Assert.AreEqual(Human.Girl, list[1].Value); + Assert.AreEqual("BOY", list[0].Name); + Assert.AreEqual(nameof(Human.Girl), list[1].Name); + + list = EnumUtil.GetEnumList(true).ToList(); + Assert.AreEqual(3, list.Count); + Assert.AreEqual(Human.Boy, list[1].Value); + Assert.AreEqual(Human.Girl, list[2].Value); + Assert.AreEqual("BOY", list[1].Name); + Assert.AreEqual(nameof(Human.Girl), list[2].Name); + } +} + +#pragma warning restore CS0618 diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Human.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Human.cs new file mode 100644 index 000000000..1f00e24c7 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Human.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Enums.Tests; + +public enum Human +{ + [EName("男")] + [Description("BOY")] + Boy = 1, + Girl +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Masa.Utils.Extensions.Enums.Tests.csproj b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Masa.Utils.Extensions.Enums.Tests.csproj new file mode 100644 index 000000000..07112757e --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/Masa.Utils.Extensions.Enums.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/_Imports.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/_Imports.cs new file mode 100644 index 000000000..24fc0fed2 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Enums.Tests/_Imports.cs @@ -0,0 +1,6 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using DescriptionAttribute = System.ComponentModel.DescriptionAttribute; +global using Masa.Utils.Extensions.Enums.Tests.Attributes; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/ExpressionExtensionsTest.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/ExpressionExtensionsTest.cs new file mode 100644 index 000000000..8d0a16528 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/ExpressionExtensionsTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Extensions.Expressions.Tests; + +[TestClass] +public class ExpressionExtensionsTest +{ + private readonly List _list; + + public ExpressionExtensionsTest() + { + _list = new List() + { + 1, 2, 3, 4, 5, 6, 7 + }; + } + + [TestMethod] + public void TestAnd() + { + Expression> condition = i => i > 0; + condition = condition.And(i => i < 5); + var list = _list.Where(condition.Compile()).ToList(); + Assert.AreEqual(4, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + Assert.AreEqual(3, list[2]); + Assert.AreEqual(4, list[3]); + + condition = i => i > 0; + condition = condition.And(false, i => i < 5); + + list = _list.Where(condition.Compile()).ToList(); + Assert.AreEqual(7, list.Count); + Assert.AreEqual(1, list[0]); + Assert.AreEqual(2, list[1]); + Assert.AreEqual(3, list[2]); + Assert.AreEqual(4, list[3]); + Assert.AreEqual(5, list[4]); + Assert.AreEqual(6, list[5]); + Assert.AreEqual(7, list[6]); + } + + [TestMethod] + public void TestOr() + { + Expression> condition = i => i > 5; + condition = condition.Or(i => i < 1); + + var list = _list.Where(condition.Compile()).ToList(); + Assert.AreEqual(2, list.Count); + Assert.AreEqual(6, list[0]); + Assert.AreEqual(7, list[1]); + } + + [TestMethod] + public void TestCompose() + { + Expression> condition = i => i > 5; + Expression> condition2 = i => i < 7; + var expression = condition.Compose(condition2, Expression.AndAlso); + var list = _list.Where(expression.Compile()).ToList(); + Assert.AreEqual(1, list.Count); + Assert.AreEqual(6, list[0]); + } +} diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/Masa.Utils.Extensions.Expressions.Tests.csproj b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/Masa.Utils.Extensions.Expressions.Tests.csproj new file mode 100644 index 000000000..70803d4f3 --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/Masa.Utils.Extensions.Expressions.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/_Imports.cs b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/_Imports.cs new file mode 100644 index 000000000..f67fa412d --- /dev/null +++ b/src/Utils/Extensions/Tests/Masa.Utils.Extensions.Expressions.Tests/_Imports.cs @@ -0,0 +1,5 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System.Linq.Expressions; diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/AesUtils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/AesUtils.cs index 8a23e7d78..b7c81dc51 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/AesUtils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/AesUtils.cs @@ -3,42 +3,25 @@ namespace Masa.Utils.Security.Cryptography; +#pragma warning disable S107 public class AesUtils : EncryptBase { - private static readonly byte[] DefaultIv = - { - 0x41, - 0x72, - 0x65, - 0x79, - 0x6F, - 0x75, - 0x6D, - 0x79, - 0x53, - 0x6E, - 0x6F, - 0x77, - 0x6D, - 0x61, - 0x6E, - 0x3F - }; + private static readonly string DefaultEncryptKey = InitDefaultEncryptKey(); - /// - /// Generate a key that complies with AES encryption rules - /// - /// - /// - public static string GenerateKey(int length) + private static readonly string DefaultEncryptIv = GlobalConfigurationUtils.DefaultAesEncryptIv; + + private static int DefaultEncryptKeyLength => GlobalConfigurationUtils.DefaultAesEncryptKeyLength; + + static string InitDefaultEncryptKey() { - var crypto = Aes.Create(); - crypto.KeySize = length; - crypto.BlockSize = 128; - crypto.GenerateKey(); - return Convert.ToBase64String(crypto.Key); + return GlobalConfigurationUtils.DefaultAesEncryptKey.GetSpecifiedLengthString( + DefaultEncryptKeyLength, () => + { + }, FillType.Right, ' '); } + #region Encrypt + /// /// Symmetric encryption algorithm AES RijndaelManaged encryption (RijndaelManaged (AES) algorithm is a block encryption algorithm) /// @@ -50,24 +33,26 @@ public static string Encrypt( string content, char fillCharacter = ' ', Encoding? encoding = null) - => Encrypt(content, GlobalConfigurationUtils.DefaultEncryKey, FillType.Right, fillCharacter, encoding); + => Encrypt(content, DefaultEncryptKey, FillType.Right, fillCharacter, encoding); /// /// Symmetric encryption algorithm AES RijndaelManaged encryption (RijndaelManaged (AES) algorithm is a block encryption algorithm) /// /// String to be encrypted - /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string + /// Encryption key, must have half-width characters. 16-bit or 24-bit or 32-bit length key or complement by fillType to calculate an 16-bit or 24-bit or 32-bit string /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 + /// Aes key length /// encrypted result public static string Encrypt( string content, string key, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => Encrypt(content, key, DefaultIv, fillType, fillCharacter, encoding); + Encoding? encoding = null, + int? aesLength = null) + => Encrypt(content, key, DefaultEncryptIv, fillType, fillCharacter, encoding, aesLength); /// /// Symmetric encryption algorithm AES RijndaelManaged encryption (RijndaelManaged (AES) algorithm is a block encryption algorithm) @@ -78,6 +63,7 @@ public static string Encrypt( /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 + /// Aes key length /// encrypted result public static string Encrypt( string content, @@ -85,22 +71,16 @@ public static string Encrypt( string iv, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - var ivBuffer = GetSafeEncoding(encoding).GetBytes(GetSpecifiedLengthString( - iv, - 16, - () => throw new ArgumentException(nameof(key), - $"Please enter a 16-bit iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - - return Encrypt(content, - key, - ivBuffer, - fillType, - fillCharacter, - encoding); + var currentEncoding = GetSafeEncoding(encoding); + var byteBuffer = EncryptToBytes( + currentEncoding.GetBytes(content), + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter) + ); + return byteBuffer.ToBase64String(); } /// @@ -108,422 +88,603 @@ public static string Encrypt( /// /// String to be encrypted /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length or complement by fillType to calculate an 16-bit string + /// 16-bit length or complement by fillType to calculate an 16-bit string /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 + /// Aes key length /// encrypted result public static string Encrypt( string content, string key, - byte[] iv, + byte[] ivBuffer, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { var currentEncoding = GetSafeEncoding(encoding); - key = GetSpecifiedLengthString( - key, - 32, - () => throw new ArgumentException(nameof(key), - $"Please enter a 32-bit AES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter); - using var aes = Aes.Create(); - aes.Key = currentEncoding.GetBytes(key); - aes.IV = iv; - using ICryptoTransform cryptoTransform = aes.CreateEncryptor(); - byte[] buffers = currentEncoding.GetBytes(content); - byte[] encryptedData = cryptoTransform.TransformFinalBlock(buffers, 0, buffers.Length); - return Convert.ToBase64String(encryptedData); + var byteBuffer = EncryptToBytes( + currentEncoding.GetBytes(content), + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + ivBuffer); + return byteBuffer.ToBase64String(); } /// - /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// Symmetric encryption algorithm AES RijndaelManaged encryption (RijndaelManaged (AES) algorithm is a block encryption algorithm) /// - /// String to be decrypted - /// character for complement - /// Encoding format, default UTF-8 - /// If the decryption succeeds, the decrypted string will be returned, and if it fails, the source string will be returned. - public static string Decrypt( - string content, - char fillCharacter = ' ', - Encoding? encoding = null) - => Decrypt(content, GlobalConfigurationUtils.DefaultEncryKey, FillType.Right, fillCharacter, encoding); + /// data array to be encrypted + /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length or complement by fillType to calculate an 16-bit string + /// encrypted result + public static byte[] EncryptToBytes( + byte[] dataBuffers, + byte[] keyBuffer, + byte[] ivBuffer) + => EncryptOrDecryptToBytes(dataBuffers, keyBuffer, ivBuffer, true); /// - /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// encrypted stream /// - /// String to be decrypted - /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// streams that require encryption + /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - /// Decryption success returns the decrypted string, failure returns empty - public static string Decrypt( - string content, + /// Aes key length + /// Returns the encrypted byte array + public static byte[] EncryptToBytes( + Stream stream, string key, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => Decrypt(content, key, DefaultIv, fillType, fillCharacter, encoding); + Encoding? encoding = null, + int? aesLength = null) + => EncryptToBytes(stream, key, DefaultEncryptIv, fillType, fillCharacter, encoding, aesLength); /// - /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// encrypted stream /// - /// String to be decrypted - /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length or complement by fillType to calculate an 16-bit string + /// streams that require encryption + /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length. 16-bit length key or complement by fillType to calculate an 16-bit string /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - /// Decryption success returns the decrypted string, failure returns empty - public static string Decrypt( - string content, + /// Aes key length + /// Returns the encrypted byte array + public static byte[] EncryptToBytes( + Stream stream, string key, string iv, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - var ivBuffer = GetSafeEncoding(encoding).GetBytes(GetSpecifiedLengthString( - iv, - 16, - () => throw new ArgumentException(nameof(key), - $"Please enter a 16-bit iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); + var currentEncoding = GetSafeEncoding(encoding); - return Decrypt(content, - key, - ivBuffer, - fillType, - fillCharacter, - encoding); + return EncryptToBytes(stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter)); } /// - /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// encrypted stream /// - /// String to be decrypted - /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length. 16-bit length key or complement by fillType to calculate an 16-bit string + /// streams that require encryption + /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length + /// Encoding format, default UTF-8 /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement - /// Encoding format, default UTF-8 - /// Decryption success returns the decrypted string, failure returns empty - public static string Decrypt( - string content, + /// Aes key length + /// Returns the encrypted byte array + public static byte[] EncryptToBytes( + Stream stream, string key, - byte[] iv, + byte[] ivBuffer, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { var currentEncoding = GetSafeEncoding(encoding); - key = GetSpecifiedLengthString( - key, - 32, - () => throw new ArgumentException(nameof(key), - $"Please enter a 32-bit AES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter); - using var aes = Aes.Create(); - aes.Key = currentEncoding.GetBytes(key); - aes.IV = iv; - using ICryptoTransform rijndaelDecrypt = aes.CreateDecryptor(); - byte[] buffers = Convert.FromBase64String(content); - byte[] decryptedData = rijndaelDecrypt.TransformFinalBlock(buffers, 0, buffers.Length); - return currentEncoding.GetString(decryptedData); + return EncryptToBytes( + stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + ivBuffer); } /// - /// encrypted file stream + /// encrypted stream + /// + /// streams that require encryption + /// Encryption key, 32-bit length + /// 16-bit length + /// Returns the encrypted byte array + public static byte[] EncryptToBytes( + Stream stream, + byte[] keyBuffer, + byte[] ivBuffer) + => EncryptOrDecryptToBytes(stream, keyBuffer, ivBuffer, true); + + /// + /// Encrypt the specified stream with AES and output a file + /// + /// stream to be encrypted + /// output file path + /// character for complement + /// Encoding format, default UTF-8 + public static void EncryptFile( + Stream stream, + string outputPath, + char fillCharacter = ' ', + Encoding? encoding = null) + => EncryptFile(stream, + DefaultEncryptKey, + outputPath, + FillType.Right, + fillCharacter, + encoding); + + /// + /// Encrypt the specified stream with AES and output a file /// - /// File streams that require encryption + /// stream to be encrypted /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// Whether to complement the key? default: no fill(Only supports 32-bit keys) + /// output file path + /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 - /// encrypted stream result - public static CryptoStream Encrypt( - FileStream fileStream, + /// Aes key length + public static void EncryptFile( + Stream stream, string key, + string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => Encrypt(fileStream, key, key, fillType, fillCharacter, encoding); + Encoding? encoding = null, + int? aesLength = null) + => EncryptFile(stream, key, DefaultEncryptIv, outputPath, fillType, fillCharacter, encoding, aesLength); /// - /// encrypted file stream + /// Encrypt the specified stream with AES and output a file /// - /// File streams that require encryption + /// stream to be encrypted /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length. 16-bit length key or complement by fillType to calculate an 16-bit string - /// Whether to complement the key? default: no fill(Only supports 32-bit keys) + /// 16-bit length + /// output file path + /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 - /// encrypted stream result - public static CryptoStream Encrypt( - FileStream fileStream, + /// Aes key length + public static void EncryptFile( + Stream stream, string key, - string iv, + byte[] ivBuffer, + string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { var currentEncoding = GetSafeEncoding(encoding); - - var ivBuffer = currentEncoding.GetBytes(GetSpecifiedLengthString(iv, - 16, - () => throw new ArgumentException(nameof(iv), - $"Please enter a 16-bit iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - - return Encrypt(fileStream, key, ivBuffer, fillType, fillCharacter, encoding); + EncryptOrDecryptFile( + stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + ivBuffer, + true, + outputPath); } /// - /// encrypted file stream + /// Encrypt the specified stream with AES and output a file /// - /// File streams that require encryption + /// stream to be encrypted /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length - /// Whether to complement the key? default: no fill(Only supports 32-bit keys) + /// 16-bit length or complement by fillType to calculate an 16-bit string + /// output file path + /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 - /// encrypted stream result - public static CryptoStream Encrypt( - FileStream fileStream, + /// Aes key length + public static void EncryptFile( + Stream stream, string key, - byte[] iv, + string iv, + string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - if (iv.Length != 16) - { - throw new Exception($"The {nameof(iv)} length is invalid. The {nameof(iv)} iv length needs 16 bits!"); - } - var currentEncoding = GetSafeEncoding(encoding); - key = GetSpecifiedLengthString(key, - 32, - () => throw new ArgumentException(nameof(key), - $"Please enter a 32-bit AES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter); - - using var aes = Aes.Create(); - aes.Key = currentEncoding.GetBytes(key); - aes.IV = iv; - using var cryptoTransform = aes.CreateEncryptor(); - return new CryptoStream(fileStream, cryptoTransform, CryptoStreamMode.Write); + EncryptOrDecryptFile( + stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter), + true, + outputPath); } + #endregion + + #region Decrypt + + /// + /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// + /// String to be decrypted + /// character for complement + /// Encoding format, default UTF-8 + /// If the decryption succeeds, the decrypted string will be returned, and if it fails, the source string will be returned. + public static string Decrypt( + string content, + char fillCharacter = ' ', + Encoding? encoding = null) + => Decrypt(content, DefaultEncryptKey, FillType.Right, fillCharacter, encoding); + /// - /// Decrypt the file stream + /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string /// - /// file stream to be decrypted + /// String to be decrypted /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - /// Decrypt the stream result - public static CryptoStream Decrypt( - FileStream fileStream, + /// Aes key length + /// Decryption success returns the decrypted string, failure returns empty + public static string Decrypt( + string content, + string key, + FillType fillType = FillType.NoFile, + char fillCharacter = ' ', + Encoding? encoding = null, + int? aesLength = null) + => Decrypt(content, key, DefaultEncryptIv, fillType, fillCharacter, encoding, aesLength); + + /// + /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string + /// + /// String to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length or complement by fillType to calculate an 16-bit string + /// Whether to complement the key? default: no fill(Only supports 32-bit keys) + /// character for complement + /// Encoding format, default UTF-8 + /// Aes key length + /// Decryption success returns the decrypted string, failure returns empty + public static string Decrypt( + string content, string key, string iv, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { var currentEncoding = GetSafeEncoding(encoding); - - var ivBuffer = currentEncoding.GetBytes(GetSpecifiedLengthString(iv, - 16, - () => throw new ArgumentException(nameof(iv), - $"Please enter a 16-bit iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - - return Decrypt(fileStream, key, ivBuffer, fillType, fillCharacter, encoding); + var decryptBuffer = DecryptToBytes( + content.FromBase64String(), + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter)); + return decryptBuffer.ConvertToString(currentEncoding); } /// - /// Decrypt the file stream + /// Symmetric encryption algorithm AES RijndaelManaged decrypts the string /// - /// file stream to be decrypted + /// String to be decrypted /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length + /// 16-bit length. 16-bit length key or complement by fillType to calculate an 16-bit string /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - /// Decrypt the stream result - public static CryptoStream Decrypt( - FileStream fileStream, + /// Aes key length + /// Decryption success returns the decrypted string, failure returns empty + public static string Decrypt( + string content, string key, - byte[] iv, + byte[] ivBuffer, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - if (iv.Length != 16) - { - throw new Exception($"The {nameof(iv)} length is invalid. The {nameof(iv)} iv length needs 16 bits!"); - } - - key = GetSpecifiedLengthString(key, - 32, - () => throw new ArgumentException(nameof(key), - $"Please enter a 32-bit AES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter); - var currentEncoding = GetSafeEncoding(encoding); - using var aes = Aes.Create(); - aes.Key = currentEncoding.GetBytes(key); - aes.IV = iv; - using var cryptoTransform = aes.CreateDecryptor(); - return new CryptoStream(fileStream, cryptoTransform, CryptoStreamMode.Read); + var decryptBuffer = DecryptToBytes( + content.FromBase64String(), + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + ivBuffer + ); + return decryptBuffer.ConvertToString(currentEncoding); } /// - /// Encrypt the specified stream with AES and output a file + /// Symmetric encryption algorithm AES RijndaelManaged decrypts /// - /// file stream to be encrypted - /// output file path + /// data array to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length. 16-bit length key or complement by fillType to calculate an 16-bit string + /// + public static byte[] DecryptToBytes( + byte[] dataBuffers, + byte[] keyBuffer, + byte[] ivBuffer) + => EncryptOrDecryptToBytes( + dataBuffers, + keyBuffer, + ivBuffer, + false); + + /// + /// Decrypt the stream + /// + /// stream to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - public static void EncryptFile( - FileStream fileStream, - string outputPath, + /// Aes key length + /// returns the decrypted byte array + public static byte[] DecryptToBytes( + Stream stream, + string key, + FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => EncryptFile(fileStream, - GlobalConfigurationUtils.DefaultEncryKey, - outputPath, - FillType.Right, - fillCharacter, - encoding); + Encoding? encoding = null, + int? aesLength = null) + => DecryptToBytes(stream, key, DefaultEncryptIv, fillType, fillCharacter, encoding, aesLength); /// - /// Encrypt the specified stream with AES and output a file + /// Decrypt the stream /// - /// file stream to be encrypted - /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// output file path - /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) + /// stream to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length + /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - public static void EncryptFile( - FileStream fileStream, + /// Aes key length + /// returns the decrypted byte array + public static byte[] DecryptToBytes( + Stream stream, string key, - string outputPath, + string iv, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => EncryptFile(fileStream, key, key, outputPath, fillType, fillCharacter, encoding); + Encoding? encoding = null, + int? aesLength = null) + { + var currentEncoding = GetSafeEncoding(encoding); + return DecryptToBytes( + stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter)); + } /// - /// Encrypt the specified stream with AES and output a file + /// Decrypt the stream /// - /// file stream to be encrypted - /// Encryption key, must have half-width characters. 32-bit length key or complement by fillType to calculate an 32-bit string - /// 16-bit length or complement by fillType to calculate an 16-bit string - /// output file path - /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) + /// stream to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length + /// Whether to complement the key? default: no fill(Only supports 32-bit keys) /// character for complement /// Encoding format, default UTF-8 - public static void EncryptFile( - FileStream fileStream, + /// Aes key length + /// returns the decrypted byte array + public static byte[] DecryptToBytes( + Stream stream, string key, - string iv, - string outputPath, + byte[] ivBuffer, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - using var fileStreamOut = new FileStream(outputPath, FileMode.Create); - using var cryptoStream = Encrypt(fileStream, key, iv, fillType, fillCharacter, encoding); - byte[] buffers = new byte[1024]; - while (true) - { - var count = cryptoStream.Read(buffers, 0, buffers.Length); - fileStreamOut.Write(buffers, 0, count); - if (count < buffers.Length) - { - break; - } - } + var currentEncoding = GetSafeEncoding(encoding); + return DecryptToBytes(stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + ivBuffer); } /// - /// AES decrypt the specified file stream and output the file + /// Decrypt the stream + /// + /// stream to be decrypted + /// Decryption key, 32-bit length + /// 16-bit length + /// returns the decrypted byte array + public static byte[] DecryptToBytes( + Stream stream, + byte[] keyBuffer, + byte[] ivBuffer) + => EncryptOrDecryptToBytes(stream, keyBuffer, ivBuffer, false); + + /// + /// AES decrypt the specified stream and output the file /// - /// file stream to be decrypted + /// stream to be decrypted /// output file path /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 public static void DecryptFile( - FileStream fileStream, + Stream stream, string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => DecryptFile(fileStream, GlobalConfigurationUtils.DefaultEncryKey, outputPath, fillType, fillCharacter, encoding); + => DecryptFile(stream, DefaultEncryptKey, outputPath, fillType, fillCharacter, encoding); /// - /// AES decrypt the specified file stream and output the file + /// AES decrypt the specified stream and output the file /// - /// file stream to be decrypted + /// stream to be decrypted /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string /// output file path /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 + /// Aes key length public static void DecryptFile( - FileStream fileStream, + Stream stream, string key, string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) - => DecryptFile(fileStream, key, key, outputPath, fillType, fillCharacter, encoding); + Encoding? encoding = null, + int? aesLength = null) + => DecryptFile(stream, key, DefaultEncryptIv, outputPath, fillType, fillCharacter, encoding, aesLength); + + /// + /// AES decrypt the specified stream and output the file + /// + /// stream to be decrypted + /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string + /// 16-bit length + /// output file path + /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) + /// character for complement + /// Encoding format, default UTF-8 + /// Aes key length + public static void DecryptFile( + Stream stream, + string key, + byte[] ivBuffer, + string outputPath, + FillType fillType = FillType.NoFile, + char fillCharacter = ' ', + Encoding? encoding = null, + int? aesLength = null) + { + var currentEncoding = GetSafeEncoding(encoding); + EncryptOrDecryptFile(stream, GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), ivBuffer, false, outputPath); + } /// - /// AES decrypt the specified file stream and output the file + /// AES decrypt the specified stream and output the file /// - /// file stream to be decrypted + /// stream to be decrypted /// Decryption key, same as encryption key. 32-bit length key or complement by fillType to calculate an 32-bit string /// 16-bit length or complement by fillType to calculate an 16-bit string /// output file path /// Whether to complement the key? default: no fill(Only supports 32-bit keys or 16-bit iv) /// character for complement /// Encoding format, default UTF-8 + /// Aes key length public static void DecryptFile( - FileStream fileStream, + Stream stream, string key, string iv, string outputPath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', - Encoding? encoding = null) + Encoding? encoding = null, + int? aesLength = null) { - using FileStream fileStreamOut = new(outputPath, FileMode.Create); - using CryptoStream cryptoStream = Decrypt(fileStream, key, iv, fillType, fillCharacter, encoding); - byte[] buffers = new byte[1024]; - while (true) - { - var count = cryptoStream.Read(buffers, 0, buffers.Length); - fileStreamOut.Write(buffers, 0, count); - if (count < buffers.Length) - { - break; - } - } + var currentEncoding = GetSafeEncoding(encoding); + EncryptOrDecryptFile( + stream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter, aesLength), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter), + false, + outputPath); + } + + #endregion + + #region private methods + + private static byte[] GetKeyBuffer(string key, + Encoding encoding, + FillType fillType, + char fillCharacter, + int? aesLength = null) + { + string paramName = nameof(key); + if (aesLength != null && aesLength != 16 && aesLength != 24 && aesLength != 32) + throw new ArgumentException("Aes key length can only be 16, 24 or 32 bits", nameof(aesLength)); + + var length = aesLength ?? DefaultEncryptKeyLength; + return GetBytes( + key, + encoding, + fillType, + fillCharacter, + length, + () => throw new ArgumentException($"Please enter a {length}-bit AES key or allow {nameof(fillType)} to Left or Right", + paramName)); + } + + private static byte[] GetIvBuffer(string iv, + Encoding encoding, + FillType fillType, + char fillCharacter) + { + string paramName = nameof(iv); + return GetBytes( + iv, + encoding, + fillType, + fillCharacter, + 16, + () => throw new ArgumentException($"Please enter a 16-bit iv or allow {nameof(fillType)} to Left or Right", paramName)); } + + public static byte[] EncryptOrDecryptToBytes( + byte[] dataBuffers, + byte[] keyBuffer, + byte[] ivBuffer, + bool isEncrypt) + { + using var aes = Aes.Create(); + var transform = GetTransform(aes, keyBuffer, ivBuffer, isEncrypt); + return transform.TransformFinalBlock(dataBuffers, 0, dataBuffers.Length); + } + + /// + /// Decrypt the stream + /// + /// stream to be decrypted + /// Decryption key, 32-bit length + /// 16-bit length + /// encrypt (true) or decrypt (false) + /// returns the decrypted byte array + private static byte[] EncryptOrDecryptToBytes( + Stream stream, + byte[] keyBuffer, + byte[] ivBuffer, + bool isEncrypt) + { + using var aes = Aes.Create(); + var transform = GetTransform(aes, keyBuffer, ivBuffer, isEncrypt); + using MemoryStream ms = new MemoryStream(); + var cryptoStream = new CryptoStream(ms, transform, CryptoStreamMode.Write); + var cipherBytes = stream.ConvertToBytes(); + cryptoStream.Write(cipherBytes, 0, cipherBytes.Length); + cryptoStream.FlushFinalBlock(); + cryptoStream.Close(); + return ms.ToArray(); + } + + private static void EncryptOrDecryptFile( + Stream stream, + byte[] keyBuffer, + byte[] ivBuffer, + bool isEncrypt, + string outputPath) + { + using var fileStreamOut = new FileStream(outputPath, FileMode.Create); + using var aes = Aes.Create(); + var transform = GetTransform(aes, keyBuffer, ivBuffer, isEncrypt); + EncryptOrDecryptFile(stream, fileStreamOut, transform); + } + + #endregion + } +#pragma warning restore S107 diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/Base64Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/Base64Utils.cs index 5db13331a..b25d56b15 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/Base64Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/Base64Utils.cs @@ -17,7 +17,7 @@ public class Base64Utils : EncryptBase public static string Encrypt(string content, Encoding? encoding = null) { byte[] buffers = GetSafeEncoding(encoding).GetBytes(content); - return Convert.ToBase64String(buffers); + return buffers.ToBase64String(); } /// @@ -29,6 +29,6 @@ public static string Encrypt(string content, Encoding? encoding = null) public static string Decrypt(string content, Encoding? encoding = null) { byte[] buffers = Convert.FromBase64String(content); - return GetSafeEncoding(encoding).GetString(buffers); + return buffers.ConvertToString(GetSafeEncoding(encoding)); } } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryType.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryptType.cs similarity index 73% rename from src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryType.cs rename to src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryptType.cs index 2890c91a9..257f8aee3 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryType.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/DESEncryptType.cs @@ -3,7 +3,9 @@ namespace Masa.Utils.Security.Cryptography; -public enum DESEncryType +#pragma warning disable S2342 +// ReSharper disable once InconsistentNaming +public enum DESEncryptType { /// /// original DES encryption @@ -15,3 +17,4 @@ public enum DESEncryType /// Improved } +#pragma warning restore S2342 diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/DesUtils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/DESUtils.cs similarity index 63% rename from src/Utils/Security/Masa.Utils.Security.Cryptography/DesUtils.cs rename to src/Utils/Security/Masa.Utils.Security.Cryptography/DESUtils.cs index ca89712d2..077d8fc90 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/DesUtils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/DESUtils.cs @@ -6,36 +6,60 @@ namespace Masa.Utils.Security.Cryptography; /// /// DES symmetric encryption and decryption /// -public class DesUtils : EncryptBase +#pragma warning disable S101 +#pragma warning disable S2342 +// ReSharper disable once InconsistentNaming +// ReSharper disable once ClassNeverInstantiated.Global +public class DESUtils : EncryptBase { /// /// Default encryption key /// - private static readonly string DefaultEncryptKey = MD5Utils.EncryptRepeat(GlobalConfigurationUtils.DefaultEncryKey, 2); + private static readonly string DefaultEncryptKey = + GlobalConfigurationUtils.DefaultDesEncryptKey + .GetSpecifiedLengthString(8, + () => + { + + }, FillType.Right); + + /// + /// Default encryption iv + /// + private static readonly string DefaultEncryptIv = + GlobalConfigurationUtils.DefaultDesEncryptIv + .GetSpecifiedLengthString(8, + () => + { + + }, FillType.Right); + +#pragma warning disable S5547 +#pragma warning disable S107 /// - /// 使用默认加密 + /// Des encrypted string /// - /// 被加密的字符串 - /// Des encryption method, default: improved (easy to transmit) + /// encrypted string + /// Des encryption method, default: improved (easy to transmit) /// Whether to convert the encrypted string to lowercase /// character for complement /// Encoding format, default UTF-8 /// encrypted result public static string Encrypt( string content, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, bool isToLower = true, char fillCharacter = ' ', Encoding? encoding = null) - => Encrypt(content, DefaultEncryptKey, desEncryType, isToLower, FillType.Right, fillCharacter, encoding); + => Encrypt(content, DefaultEncryptKey, desEncryptType, isToLower, FillType.Right, fillCharacter, encoding); /// /// Des encrypted string /// /// String to be encrypted /// 8-bit length key or complement by fillType to calculate an 8-bit string - /// Des encryption method, default: improved (easy to transmit) + /// Des encryption method, default: improved (easy to transmit) /// Whether to convert the encrypted string to lowercase /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement @@ -44,12 +68,12 @@ public static string Encrypt( public static string Encrypt( string content, string key, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, bool isToLower = true, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => Encrypt(content, key, key, desEncryType, isToLower, fillType, fillCharacter, encoding); + => Encrypt(content, key, DefaultEncryptIv, desEncryptType, isToLower, fillType, fillCharacter, encoding); /// /// Des encrypted string @@ -57,7 +81,7 @@ public static string Encrypt( /// String to be encrypted /// 8-bit length key or complement by fillType to calculate an 8-bit string /// 8-bit length key or complement by fillType to calculate an 8-bit string - /// Des encryption method, default: improved (easy to transmit) + /// Des encryption method, default: improved (easy to transmit) /// Whether to convert the encrypted string to lowercase /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement @@ -67,35 +91,23 @@ public static string Encrypt( string content, string key, string iv, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, bool isToLower = true, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) { var currentEncoding = GetSafeEncoding(encoding); -#pragma warning disable S2278 - var des = DES.Create(); - des.Key = currentEncoding.GetBytes( - GetSpecifiedLengthString(key, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - des.IV = currentEncoding.GetBytes( - GetSpecifiedLengthString(iv, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - using MemoryStream memoryStream = new MemoryStream(); byte[] buffer = currentEncoding.GetBytes(content); - using CryptoStream cs = new CryptoStream(memoryStream, des.CreateEncryptor(), CryptoStreamMode.Write); + var des = DES.Create(); + var keyBuffer = GetKeyBuffer(key, currentEncoding, fillType, fillCharacter); + var ivBuffer = GetIvBuffer(iv, currentEncoding, fillType, fillCharacter); + using CryptoStream cs = new CryptoStream(memoryStream, des.CreateEncryptor(keyBuffer, ivBuffer), CryptoStreamMode.Write); cs.Write(buffer, 0, buffer.Length); cs.FlushFinalBlock(); - if (desEncryType == DESEncryType.Normal) - return Convert.ToBase64String(memoryStream.ToArray()); + if (desEncryptType == DESEncryptType.Normal) + return memoryStream.ToArray().ToBase64String(); StringBuilder stringBuilder = new(); foreach (byte b in memoryStream.ToArray()) @@ -104,29 +116,28 @@ public static string Encrypt( } return stringBuilder.ToString(); -#pragma warning restore S2278 } /// /// DES decryption with default key /// /// String to be decrypted - /// Des encryption method, default: improved (easy to transmit) + /// Des encryption method, default: improved (easy to transmit) /// character for complement /// Encoding format, default UTF-8 /// decrypted result public static string Decrypt(string content, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, char fillCharacter = ' ', Encoding? encoding = null) - => Decrypt(content, DefaultEncryptKey, desEncryType, FillType.Right, fillCharacter, encoding); + => Decrypt(content, DefaultEncryptKey, desEncryptType, FillType.Right, fillCharacter, encoding); /// /// DES decryption /// /// String to be decrypted /// 8-bit length key - /// Des encryption method, default: improved (easy to transmit) + /// Des encryption method, default: improved (easy to transmit) /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 @@ -134,11 +145,11 @@ public static string Decrypt(string content, public static string Decrypt( string content, string key, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => Decrypt(content, key, key, desEncryType, fillType, fillCharacter, encoding); + => Decrypt(content, key, DefaultEncryptIv, desEncryptType, fillType, fillCharacter, encoding); /// /// DES decryption @@ -146,7 +157,7 @@ public static string Decrypt( /// String to be decrypted /// 8-bit length key or complement by fillType to calculate an 8-bit string /// 8-bit length key or complement by fillType to calculate an 8-bit string - /// Des encryption method, default: improved (easy to transmit) + /// Des encryption method, default: improved (easy to transmit) /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 @@ -155,248 +166,251 @@ public static string Decrypt( string content, string key, string iv, - DESEncryType desEncryType = DESEncryType.Improved, + DESEncryptType desEncryptType = DESEncryptType.Improved, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) { - using var memoryStream = new MemoryStream(); -#pragma warning disable S2278 - using var des = DES.Create(); var currentEncoding = GetSafeEncoding(encoding); - des.Key = currentEncoding.GetBytes( - GetSpecifiedLengthString(key, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - des.IV = currentEncoding.GetBytes( - GetSpecifiedLengthString(iv, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - - using (MemoryStream ms = new MemoryStream()) + using MemoryStream ms = new MemoryStream(); + byte[] buffers = desEncryptType == DESEncryptType.Improved ? new byte[content.Length / 2] : content.FromBase64String(); + if (desEncryptType == DESEncryptType.Improved) { - byte[] buffers = desEncryType == DESEncryType.Improved ? new byte[content.Length / 2] : Convert.FromBase64String(content); - if (desEncryType == DESEncryType.Improved) + for (int x = 0; x < content.Length / 2; x++) { - for (int x = 0; x < content.Length / 2; x++) - { - int i = Convert.ToInt32(content.Substring(x * 2, 2), 16); - buffers[x] = (byte)i; - } - } - - using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write)) - { - cs.Write(buffers, 0, buffers.Length); - cs.FlushFinalBlock(); + int i = Convert.ToInt32(content.Substring(x * 2, 2), 16); + buffers[x] = (byte)i; } + } - return currentEncoding.GetString(ms.ToArray()); + using var des = DES.Create(); + var keyBuffer = GetKeyBuffer(key, currentEncoding, fillType, fillCharacter); + var ivBuffer = GetIvBuffer(iv, currentEncoding, fillType, fillCharacter); + using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(keyBuffer, ivBuffer), CryptoStreamMode.Write)) + { + cs.Write(buffers, 0, buffers.Length); + cs.FlushFinalBlock(); } -#pragma warning restore S2278 + + return currentEncoding.GetString(ms.ToArray()); } /// /// DES encrypts the file stream and outputs the encrypted file /// /// file input stream - /// file output path /// 8-bit length key or complement by fillType to calculate an 8-bit string + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void EncryptFile( FileStream fileStream, - string outFilePath, string key, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, key, true, fillType, fillCharacter, encoding); + => EncryptOrDecryptFile(fileStream, key, outFilePath, true, fillType, fillCharacter, encoding); /// /// DES encrypts the file stream and outputs the encrypted file /// /// file input stream - /// file output path /// 8-bit length key or complement by fillType to calculate an 8-bit string /// 8-bit length key or complement by fillType to calculate an 8-bit string + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void EncryptFile( FileStream fileStream, - string outFilePath, string key, string iv, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, (key, iv), true, fillType, fillCharacter, encoding); + => EncryptOrDecryptFile(fileStream, key, iv, true, outFilePath, fillType, fillCharacter, encoding); /// /// DES encrypts the file stream and outputs the encrypted file /// /// file input stream - /// file output path /// 8-bit length key - /// 8-bit length key + /// 8-bit length key + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void EncryptFile( FileStream fileStream, - string outFilePath, string key, - byte[] iv, + byte[] ivBuffer, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, (key, iv), true, fillType, fillCharacter, encoding); + { + var currentEncoding = GetSafeEncoding(encoding); + EncryptOrDecryptFile( + fileStream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter), + ivBuffer, + true, + outFilePath); + } /// /// DES decrypts the file stream and outputs the source file /// /// input file stream to be decrypted - /// file output path /// decryption key or complement by fillType to calculate an 8-bit string + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void DecryptFile( FileStream fileStream, - string outFilePath, string key, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, key, false, fillType, fillCharacter, encoding); + => EncryptOrDecryptFile(fileStream, key, outFilePath, false, fillType, fillCharacter, encoding); /// /// DES decrypts the file stream and outputs the source file /// /// input file stream to be decrypted - /// file output path /// decryption key or complement by fillType to calculate an 8-bit string /// 8-bit length key or complement by fillType to calculate an 8-bit string + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void DecryptFile( FileStream fileStream, - string outFilePath, string key, string iv, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, (key, iv), false, fillType, fillCharacter, encoding); + => EncryptOrDecryptFile(fileStream, key, iv, false, outFilePath, fillType, fillCharacter, encoding); /// /// DES decrypts the file stream and outputs the source file /// /// input file stream to be decrypted - /// file output path /// decryption key or complement by fillType to calculate an 8-bit string - /// + /// + /// file output path /// Do you supplement key and iv? default: no fill(Only supports 8-bit keys) /// character for complement /// Encoding format, default UTF-8 public static void DecryptFile( FileStream fileStream, - string outFilePath, string key, - byte[] iv, + byte[] ivBuffer, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - => EncryptOrDecryptFile(fileStream, outFilePath, (key, iv), false, fillType, fillCharacter, encoding); + { + var currentEncoding = GetSafeEncoding(encoding); + EncryptOrDecryptFile( + fileStream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter), + ivBuffer, + false, + outFilePath); + } - public static void EncryptOrDecryptFile( + private static void EncryptOrDecryptFile( FileStream fileStream, - string outFilePath, string key, + string outFilePath, bool isEncrypt, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) - { - byte[] iv = - { - 0x12, - 0x34, - 0x56, - 0x78, - 0x90, - 0xAB, - 0xCD, - 0xEF - }; - EncryptOrDecryptFile(fileStream, outFilePath, (key, iv), isEncrypt, fillType, fillCharacter, encoding); - } + => EncryptOrDecryptFile(fileStream, key, DefaultEncryptKey, isEncrypt, outFilePath, fillType, fillCharacter, encoding); private static void EncryptOrDecryptFile( FileStream fileStream, - string outFilePath, - (string Key, string IV) keyIv, + string key, + string iv, bool isEncrypt, + string outFilePath, FillType fillType = FillType.NoFile, char fillCharacter = ' ', Encoding? encoding = null) { var currentEncoding = GetSafeEncoding(encoding); - var ivBuffer = currentEncoding.GetBytes( - GetSpecifiedLengthString(keyIv.IV, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES iv or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - EncryptOrDecryptFile(fileStream, outFilePath, (keyIv.Key, ivBuffer), isEncrypt, fillType, fillCharacter, encoding); + EncryptOrDecryptFile( + fileStream, + GetKeyBuffer(key, currentEncoding, fillType, fillCharacter), + GetIvBuffer(iv, currentEncoding, fillType, fillCharacter), + isEncrypt, + outFilePath); } private static void EncryptOrDecryptFile( FileStream fileStream, - string outFilePath, - (string Key, byte[] IV) keyIv, + byte[] keyBuffer, + byte[] ivBuffer, bool isEncrypt, - FillType fillType = FillType.NoFile, - char fillCharacter = ' ', - Encoding? encoding = null) + string outFilePath) { - if (keyIv.IV.Length != 8) - { - throw new ArgumentException($"The iv length is invalid. The iv length needs 8 bits!"); - } + using var fileStreamOut = new FileStream(outFilePath, FileMode.Create); + using var des = DES.Create(); + var transform = GetTransform(des, keyBuffer, ivBuffer, isEncrypt); + EncryptOrDecryptFile(fileStream, fileStreamOut, transform); + } - var currentEncoding = GetSafeEncoding(encoding); - using var fileStreamOut = new FileStream(outFilePath, FileMode.OpenOrCreate, FileAccess.Write); - fileStreamOut.SetLength(0); - byte[] buffers = new byte[100]; - long readLength = 0; + private static byte[] GetKeyBuffer(string key, + Encoding encoding, + FillType fillType, + char fillCharacter) + => GetBytes( + key, + encoding, + fillType, + fillCharacter, + nameof(key), + $"Please enter a 8-bit DES key or allow {nameof(fillType)} to Left or Right"); -#pragma warning disable S2278 - using var des = DES.Create(); - des.Key = currentEncoding.GetBytes( - GetSpecifiedLengthString(keyIv.Key, - 8, - () => throw new ArgumentException($"Please enter a 8-bit DES key or allow {nameof(fillType)} to Left or Right"), - fillType, - fillCharacter)); - des.IV = keyIv.IV; + private static byte[] GetIvBuffer(string iv, + Encoding encoding, + FillType fillType, + char fillCharacter) + => GetBytes( + iv, + encoding, + fillType, + fillCharacter, + nameof(iv), + $"Please enter a 8-bit DES iv or allow {nameof(fillType)} to Left or Right"); - using var cryptoStream = new CryptoStream(fileStreamOut, - isEncrypt ? des.CreateEncryptor() : des.CreateDecryptor(), - CryptoStreamMode.Write); - while (readLength < fileStream.Length) - { - var length = fileStream.Read(buffers, 0, 100); - cryptoStream.Write(buffers, 0, length); - readLength += length; - } -#pragma warning restore S2278 + private static byte[] GetBytes(string str, + Encoding encoding, + FillType fillType, + char fillCharacter, + string paramName, + string message) + { + return GetBytes( + str, + encoding, + fillType, + fillCharacter, + 8, + () => throw new ArgumentException(message, paramName)); } + +#pragma warning restore S107 +#pragma warning restore S2278 } +#pragma warning restore S2342 +#pragma warning restore S101 diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/EncryptBase.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/EncryptBase.cs index e64c337d6..2021061da 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/EncryptBase.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/EncryptBase.cs @@ -5,36 +5,51 @@ namespace Masa.Utils.Security.Cryptography; public class EncryptBase { - protected static string GetSpecifiedLengthString( - string key, + protected EncryptBase() { } + + protected static byte[] GetBytes(string str, + Encoding encoding, + FillType fillType, + char fillCharacter, int length, - Func func, - FillType fillType = FillType.NoFile, - char fillCharacter = ' ') + Action action) { - if (fillType == FillType.NoFile && key.Length < length) - { - throw func.Invoke(); - } - - if (key.Length >= length) - { - return key.Substring(0, length); - } - - if (fillType == FillType.Left) - { - return key.PadLeft(length, fillCharacter); - } + var result = str.GetSpecifiedLengthString( + length, + action, + fillType, + fillCharacter); + return result.ConvertToBytes(encoding); + } - if (fillType == FillType.Right) + protected static void EncryptOrDecryptFile( + Stream stream, + Stream outPutStream, + ICryptoTransform transform) + { + int bufferLength = 1024; + byte[] buffers = new byte[bufferLength]; + long readLength = 0; + using var cryptoStream = new CryptoStream(outPutStream, + transform, + CryptoStreamMode.Write); + while (readLength < stream.Length) { - return key.PadRight(length, fillCharacter); + var length = stream.Read(buffers, 0, bufferLength); + cryptoStream.Write(buffers, 0, length); + readLength += length; } - - throw new NotSupportedException($"... Unsupported {nameof(fillType)}"); } + protected static ICryptoTransform GetTransform( + SymmetricAlgorithm symmetricAlgorithm, + byte[] keyBuffer, + byte[] ivBuffer, + bool isEncrypt) + => isEncrypt ? + symmetricAlgorithm.CreateEncryptor(keyBuffer, ivBuffer) : + symmetricAlgorithm.CreateDecryptor(keyBuffer, ivBuffer); + protected static Encoding GetSafeEncoding(Encoding? encoding = null) => GetSafeEncoding(() => Encoding.UTF8, encoding); diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/GlobalConfigurationUtils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/GlobalConfigurationUtils.cs index ffc41192b..e31894953 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/GlobalConfigurationUtils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/GlobalConfigurationUtils.cs @@ -3,19 +3,83 @@ namespace Masa.Utils.Security.Cryptography; -public class GlobalConfigurationUtils +public static class GlobalConfigurationUtils { - private static string _defaultEncryKey = "masastack.com"; + #region Aes - public static string DefaultEncryKey + private static string _defaultAesEncryptKey = "masastack.com "; + + public static string DefaultAesEncryptKey + { + get => _defaultAesEncryptKey; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException($"{nameof(DefaultAesEncryptKey)} cannot be empty", nameof(DefaultAesEncryptKey)); + + _defaultAesEncryptKey = value; + } + } + + private static string _defaultAesEncryptIv = "AreyoumySnowman?"; + + public static string DefaultAesEncryptIv { - get => _defaultEncryKey; + get => _defaultAesEncryptIv; set { if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException($"{nameof(DefaultEncryKey)} cannot be empty", nameof(DefaultEncryKey)); + throw new ArgumentException($"{nameof(DefaultAesEncryptIv)} cannot be empty", nameof(DefaultAesEncryptIv)); - _defaultEncryKey = value; + _defaultAesEncryptIv = value; } } + + private static int _defaultAesEncryptKeyLength = 32; + + public static int DefaultAesEncryptKeyLength + { + get => _defaultAesEncryptKeyLength; + set + { + if (value != 16 && value != 24 && value != 32) + throw new ArgumentException("Aes key length can only be 16, 24 or 32 bits", nameof(DefaultAesEncryptKeyLength)); + + _defaultAesEncryptKeyLength = value; + } + } + + #endregion + + #region Des + + private static string _defaultDesEncryptKey = "c7fac67c"; + + public static string DefaultDesEncryptKey + { + get => _defaultDesEncryptKey; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException($"{nameof(DefaultDesEncryptKey)} cannot be empty", nameof(DefaultDesEncryptKey)); + + _defaultDesEncryptKey = value; + } + } + + private static string _defaultDesEncryptIv = "c7fac67c"; + + public static string DefaultDesEncryptIv + { + get => _defaultDesEncryptIv; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException($"{nameof(DefaultDesEncryptIv)} cannot be empty", nameof(DefaultDesEncryptIv)); + + _defaultDesEncryptIv = value; + } + } + + #endregion } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/HashAlgorithmBase.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/HashAlgorithmBase.cs index 7ce6d52fe..bcacc719f 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/HashAlgorithmBase.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/HashAlgorithmBase.cs @@ -13,7 +13,7 @@ public class HashAlgorithmBase : EncryptBase /// Whether to convert the encrypted string to lowercase /// /// - public static string Encrypt(EncryptType encryptType, string content, bool isToLower = false, Encoding? encoding = null) + public static string Encrypt(EncryptType encryptType, string content, bool isToLower = true, Encoding? encoding = null) { using (var hashAlgorithm = HashAlgorithm.Create(encryptType.ToString())) { @@ -26,7 +26,7 @@ public static string Encrypt(EncryptType encryptType, string content, bool isToL } } - protected static string Encrypt(EncryptType encryptType, byte[] buffer, HashAlgorithm? hashAlgorithm = null, bool isToLower = false) + protected static string Encrypt(EncryptType encryptType, byte[] buffer, HashAlgorithm? hashAlgorithm = null, bool isToLower = true) { using (hashAlgorithm ??= HashAlgorithm.Create(encryptType.ToString())) { diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/MD5Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/MD5Utils.cs index 5a771a70d..f00b972a5 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/MD5Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/MD5Utils.cs @@ -6,6 +6,7 @@ namespace Masa.Utils.Security.Cryptography; /// /// MD5加密算法 /// +// ReSharper disable once InconsistentNaming public class MD5Utils : HashAlgorithmBase { /// @@ -15,41 +16,41 @@ public class MD5Utils : HashAlgorithmBase /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// - private static string Encrypt( + public static string Encrypt( string content, bool isToLower = true, Encoding? encoding = null) => Encrypt(content, string.Empty, isToLower, encoding); /// - /// MD5 multiple encryption + /// MD5 salt-encrypted string /// /// String to be encrypted - /// Encryption times,default: 1 + /// /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string EncryptRepeat( + public static string Encrypt( string content, - int encryptTimes = 1, + string salt, bool isToLower = true, Encoding? encoding = null) - => EncryptRepeat(content, string.Empty, encryptTimes, false, isToLower, encoding); + => Encrypt(EncryptType.Md5, content + salt, isToLower, encoding); /// - /// MD5 salt-encrypted string + /// MD5 multiple encryption /// /// String to be encrypted - /// + /// Encryption times,default: 1 /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string Encrypt( + public static string EncryptRepeat( string content, - string salt, + int encryptTimes = 1, bool isToLower = true, Encoding? encoding = null) - => Encrypt(EncryptType.Md5, content + salt, isToLower, encoding); + => EncryptRepeat(content, string.Empty, encryptTimes, isToLower, encoding); /// /// MD5 multiple encryption @@ -57,7 +58,6 @@ public static string Encrypt( /// String to be encrypted /// /// - /// When the number of executions is greater than 1, is it necessary to add salt and then encrypt after the second time? default: false (Salt encryption only for the first time) /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result @@ -65,7 +65,6 @@ public static string EncryptRepeat( string content, string salt, int encryptTimes, - bool isNeedSalt = false, bool isToLower = true, Encoding? encoding = null) { @@ -76,8 +75,7 @@ public static string EncryptRepeat( string result = Encrypt(content + salt, isToLower, encoding); while (times < encryptTimes) { - result = isNeedSalt ? result + salt : result; - result = Encrypt(result, isToLower, encoding); + result = Encrypt(result + salt, isToLower, encoding); times++; } @@ -104,14 +102,10 @@ public static string EncryptFile(string fileName, bool isToLower = true) /// encrypted result public static string EncryptStream(Stream stream, bool isToLower = true) { - stream.Position = 0; - byte[] buffers = new byte[stream.Length]; - stream.Read(buffers, 0, buffers.Length); - stream.Seek(0, SeekOrigin.Begin); + byte[] buffers = stream.ConvertToBytes(); using var md5 = MD5.Create(); byte[] bytes = md5.ComputeHash(buffers); var encryptedContent = Encrypt(EncryptType.Md5, bytes, null, isToLower); - stream.Position = 0; return encryptedContent; } } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/Masa.Utils.Security.Cryptography.csproj b/src/Utils/Security/Masa.Utils.Security.Cryptography/Masa.Utils.Security.Cryptography.csproj index 132c02c59..44b0c6398 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/Masa.Utils.Security.Cryptography.csproj +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/Masa.Utils.Security.Cryptography.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA1Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA1Utils.cs index b71f70354..d5a4122eb 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA1Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA1Utils.cs @@ -6,6 +6,7 @@ namespace Masa.Utils.Security.Cryptography; /// /// Hash algorithm encryption SHA1 /// +// ReSharper disable once InconsistentNaming public class SHA1Utils : HashAlgorithmBase { /// @@ -15,6 +16,6 @@ public class SHA1Utils : HashAlgorithmBase /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string Encrypt(string content, bool isToLower = false, Encoding? encoding = null) + public static string Encrypt(string content, bool isToLower = true, Encoding? encoding = null) => Encrypt(EncryptType.Sha1, content, isToLower, encoding); } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA256Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA256Utils.cs index d3567d057..2bbe8cd32 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA256Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA256Utils.cs @@ -6,6 +6,7 @@ namespace Masa.Utils.Security.Cryptography; /// /// Hash algorithm encryption SHA256 /// +// ReSharper disable once InconsistentNaming public class SHA256Utils : HashAlgorithmBase { /// @@ -15,6 +16,6 @@ public class SHA256Utils : HashAlgorithmBase /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string Encrypt(string content, bool isToLower = false, Encoding? encoding = null) + public static string Encrypt(string content, bool isToLower = true, Encoding? encoding = null) => Encrypt(EncryptType.Sha256, content, isToLower, encoding); } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA384Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA384Utils.cs index fb4bdfe4a..e008c1459 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA384Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA384Utils.cs @@ -6,6 +6,7 @@ namespace Masa.Utils.Security.Cryptography; /// /// Hash algorithm encryption SHA384 /// +// ReSharper disable once InconsistentNaming public class SHA384Utils : HashAlgorithmBase { /// @@ -15,6 +16,6 @@ public class SHA384Utils : HashAlgorithmBase /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string Encrypt(string content, bool isToLower = false, Encoding? encoding = null) + public static string Encrypt(string content, bool isToLower = true, Encoding? encoding = null) => Encrypt(EncryptType.Sha384, content, isToLower, encoding); } diff --git a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA512Utils.cs b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA512Utils.cs index 383547368..9e5b1fb7f 100644 --- a/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA512Utils.cs +++ b/src/Utils/Security/Masa.Utils.Security.Cryptography/SHA512Utils.cs @@ -6,6 +6,7 @@ namespace Masa.Utils.Security.Cryptography; /// /// Hash algorithm encryption SHA512 /// +// ReSharper disable once InconsistentNaming public class SHA512Utils : HashAlgorithmBase { /// @@ -15,6 +16,6 @@ public class SHA512Utils : HashAlgorithmBase /// Whether to convert the encrypted string to lowercase /// Encoding format, default UTF-8 /// encrypted result - public static string Encrypt(string content, bool isToLower = false, Encoding? encoding = null) + public static string Encrypt(string content, bool isToLower = true, Encoding? encoding = null) => Encrypt(EncryptType.Sha512, content, isToLower, encoding); } diff --git a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/AesTest.cs b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/AesTest.cs index 9e4462630..d448c83ce 100644 --- a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/AesTest.cs +++ b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/AesTest.cs @@ -7,7 +7,7 @@ namespace Masa.Utils.Security.Cryptography.Tests; public class AesTest { [TestMethod] - public void EncryptAndDecrypt() + public void TestEncryptAndDecrypt() { string str = "Hello MASA Stack"; string key = "12345678901234567890123456789021"; @@ -25,4 +25,70 @@ public void EncryptAndDecrypt() string encryptResult = AesUtils.Encrypt(str, key, "123", FillType.Right); Assert.ThrowsException(() => AesUtils.Decrypt(encryptResult, key, "123", FillType.NoFile)); } + + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + [DataRow(18)] + [DataTestMethod] + public void TestEncryptAndDecrypt2(int length) + { + GlobalConfigurationUtils.DefaultAesEncryptKey = "Hello MASA Stack"; + if (length != 16 && length != 24 && length != 32) + { + Assert.ThrowsException(() => GlobalConfigurationUtils.DefaultAesEncryptKeyLength = length); + } + else + { + GlobalConfigurationUtils.DefaultAesEncryptKeyLength = length; + } + string str = "Hello MASA Stack"; + string key = "12345678901234567890123456789021"; + var source = AesUtils.Decrypt(AesUtils.Encrypt(str, key), key); + Assert.IsTrue(str == source); + + var source2 = AesUtils.Decrypt(AesUtils.Encrypt(str)); + Assert.IsTrue(str == source2); + + var source3 = AesUtils.Decrypt(AesUtils.Encrypt(str, key, "123", FillType.Right), key, "123", FillType.Right); + Assert.IsTrue(str == source3); + + Assert.ThrowsException(() => AesUtils.Encrypt(str, key, "123", FillType.NoFile)); + + string encryptResult = AesUtils.Encrypt(str, key, "123", FillType.Right); + Assert.ThrowsException(() => AesUtils.Decrypt(encryptResult, key, "123", FillType.NoFile)); + } + + [TestMethod] + public void TestEncryptAndDecryptToBytes() + { + var str = "Hello MASA Stack"; + var encoding = Encoding.UTF8; + string key = "12345678901234567890123456789021"; + var path = Path.Combine("1.txt"); + File.WriteAllText(path, str); + var fileStream = File.OpenRead(path); + var encryptBuffer = AesUtils.EncryptToBytes(fileStream, key); + var decryptBuffer = AesUtils.DecryptToBytes(new MemoryStream(encryptBuffer), key); + + var actualResult = encoding.GetString(decryptBuffer); + Assert.AreEqual(str, actualResult); + } + + [TestMethod] + public void EncryptAndDecryptFile() + { + var str = "Hello MASA Stack"; + var sourcePath = $"{Guid.NewGuid()}.txt"; + File.WriteAllText(sourcePath, str); + var fileStream = File.OpenRead(sourcePath); + + var encryptPath = $"{Guid.NewGuid()}.txt"; + AesUtils.EncryptFile(fileStream, encryptPath); + + var decryptPath = $"{Guid.NewGuid()}.txt"; + AesUtils.DecryptFile(File.OpenRead(encryptPath), decryptPath); + var actualResult = File.ReadAllText(decryptPath); + Assert.AreEqual(str, actualResult); + } } diff --git a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/DesTest.cs b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/DesTest.cs index 0054d905d..ba36fbfeb 100644 --- a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/DesTest.cs +++ b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/DesTest.cs @@ -1,27 +1,28 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Utils.Security.Cryptography.Tests +namespace Masa.Utils.Security.Cryptography.Tests; + +[TestClass] +public class DesTest { - [TestClass] - public class DesTest +#pragma warning disable CS0618 + [TestMethod] + public void EncryptAndDecrypt() { - [TestMethod] - public void EncryptAndDecrypt() + for (int i = 0; i < 10000; i++) { - for (int i = 0; i < 10000; i++) - { - string str = new Random().Next(0, 1000000000).ToString(); - string key = "masastac"; - string iv = "masastack"; - var source = DesUtils.Decrypt(DesUtils.Encrypt(str, key), key); - var source2 = DesUtils.Decrypt(DesUtils.Encrypt(str, key, iv), key, iv); - var source3 = DesUtils.Decrypt(DesUtils.Encrypt(str, DESEncryType.Normal), DESEncryType.Normal); + string str = new Random().Next(0, 1000000000).ToString(); + string key = "masastac"; + string iv = "masastack"; + var source = DESUtils.Decrypt(DESUtils.Encrypt(str, key), key); + var source2 = DESUtils.Decrypt(DESUtils.Encrypt(str, key, iv), key, iv); + var source3 = DESUtils.Decrypt(DESUtils.Encrypt(str, DESEncryptType.Normal), DESEncryptType.Normal); - Assert.IsTrue(str == source); - Assert.IsTrue(str == source2); - Assert.IsTrue(str == source3); - } + Assert.IsTrue(str == source); + Assert.IsTrue(str == source2); + Assert.IsTrue(str == source3); } } +#pragma warning restore CS0618 } diff --git a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/MD5UtilsTest.cs b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/MD5UtilsTest.cs new file mode 100644 index 000000000..8f03156d2 --- /dev/null +++ b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/MD5UtilsTest.cs @@ -0,0 +1,40 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Security.Cryptography.Tests; + +[TestClass] +// ReSharper disable once InconsistentNaming +public class MD5UtilsTest +{ + [TestMethod] + public void TestEncrypt() + { + var str = "Hello MASA Stack"; + var encryptResult = MD5Utils.Encrypt(str); + Assert.AreEqual("e7b1bf81bacd21f9396bdbab6d881fe2", encryptResult); + } + + [TestMethod] + public void TestEncryptRepeat() + { + var str = "Hello MASA Stack"; + var encryptResult = MD5Utils.EncryptRepeat(str, 1); + Assert.AreEqual("e7b1bf81bacd21f9396bdbab6d881fe2", encryptResult); + + encryptResult = MD5Utils.EncryptRepeat(str, 2); + Assert.AreEqual("58349bb94668d886b6fcf3de0ccd5382", encryptResult); + } + + [TestMethod] + public void TestEncryptStream() + { + var str = "Hello MASA Stack"; + var path = Path.Combine($"{Guid.NewGuid()}.txt"); + File.WriteAllText(path, str); + var fileStream = File.OpenRead(path); + + var encryptResult = MD5Utils.EncryptStream(fileStream); + Assert.AreEqual("e7b1bf81bacd21f9396bdbab6d881fe2", encryptResult); + } +} diff --git a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/SHAUtilsTest.cs b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/SHAUtilsTest.cs new file mode 100644 index 000000000..0f31f3b7f --- /dev/null +++ b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/SHAUtilsTest.cs @@ -0,0 +1,17 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Security.Cryptography.Tests; + +[TestClass] +// ReSharper disable once InconsistentNaming +public class SHAUtilsTest +{ + [TestMethod] + public void TestEncrypt() + { + var str = "Hello MASA Stack"; + var encryptResult = SHA256Utils.Encrypt(str); + Assert.AreEqual("577da9f7698725d8ac8fc73e70b182b5ae47edaf5c2be73524861b3bf0f148dc", encryptResult); + } +} diff --git a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/_Imports.cs b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/_Imports.cs index abe6071b3..58d99a323 100644 --- a/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/_Imports.cs +++ b/src/Utils/Security/Tests/Masa.Utils.Security.Cryptography.Tests/_Imports.cs @@ -3,3 +3,5 @@ global using Microsoft.VisualStudio.TestTools.UnitTesting; global using System; +global using System.IO; +global using System.Text;