Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

306 lines
12 KiB

/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace OpenIddict.Extensions;
/// <summary>
/// Exposes common polyfills used by the OpenIddict assemblies.
/// </summary>
internal static class OpenIddictPolyfills
{
extension(ArgumentOutOfRangeException)
{
/// <summary>Throws an <see cref="ArgumentOutOfRangeException"/> if <paramref name="value"/> is negative.</summary>
/// <param name="value">The argument to validate as non-negative.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="value"/> corresponds.</param>
public static void ThrowIfNegative<T>(T value, [CallerArgumentExpression(nameof(value))] string? paramName = null)
where T : struct, IComparable<T>
{
switch (value)
{
case byte or ushort or uint or ulong or char:
return;
case sbyte n:
if (n < 0)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case short n:
if (n < 0)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case int n:
if (n < 0)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case long n:
if (n < 0L)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case float n:
if (n < 0F)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case double n:
if (n < 0D)
ThrowArgumentOutOfRangeException(paramName, value);
return;
case decimal n:
if (n < 0M)
ThrowArgumentOutOfRangeException(paramName, value);
return;
default:
throw new InvalidOperationException($"Invalid type '{typeof(T).AssemblyQualifiedName}' for {paramName}.");
}
static void ThrowArgumentOutOfRangeException(string? paramName, object value)
{
throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must not be negative.");
}
}
}
extension(Convert)
{
#if !SUPPORTS_HEXADECIMAL_STRING_CONVERSION
/// <summary>Converts the specified string, which encodes binary data as hex characters, to an equivalent 8-bit unsigned integer array.</summary>
/// <param name="s">The string to convert.</param>
/// <returns>An array of 8-bit unsigned integers that is equivalent to <paramref name="s"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="s"/> is <code>null</code>.</exception>
/// <exception cref="FormatException">The length of <paramref name="s"/>, is not zero or a multiple of 2.</exception>
/// <exception cref="FormatException">The format of <paramref name="s"/> is invalid. <paramref name="s"/> contains a non-hex character.</exception>
public static byte[] FromHexString(string s)
{
if ((uint) s.Length % 2 is not 0)
{
throw new FormatException(SR.GetResourceString(SR.ID0413));
}
var array = new byte[s.Length / 2];
for (var index = 0; index < s.Length; index += 2)
{
array[index / 2] = Convert.ToByte(s.Substring(index, 2), 16);
}
return array;
}
#endif
}
extension<TSource>(IEnumerable<TSource> source)
{
#if !SUPPORTS_CHUNK_LINQ_EXTENSION
/// <summary>
/// Split the elements of a sequence into chunks of size at most <paramref name="size"/>.
/// </summary>
/// <remarks>
/// Every chunk except the last will be of size <paramref name="size"/>.
/// The last chunk will contain the remaining elements and may be of a smaller size.
/// </remarks>
/// <param name="size">Maximum size of each chunk.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that contains the elements of the input
/// sequence split into chunks of size <paramref name="size"/>.
/// </returns>
public IEnumerable<TSource[]> Chunk(int size)
{
// Note: this polyfill was directly copied from .NET's source code:
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Linq/src/System/Linq/Chunk.cs.
using IEnumerator<TSource> enumerator = source.GetEnumerator();
if (enumerator.MoveNext())
{
var count = Math.Min(size, 4);
int index;
do
{
var array = new TSource[count];
array[0] = enumerator.Current;
index = 1;
if (size != array.Length)
{
for (; index < size && enumerator.MoveNext(); index++)
{
if (index >= array.Length)
{
count = (int) Math.Min((uint) size, 2 * (uint) array.Length);
Array.Resize(ref array, count);
}
array[index] = enumerator.Current;
}
}
else
{
var local = array;
Debug.Assert(local.Length == size);
for (; (uint) index < (uint) local.Length && enumerator.MoveNext(); index++)
{
local[index] = enumerator.Current;
}
}
if (index != array.Length)
{
Array.Resize(ref array, index);
}
yield return array;
}
while (index >= size && enumerator.MoveNext());
}
}
#endif
}
extension(OperatingSystem)
{
#if !SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
/// <summary>
/// Indicates whether the current application is running on Android.
/// </summary>
public static bool IsAndroid() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"));
/// <summary>
/// Check for the Android API level (returned by 'ro.build.version.sdk') with a >=
/// version comparison. Used to guard APIs that were added in the given Android release.
/// </summary>
public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
=> IsAndroid() && IsOSVersionAtLeast(major, minor, build, revision);
/// <summary>
/// Indicates whether the current application is running on iOS or MacCatalyst.
/// </summary>
[SupportedOSPlatformGuard("maccatalyst")]
public static bool IsIOS() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS"));
/// <summary>
/// Check for the iOS/MacCatalyst version (returned by 'libobjc.get_operatingSystemVersion')
/// with a >= version comparison. Used to guard APIs that were added in the given iOS release.
/// </summary>
[SupportedOSPlatformGuard("maccatalyst")]
public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0)
=> IsIOS() && IsOSVersionAtLeast(major, minor, build, 0);
/// <summary>
/// Indicates whether the current application is running on Linux.
/// </summary>
public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
/// <summary>
/// Indicates whether the current application is running on Mac Catalyst.
/// </summary>
public static bool IsMacCatalyst() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("MACCATALYST"));
/// <summary>
/// Check for the Mac Catalyst version (iOS version as presented in Apple documentation) with a >=
/// version comparison. Used to guard APIs that were added in the given Mac Catalyst release.
/// </summary>
public static bool IsMacCatalystVersionAtLeast(int major, int minor = 0, int build = 0)
=> IsMacCatalyst() && IsOSVersionAtLeast(major, minor, build, 0);
/// <summary>
/// Indicates whether the current application is running on macOS.
/// </summary>
public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
/// <summary>
/// Check for the macOS version (returned by 'libobjc.get_operatingSystemVersion') with a >=
/// version comparison. Used to guard APIs that were added in the given macOS release.
/// </summary>
public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0)
=> IsMacOS() && IsOSVersionAtLeast(major, minor, build, 0);
/// <summary>
/// Indicates whether the current application is running on Windows.
/// </summary>
public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
/// <summary>
/// Check for the Windows version (returned by 'RtlGetVersion') with a >= version
/// comparison. Used to guard APIs that were added in the given Windows release.
/// </summary>
public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
{
if (Environment.OSVersion.Platform is PlatformID.Win32NT &&
Environment.OSVersion.Version >= new Version(major, minor, build, revision))
{
return true;
}
// Note: on older versions of .NET, Environment.OSVersion.Version is known to be affected by
// the compatibility shims used by Windows 10+ when the application doesn't have a manifest
// that explicitly indicates it's compatible with Windows 10 and higher. To avoid that, a
// second pass using RuntimeInformation.OSDescription (that calls NtDll.RtlGetVersion() under
// the hood) is made. Note: no version is returned on UWP due to the missing Win32 API.
return RuntimeInformation.OSDescription.StartsWith("Microsoft Windows ", StringComparison.OrdinalIgnoreCase) &&
RuntimeInformation.OSDescription["Microsoft Windows ".Length..] is string value &&
Version.TryParse(value, out Version? version) && version >= new Version(major, minor, build, revision);
}
#endif
}
extension(ValueTask)
{
#if !SUPPORTS_VALUETASK_COMPLETED_TASK
/// <summary>
/// Gets a task that has already completed successfully.
/// </summary>
public static ValueTask CompletedTask => default;
#endif
}
extension<TResult>(ValueTask<TResult>)
{
#if !SUPPORTS_VALUETASK_COMPLETED_TASK
/// <summary>
/// Gets a task that has already completed successfully.
/// </summary>
public static ValueTask<TResult> CompletedTask => default;
#endif
}
#if !SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON
static bool IsOSVersionAtLeast(int major, int minor, int build, int revision)
{
Version current = Environment.OSVersion.Version;
if (current.Major != major)
{
return current.Major > major;
}
if (current.Minor != minor)
{
return current.Minor > minor;
}
int currentBuild = current.Build < 0 ? 0 : current.Build;
build = build < 0 ? 0 : build;
if (currentBuild != build)
{
return currentBuild > build;
}
int currentRevision = current.Revision < 0 ? 0 : current.Revision;
revision = revision < 0 ? 0 : revision;
return currentRevision >= revision;
}
#endif
}