Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
20
Vendor/EmbedIO-3.5.2/Utilities/ComponentCollectionExtensions.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/Utilities/ComponentCollectionExtensions.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IComponentCollection{T}"/>.
|
||||
/// </summary>
|
||||
public static class ComponentCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the specified component to a collection, without giving it a name.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of components in the collection.</typeparam>
|
||||
/// <param name="this">The <see cref="IComponentCollection{T}" /> on which this method is called.</param>
|
||||
/// <param name="component">The component to add.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this" /> is <see langword="null" />.</exception>
|
||||
/// <seealso cref="IComponentCollection{T}.Add" />
|
||||
public static void Add<T>(this IComponentCollection<T> @this, T component) => @this.Add(null, component);
|
||||
}
|
||||
}
|
||||
76
Vendor/EmbedIO-3.5.2/Utilities/ComponentCollection`1.cs
vendored
Normal file
76
Vendor/EmbedIO-3.5.2/Utilities/ComponentCollection`1.cs
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Swan.Configuration;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Implements a collection of components.</para>
|
||||
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of components in the collection.</typeparam>
|
||||
/// <seealso cref="IComponentCollection{T}" />
|
||||
public class ComponentCollection<T> : ConfiguredObject, IComponentCollection<T>
|
||||
{
|
||||
private readonly List<T> _components = new List<T>();
|
||||
|
||||
private readonly List<(string, T)> _componentsWithSafeNames = new List<(string, T)>();
|
||||
|
||||
private readonly Dictionary<string, T> _namedComponents = new Dictionary<string, T>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _components.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, T> Named => _namedComponents;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<(string SafeName, T Component)> WithSafeNames => _componentsWithSafeNames;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T this[int index] => _components[index];
|
||||
|
||||
/// <inheritdoc />
|
||||
public T this[string key] => _namedComponents[key];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<T> GetEnumerator() => _components.GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_components).GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="InvalidOperationException">The collection is locked.</exception>
|
||||
public void Add(string? name, T component)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
if (name.Length == 0)
|
||||
throw new ArgumentException("Component name is empty.", nameof(name));
|
||||
|
||||
if (_namedComponents.ContainsKey(name))
|
||||
throw new ArgumentException("Duplicate component name.", nameof(name));
|
||||
}
|
||||
|
||||
if (component == null)
|
||||
throw new ArgumentNullException(nameof(component));
|
||||
|
||||
if (_components.Contains(component))
|
||||
throw new ArgumentException("Component has already been added.", nameof(component));
|
||||
|
||||
_components.Add(component);
|
||||
_componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component));
|
||||
|
||||
if (name != null)
|
||||
_namedComponents.Add(name, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locks the collection, preventing further additions.
|
||||
/// </summary>
|
||||
public void Lock() => LockConfiguration();
|
||||
}
|
||||
}
|
||||
49
Vendor/EmbedIO-3.5.2/Utilities/DisposableComponentCollection`1.cs
vendored
Normal file
49
Vendor/EmbedIO-3.5.2/Utilities/DisposableComponentCollection`1.cs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Implements a collection of components that automatically disposes each component
|
||||
/// implementing <see cref="IDisposable"/>.</para>
|
||||
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of components in the collection.</typeparam>
|
||||
/// <seealso cref="ComponentCollection{T}" />
|
||||
/// <seealso cref="IComponentCollection{T}" />
|
||||
public class DisposableComponentCollection<T> : ComponentCollection<T>, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="DisposableComponentCollection{T}"/> class.
|
||||
/// </summary>
|
||||
~DisposableComponentCollection()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="true"/> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
foreach (var component in this)
|
||||
{
|
||||
if (component is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Vendor/EmbedIO-3.5.2/Utilities/HttpDate.cs
vendored
Normal file
78
Vendor/EmbedIO-3.5.2/Utilities/HttpDate.cs
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard methods to parse and format <see cref="DateTime"/>s according to various RFCs.
|
||||
/// </summary>
|
||||
public static class HttpDate
|
||||
{
|
||||
// https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Net/HttpDateParser.cs
|
||||
private static readonly string[] DateFormats = {
|
||||
// "r", // RFC 1123, required output format but too strict for input
|
||||
"ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
|
||||
"ddd, d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC
|
||||
"ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
|
||||
"d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
|
||||
"d MMM yyyy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week
|
||||
"d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
|
||||
"ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
|
||||
"ddd, d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, short year
|
||||
"ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
|
||||
"d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
|
||||
"d MMM yy H:m:s 'UTC'", // RFC 1123, UTC, no day-of-week, short year
|
||||
"d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
|
||||
|
||||
"dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
|
||||
"dddd, d'-'MMM'-'yy H:m:s 'UTC'", // RFC 850, UTC
|
||||
"dddd, d'-'MMM'-'yy H:m:s zzz", // RFC 850, offset
|
||||
"dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
|
||||
"ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
|
||||
|
||||
"ddd, d MMM yyyy H:m:s zzz", // RFC 5322
|
||||
"ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
|
||||
"d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
|
||||
"d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a string containing a date and time, and possibly a time zone offset,
|
||||
/// in one of the formats specified in <see href="https://tools.ietf.org/html/rfc850">RFC850</see>,
|
||||
/// <see href="https://tools.ietf.org/html/rfc1123">RFC1123</see>,
|
||||
/// and <see href="https://tools.ietf.org/html/rfc5322">RFC5322</see>,
|
||||
/// or ANSI C's <see href="https://linux.die.net/man/3/asctime"><c>asctime()</c></see> format.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to parse.</param>
|
||||
/// <param name="result">When this method returns <see langword="true"/>,
|
||||
/// a <see cref="DateTimeOffset"/> representing the parsed date, time, and time zone offset.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="str"/> was successfully parsed;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryParse(string str, out DateTimeOffset result) =>
|
||||
DateTimeOffset.TryParseExact(
|
||||
str,
|
||||
DateFormats,
|
||||
DateTimeFormatInfo.InvariantInfo,
|
||||
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal,
|
||||
out result);
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified <see cref="DateTimeOffset"/>
|
||||
/// according to <see href="https://tools.ietf.org/html/rfc1123">RFC1123</see>.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeOffset">The <see cref="DateTimeOffset"/> to format.</param>
|
||||
/// <returns>A string containing the formatted <paramref name="dateTimeOffset"/>.</returns>
|
||||
public static string Format(DateTimeOffset dateTimeOffset)
|
||||
=> dateTimeOffset.ToUniversalTime().ToString("r", DateTimeFormatInfo.InvariantInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified <see cref="DateTime"/>
|
||||
/// according to <see href="https://tools.ietf.org/html/rfc1123">RFC1123</see>.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The <see cref="DateTime"/> to format.</param>
|
||||
/// <returns>A string containing the formatted <paramref name="dateTime"/>.</returns>
|
||||
public static string Format(DateTime dateTime)
|
||||
=> dateTime.ToUniversalTime().ToString("r", DateTimeFormatInfo.InvariantInfo);
|
||||
}
|
||||
}
|
||||
54
Vendor/EmbedIO-3.5.2/Utilities/IComponentCollection`1.cs
vendored
Normal file
54
Vendor/EmbedIO-3.5.2/Utilities/IComponentCollection`1.cs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a collection of components.</para>
|
||||
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of components in the collection.</typeparam>
|
||||
public interface IComponentCollection<T> : IReadOnlyList<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IReadOnlyDictionary{TKey,TValue}"/> interface representing the named components.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The named components.
|
||||
/// </value>
|
||||
IReadOnlyDictionary<string, T> Named { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets an <see cref="IReadOnlyList{T}"/> interface representing all components
|
||||
/// associated with safe names.</para>
|
||||
/// <para>The safe name of a component is never <see langword="null"/>.
|
||||
/// If a component's unique name if <see langword="null"/>, its safe name
|
||||
/// will be some non-<see langword="null"/> string somehow identifying it.</para>
|
||||
/// <para>Note that safe names are not necessarily unique.</para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A list of <see cref="ValueTuple{T1,T2}"/>s, each containing a safe name and a component.
|
||||
/// </value>
|
||||
IReadOnlyList<(string SafeName, T Component)> WithSafeNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component with the specified name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The component.
|
||||
/// </value>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The component with the specified <paramref name="name"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception>
|
||||
/// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="name"/> is not found.</exception>
|
||||
T this[string name] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a component to the collection,
|
||||
/// giving it the specified <paramref name="name"/> if it is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name given to the module, or <see langword="null"/>.</param>
|
||||
/// <param name="component">The component.</param>
|
||||
void Add(string? name, T component);
|
||||
}
|
||||
}
|
||||
209
Vendor/EmbedIO-3.5.2/Utilities/IPParser.cs
vendored
Normal file
209
Vendor/EmbedIO-3.5.2/Utilities/IPParser.cs
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard methods to parse IP address strings.
|
||||
/// </summary>
|
||||
public static class IPParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the specified IP address.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address.</param>
|
||||
/// <returns>A collection of <see cref="IPAddress"/> parsed correctly from <paramref name="address"/>.</returns>
|
||||
public static async Task<IEnumerable<IPAddress>> ParseAsync(string address)
|
||||
{
|
||||
if (address == null)
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
if (IPAddress.TryParse(address, out var ip))
|
||||
return new List<IPAddress> { ip };
|
||||
|
||||
try
|
||||
{
|
||||
return await Dns.GetHostAddressesAsync(address).ConfigureAwait(false);
|
||||
}
|
||||
catch (SocketException socketEx)
|
||||
{
|
||||
socketEx.Log(nameof(IPParser));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (IsCidrNotation(address))
|
||||
return ParseCidrNotation(address);
|
||||
|
||||
return IsSimpleIPRange(address) ? TryParseSimpleIPRange(address) : Enumerable.Empty<IPAddress>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the IP-range string is in CIDR notation.
|
||||
/// </summary>
|
||||
/// <param name="range">The IP-range string.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP-range string is CIDR notation; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsCidrNotation(string range)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(range))
|
||||
return false;
|
||||
|
||||
var parts = range.Split('/');
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
var prefix = parts[0];
|
||||
var prefixLen = parts[1];
|
||||
|
||||
var prefixParts = prefix.Split('.');
|
||||
if (prefixParts.Length != 4)
|
||||
return false;
|
||||
|
||||
return byte.TryParse(prefixLen, out var len) && len <= 32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse IP-range string in CIDR notation. For example "12.15.0.0/16".
|
||||
/// </summary>
|
||||
/// <param name="range">The IP-range string.</param>
|
||||
/// <returns>A collection of <see cref="IPAddress"/> parsed correctly from <paramref name="range"/>.</returns>
|
||||
public static IEnumerable<IPAddress> ParseCidrNotation(string range)
|
||||
{
|
||||
if (!IsCidrNotation(range))
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
var parts = range.Split('/');
|
||||
var prefix = parts[0];
|
||||
|
||||
if (!byte.TryParse(parts[1], out var prefixLen))
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
var prefixParts = prefix.Split('.');
|
||||
if (prefixParts.Select(x => byte.TryParse(x, out _)).Any(x => !x))
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
uint ip = 0;
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
ip <<= 8;
|
||||
ip += uint.Parse(prefixParts[i], NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
var shiftBits = (byte)(32 - prefixLen);
|
||||
var ip1 = (ip >> shiftBits) << shiftBits;
|
||||
|
||||
if ((ip1 & ip) != ip1) // Check correct subnet address
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
var ip2 = ip1 >> shiftBits;
|
||||
for (var k = 0; k < shiftBits; k++)
|
||||
{
|
||||
ip2 = (ip2 << 1) + 1;
|
||||
}
|
||||
|
||||
var beginIP = new byte[4];
|
||||
var endIP = new byte[4];
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
beginIP[i] = (byte)((ip1 >> ((3 - i) * 8)) & 255);
|
||||
endIP[i] = (byte)((ip2 >> ((3 - i) * 8)) & 255);
|
||||
}
|
||||
|
||||
return GetAllIPAddresses(beginIP, endIP);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the IP-range string is in simple IP range notation.
|
||||
/// </summary>
|
||||
/// <param name="range">The IP-range string.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP-range string is in simple IP range notation; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsSimpleIPRange(string range)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(range))
|
||||
return false;
|
||||
|
||||
var parts = range.Split('.');
|
||||
if (parts.Length != 4)
|
||||
return false;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var rangeParts = part.Split('-');
|
||||
if (rangeParts.Length < 1 || rangeParts.Length > 2)
|
||||
return false;
|
||||
|
||||
if (!byte.TryParse(rangeParts[0], out _) ||
|
||||
(rangeParts.Length > 1 && !byte.TryParse(rangeParts[1], out _)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse IP-range string "12.15-16.1-30.10-255"
|
||||
/// </summary>
|
||||
/// <param name="range">The IP-range string.</param>
|
||||
/// <returns>A collection of <see cref="IPAddress"/> parsed correctly from <paramref name="range"/>.</returns>
|
||||
public static IEnumerable<IPAddress> TryParseSimpleIPRange(string range)
|
||||
{
|
||||
if (!IsSimpleIPRange(range))
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
|
||||
var beginIP = new byte[4];
|
||||
var endIP = new byte[4];
|
||||
|
||||
var parts = range.Split('.');
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var rangeParts = parts[i].Split('-');
|
||||
beginIP[i] = byte.Parse(rangeParts[0], NumberFormatInfo.InvariantInfo);
|
||||
endIP[i] = (rangeParts.Length == 1) ? beginIP[i] : byte.Parse(rangeParts[1], NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
return GetAllIPAddresses(beginIP, endIP);
|
||||
}
|
||||
|
||||
private static IEnumerable<IPAddress> GetAllIPAddresses(byte[] beginIP, byte[] endIP)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
if (endIP[i] < beginIP[i])
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
}
|
||||
|
||||
var capacity = 1;
|
||||
for (var i = 0; i < 4; i++)
|
||||
capacity *= endIP[i] - beginIP[i] + 1;
|
||||
|
||||
var ips = new List<IPAddress>(capacity);
|
||||
for (int i0 = beginIP[0]; i0 <= endIP[0]; i0++)
|
||||
{
|
||||
for (int i1 = beginIP[1]; i1 <= endIP[1]; i1++)
|
||||
{
|
||||
for (int i2 = beginIP[2]; i2 <= endIP[2]; i2++)
|
||||
{
|
||||
for (int i3 = beginIP[3]; i3 <= endIP[3]; i3++)
|
||||
{
|
||||
ips.Add(new IPAddress(new[] { (byte)i0, (byte)i1, (byte)i2, (byte)i3 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Vendor/EmbedIO-3.5.2/Utilities/MimeTypeProviderStack.cs
vendored
Normal file
56
Vendor/EmbedIO-3.5.2/Utilities/MimeTypeProviderStack.cs
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Manages a stack of MIME type providers.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="IMimeTypeProvider" />
|
||||
public sealed class MimeTypeProviderStack : IMimeTypeProvider
|
||||
{
|
||||
private readonly Stack<IMimeTypeProvider> _providers = new Stack<IMimeTypeProvider>();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Pushes the specified MIME type provider on the stack.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="IMimeTypeProvider"/> interface to push on the stack.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="provider"/>is <see langword="null"/>.</exception>
|
||||
public void Push(IMimeTypeProvider provider)
|
||||
=> _providers.Push(Validate.NotNull(nameof(provider), provider));
|
||||
|
||||
/// <summary>
|
||||
/// <para>Removes the most recently added MIME type provider from the stack.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
public void Pop() => _providers.Pop();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetMimeType(string extension)
|
||||
{
|
||||
var result = _providers.Select(p => p.GetMimeType(extension))
|
||||
.FirstOrDefault(m => m != null);
|
||||
|
||||
if (result == null)
|
||||
_ = MimeType.Associations.TryGetValue(extension, out result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
{
|
||||
foreach (var provider in _providers)
|
||||
{
|
||||
if (provider.TryDetermineCompression(mimeType, out preferCompression))
|
||||
return true;
|
||||
}
|
||||
|
||||
preferCompression = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Vendor/EmbedIO-3.5.2/Utilities/NameValueCollectionExtensions.cs
vendored
Normal file
110
Vendor/EmbedIO-3.5.2/Utilities/NameValueCollectionExtensions.cs
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="NameValueCollection"/>.
|
||||
/// </summary>
|
||||
public static class NameValueCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Converts a <see cref="NameValueCollection"/> to a dictionary of objects.</para>
|
||||
/// <para>Values in the returned dictionary will wither be strings, or arrays of strings,
|
||||
/// depending on the presence of multiple values for the same key in the collection.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> associating the collection's keys
|
||||
/// with their values.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static Dictionary<string, object?> ToDictionary(this NameValueCollection @this)
|
||||
=> @this.Keys.Cast<string>().ToDictionary(key => key, key => {
|
||||
var values = @this.GetValues(key);
|
||||
if (values == null)
|
||||
return null;
|
||||
|
||||
return values.Length switch {
|
||||
0 => null,
|
||||
1 => (object) values[0],
|
||||
_ => (object) values
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="NameValueCollection"/> to a dictionary of strings.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> associating the collection's keys
|
||||
/// with their values (or comma-separated lists in case of multiple values).</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static Dictionary<string, string> ToStringDictionary(this NameValueCollection @this)
|
||||
=> @this.Keys.Cast<string>().ToDictionary(key => key, @this.Get);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="NameValueCollection"/> to a dictionary of arrays of strings.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Dictionary{TKey,TValue}"/> associating the collection's keys
|
||||
/// with arrays of their values.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static Dictionary<string, string[]> ToArrayDictionary(this NameValueCollection @this)
|
||||
=> @this.Keys.Cast<string>().ToDictionary(key => key, @this.GetValues);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="NameValueCollection"/> contains one or more values
|
||||
/// for the specified <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <param name="key">The key to look for.</param>
|
||||
/// <returns><see langword="true"/> if at least one value for <paramref name="key"/>
|
||||
/// is present in the collection; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static bool ContainsKey(this NameValueCollection @this, string key)
|
||||
=> @this.Keys.Cast<string>().Contains(key);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="NameValueCollection"/> contains one or more values
|
||||
/// for the specified <paramref name="name"/>, at least one of which is equal to the specified
|
||||
/// <paramref name="value"/>. Value comparisons are carried out using the
|
||||
/// <see cref="StringComparison.OrdinalIgnoreCase"/> comparison type.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <param name="name">The name to look for.</param>
|
||||
/// <param name="value">The value to look for.</param>
|
||||
/// <returns><see langword="true"/> if at least one of the values for <paramref name="name"/>
|
||||
/// in the collection is equal to <paramref name="value"/>; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>White space is trimmed from the start and end of each value before comparison.</remarks>
|
||||
/// <seealso cref="Contains(NameValueCollection,string,string,StringComparison)"/>
|
||||
public static bool Contains(this NameValueCollection @this, string name, string value)
|
||||
=> Contains(@this, name, value, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <see cref="NameValueCollection"/> contains one or more values
|
||||
/// for the specified <paramref name="name"/>, at least one of which is equal to the specified
|
||||
/// <paramref name="value"/>. Value comparisons are carried out using the specified
|
||||
/// <paramref name="comparisonType"/>.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="NameValueCollection"/> on which this method is called.</param>
|
||||
/// <param name="name">The name to look for.</param>
|
||||
/// <param name="value">The value to look for.</param>
|
||||
/// <param name="comparisonType">One of the <see cref="StringComparison"/> enumeration values
|
||||
/// that specifies how the strings will be compared.</param>
|
||||
/// <returns><see langword="true"/> if at least one of the values for <paramref name="name"/>
|
||||
/// in the collection is equal to <paramref name="value"/>; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>White space is trimmed from the start and end of each value before comparison.</remarks>
|
||||
/// <seealso cref="Contains(NameValueCollection,string,string)"/>
|
||||
public static bool Contains(this NameValueCollection @this, string name, string? value, StringComparison comparisonType)
|
||||
{
|
||||
value = value?.Trim();
|
||||
return @this[name]?.SplitByComma()
|
||||
.Any(val => string.Equals(val?.Trim(), value, comparisonType)) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
279
Vendor/EmbedIO-3.5.2/Utilities/QValueList.cs
vendored
Normal file
279
Vendor/EmbedIO-3.5.2/Utilities/QValueList.cs
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a list of names with associated quality values extracted from an HTTP header,
|
||||
/// e.g. <c>gzip; q=0.9, deflate</c>.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7231#section-5.3">RFC7231, section 5.3</see>.</para>
|
||||
/// <para>This class ignores and discards extensions (<c>accept-ext</c> in RFC7231 terminology).</para>
|
||||
/// <para>If a name has one or more parameters (e.g. <c>text/html;level=1</c>) it is not
|
||||
/// further parsed: parameters will appear as part of the name.</para>
|
||||
/// </summary>
|
||||
public sealed class QValueList
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>A value signifying "anything will do" in request headers.</para>
|
||||
/// <para>For example, a request header of
|
||||
/// <c>Accept-Encoding: *;q=0.8, gzip</c> means "I prefer GZip compression;
|
||||
/// if it is not available, any other compression (including no compression at all)
|
||||
/// is OK for me".</para>
|
||||
/// </summary>
|
||||
public const string Wildcard = "*";
|
||||
|
||||
// This will match a quality value between two semicolons
|
||||
// or between a semicolon and the end of a string.
|
||||
// Match groups will be:
|
||||
// Groups[0] = The matching string
|
||||
// Groups[1] = If group is successful, "0"; otherwise, the weight is 1.000
|
||||
// Groups[2] = If group is successful, the decimal digits after 0
|
||||
// The part of string before the match contains the value and parameters (if any).
|
||||
// The part of string after the match contains the extensions (if any).
|
||||
// If there is no match, the whole string is just value and parameters (if any).
|
||||
private static readonly Regex QualityValueRegex = new Regex(
|
||||
@";[ \t]*q=(?:(?:1(?:\.(?:0{1,3}))?)|(?:(0)(?:\.(\d{1,3}))?))[ \t]*(?:;|,|$)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QValueList"/> class
|
||||
/// by parsing comma-separated request header values.
|
||||
/// </summary>
|
||||
/// <param name="useWildcard">If set to <see langword="true"/>, a value of <c>*</c>
|
||||
/// will be treated as signifying "anything".</param>
|
||||
/// <param name="headerValues">A list of comma-separated header values.</param>
|
||||
/// <seealso cref="UseWildcard"/>
|
||||
public QValueList(bool useWildcard, string headerValues)
|
||||
{
|
||||
UseWildcard = useWildcard;
|
||||
QValues = Parse(headerValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QValueList"/> class
|
||||
/// by parsing comma-separated request header values.
|
||||
/// </summary>
|
||||
/// <param name="useWildcard">If set to <see langword="true"/>, a value of <c>*</c>
|
||||
/// will be treated as signifying "anything".</param>
|
||||
/// <param name="headerValues">An enumeration of header values.
|
||||
/// Note that each element of the enumeration may in turn be
|
||||
/// a comma-separated list.</param>
|
||||
/// <seealso cref="UseWildcard"/>
|
||||
public QValueList(bool useWildcard, IEnumerable<string> headerValues)
|
||||
{
|
||||
UseWildcard = useWildcard;
|
||||
QValues = Parse(headerValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QValueList"/> class
|
||||
/// by parsing comma-separated request header values.
|
||||
/// </summary>
|
||||
/// <param name="useWildcard">If set to <see langword="true"/>, a value of <c>*</c>
|
||||
/// will be treated as signifying "anything".</param>
|
||||
/// <param name="headerValues">An array of header values.
|
||||
/// Note that each element of the array may in turn be
|
||||
/// a comma-separated list.</param>
|
||||
/// <seealso cref="UseWildcard"/>
|
||||
public QValueList(bool useWildcard, params string[] headerValues)
|
||||
: this(useWildcard, headerValues as IEnumerable<string>)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary associating values with their relative weight
|
||||
/// (an integer ranging from 0 to 1000) and their position in the
|
||||
/// list of header values from which this instance has been constructed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This property does not usually need to be used directly;
|
||||
/// use the <see cref="IsCandidate"/>, <see cref="FindPreferred"/>,
|
||||
/// <see cref="FindPreferredIndex(IEnumerable{string})"/>, and
|
||||
/// <see cref="FindPreferredIndex(string[])"/> methods instead.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsCandidate"/>
|
||||
/// <seealso cref="FindPreferred"/>
|
||||
/// <seealso cref="FindPreferredIndex(IEnumerable{string})"/>
|
||||
/// <seealso cref="FindPreferredIndex(string[])"/>
|
||||
public IReadOnlyDictionary<string, (int Weight, int Ordinal)> QValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether <c>*</c> is treated as a special value
|
||||
/// with the meaning of "anything".
|
||||
/// </summary>
|
||||
public bool UseWildcard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified value is a possible candidate.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns><see langword="true"/>if <paramref name="value"/> is a candidate;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsCandidate(string value)
|
||||
=> TryGetCandidateValue(Validate.NotNull(nameof(value), value), out var candidate) && candidate.Weight > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to determine whether the weight of a possible candidate.
|
||||
/// </summary>
|
||||
/// <param name="value">The value whose weight is to be determined.</param>
|
||||
/// <param name="weight">When this method returns <see langword="true"/>,
|
||||
/// the weight of the candidate.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="value"/> is a candidate;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public bool TryGetWeight(string value, out int weight)
|
||||
{
|
||||
var result = TryGetCandidateValue(Validate.NotNull(nameof(value), value), out var candidate);
|
||||
weight = candidate.Weight;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the value preferred by the client among an enumeration of values.
|
||||
/// </summary>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <returns>The value preferred by the client, or <see langword="null"/>
|
||||
/// if none of the provided <paramref name="values"/> is accepted.</returns>
|
||||
public string? FindPreferred(IEnumerable<string> values)
|
||||
=> FindPreferredCore(values, out var result) >= 0 ? result : null;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the index of the value preferred by the client in a list of values.
|
||||
/// </summary>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <returns>The index of the value preferred by the client, or -1
|
||||
/// if none of the values in <paramref name="values"/> is accepted.</returns>
|
||||
public int FindPreferredIndex(IEnumerable<string> values) => FindPreferredCore(values, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the index of the value preferred by the client in an array of values.
|
||||
/// </summary>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <returns>The index of the value preferred by the client, or -1
|
||||
/// if none of the values in <paramref name="values"/> is accepted.</returns>
|
||||
public int FindPreferredIndex(params string[] values) => FindPreferredIndex(values as IReadOnlyList<string>);
|
||||
|
||||
private static IReadOnlyDictionary<string, (int Weight, int Ordinal)> Parse(string headerValues)
|
||||
{
|
||||
var result = new Dictionary<string, (int Weight, int Ordinal)>();
|
||||
ParseCore(headerValues, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, (int Weight, int Ordinal)> Parse(IEnumerable<string> headerValues)
|
||||
{
|
||||
var result = new Dictionary<string, (int Weight, int Ordinal)>();
|
||||
|
||||
if (headerValues == null) return result;
|
||||
|
||||
foreach (var headerValue in headerValues)
|
||||
ParseCore(headerValue, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ParseCore(string text, IDictionary<string, (int Weight, int Ordinal)> dictionary)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
var length = text.Length;
|
||||
var position = 0;
|
||||
var ordinal = 0;
|
||||
while (position < length)
|
||||
{
|
||||
var stop = text.IndexOf(',', position);
|
||||
if (stop < 0)
|
||||
stop = length;
|
||||
|
||||
string name;
|
||||
var weight = 1000;
|
||||
var match = QualityValueRegex.Match(text, position, stop - position);
|
||||
if (match.Success)
|
||||
{
|
||||
var groups = match.Groups;
|
||||
var wholeMatch = groups[0];
|
||||
name = text.Substring(position, wholeMatch.Index - position).Trim();
|
||||
if (groups[1].Success)
|
||||
{
|
||||
weight = 0;
|
||||
if (groups[2].Success)
|
||||
{
|
||||
var digits = groups[2].Value;
|
||||
var n = 0;
|
||||
while (n < digits.Length)
|
||||
{
|
||||
weight = (10 * weight) + (digits[n] - '0');
|
||||
n++;
|
||||
}
|
||||
|
||||
while (n < 3)
|
||||
{
|
||||
weight = 10 * weight;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
name = text.Substring(position, stop - position).Trim();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
dictionary[name] = (weight, ordinal);
|
||||
|
||||
position = stop + 1;
|
||||
ordinal++;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CompareQualities((int Weight, int Ordinal) a, (int Weight, int Ordinal) b)
|
||||
{
|
||||
if (a.Weight > b.Weight)
|
||||
return 1;
|
||||
|
||||
if (a.Weight < b.Weight)
|
||||
return -1;
|
||||
|
||||
if (a.Ordinal < b.Ordinal)
|
||||
return 1;
|
||||
|
||||
if (a.Ordinal > b.Ordinal)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int FindPreferredCore(IEnumerable<string> values, out string? result)
|
||||
{
|
||||
values = Validate.NotNull(nameof(values), values);
|
||||
|
||||
result = null;
|
||||
var best = -1;
|
||||
|
||||
// Set initial values such as a weight of 0 can never win over them
|
||||
(int Weight, int Ordinal) bestValue = (0, int.MinValue);
|
||||
var i = 0;
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
if (TryGetCandidateValue(value, out var candidateValue) && CompareQualities(candidateValue, bestValue) > 0)
|
||||
{
|
||||
result = value;
|
||||
best = i;
|
||||
bestValue = candidateValue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
private bool TryGetCandidateValue(string value, out (int Weight, int Ordinal) candidate)
|
||||
=> QValues.TryGetValue(value, out candidate)
|
||||
|| (UseWildcard && QValues.TryGetValue(Wildcard, out candidate));
|
||||
}
|
||||
}
|
||||
72
Vendor/EmbedIO-3.5.2/Utilities/QValueListExtensions.cs
vendored
Normal file
72
Vendor/EmbedIO-3.5.2/Utilities/QValueListExtensions.cs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="QValueList"/>.
|
||||
/// </summary>
|
||||
public static class QValueListExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Attempts to proactively negotiate a compression method for a response,
|
||||
/// based on the contents of a <see cref="QValueList"/>.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="QValueList"/> on which this method is called.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <param name="compressionMethod">When this method returns, the compression method to use for the response,
|
||||
/// if content negotiation is successful. This parameter is passed uninitialized.</param>
|
||||
/// <param name="compressionMethodName">When this method returns, the name of the compression method,
|
||||
/// if content negotiation is successful. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if content negotiation is successful;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="this"/> is empty, this method always returns <see langword="true"/>,
|
||||
/// setting <paramref name="compressionMethod"/> to <see cref="CompressionMethod.None"/>
|
||||
/// and <paramref name="compressionMethodName"/> to <see cref="CompressionMethodNames.None"/>.</para>
|
||||
/// </remarks>
|
||||
public static bool TryNegotiateContentEncoding(
|
||||
this QValueList @this,
|
||||
bool preferCompression,
|
||||
out CompressionMethod compressionMethod,
|
||||
out string? compressionMethodName)
|
||||
{
|
||||
if (@this.QValues.Count < 1)
|
||||
{
|
||||
compressionMethod = CompressionMethod.None;
|
||||
compressionMethodName = CompressionMethodNames.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7231#section-5.3.4
|
||||
// RFC7231, Section 5.3.4, rule #2:
|
||||
// If the representation has no content-coding, then it is
|
||||
// acceptable by default unless specifically excluded by the
|
||||
// Accept - Encoding field stating either "identity;q=0" or "*;q=0"
|
||||
// without a more specific entry for "identity".
|
||||
if (!preferCompression && (!@this.TryGetWeight(CompressionMethodNames.None, out var weight) || weight > 0))
|
||||
{
|
||||
compressionMethod = CompressionMethod.None;
|
||||
compressionMethodName = CompressionMethodNames.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
var acceptableMethods = preferCompression
|
||||
? new[] { CompressionMethod.Gzip, CompressionMethod.Deflate, CompressionMethod.None }
|
||||
: new[] { CompressionMethod.None, CompressionMethod.Gzip, CompressionMethod.Deflate };
|
||||
var acceptableMethodNames = preferCompression
|
||||
? new[] { CompressionMethodNames.Gzip, CompressionMethodNames.Deflate, CompressionMethodNames.None }
|
||||
: new[] { CompressionMethodNames.None, CompressionMethodNames.Gzip, CompressionMethodNames.Deflate };
|
||||
|
||||
var acceptableMethodIndex = @this.FindPreferredIndex(acceptableMethodNames);
|
||||
if (acceptableMethodIndex < 0)
|
||||
{
|
||||
compressionMethod = default;
|
||||
compressionMethodName = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
compressionMethod = acceptableMethods[acceptableMethodIndex];
|
||||
compressionMethodName = acceptableMethodNames[acceptableMethodIndex];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/Utilities/StringExtensions.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/Utilities/StringExtensions.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="string"/>.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly char[] CommaSplitChars = {','};
|
||||
|
||||
/// <summary>Splits a string into substrings based on the specified <paramref name="delimiters"/>.
|
||||
/// The returned array includes empty array elements if two or more consecutive delimiters are found
|
||||
/// in <paramref name="this"/>.</summary>
|
||||
/// <param name="this">The <see cref="string"/> on which this method is called.</param>
|
||||
/// <param name="delimiters">An array of <see cref="char"/>s to use as delimiters.</param>
|
||||
/// <returns>An array whose elements contain the substrings in <paramref name="this"/> that are delimited
|
||||
/// by one or more characters in <paramref name="delimiters"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static string[] SplitByAny(this string @this, params char[] delimiters) => @this.Split(delimiters);
|
||||
|
||||
/// <summary>Splits a string into substrings, using the comma (<c>,</c>) character as a delimiter.
|
||||
/// The returned array includes empty array elements if two or more commas are found in <paramref name="this"/>.</summary>
|
||||
/// <param name="this">The <see cref="string"/> on which this method is called.</param>
|
||||
/// <returns>An array whose elements contain the substrings in <paramref name="this"/> that are delimited by commas.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="SplitByComma(string,StringSplitOptions)"/>
|
||||
public static string[] SplitByComma(this string @this) => @this.Split(CommaSplitChars);
|
||||
|
||||
/// <summary>Splits a string into substrings, using the comma (<c>,</c>) character as a delimiter.
|
||||
/// You can specify whether the substrings include empty array elements.</summary>
|
||||
/// <param name="this">The <see cref="string"/> on which this method is called.</param>
|
||||
/// <param name="options"><see cref="StringSplitOptions.RemoveEmptyEntries"/> to omit empty array elements from the array returned;
|
||||
/// or <see cref="StringSplitOptions.None"/> to include empty array elements in the array returned.</param>
|
||||
/// <returns>
|
||||
/// <para>An array whose elements contain the substrings in <paramref name="this"/> that are delimited by commas.</para>
|
||||
/// <para>For more information, see the Remarks section of the <see cref="string.Split(char[],StringSplitOptions)"/> method.</para>
|
||||
/// </returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="options">options</paramref> is not one of the <see cref="StringSplitOptions"/> values.</exception>
|
||||
/// <seealso cref="SplitByComma(string)"/>
|
||||
public static string[] SplitByComma(this string @this, StringSplitOptions options) =>
|
||||
@this.Split(CommaSplitChars, options);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a <see cref="string"/> is never empty,
|
||||
/// by transforming empty strings into <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="string"/> on which this method is called.</param>
|
||||
/// <returns>If <paramref name="this"/> is the empty string, <see langword="null"/>;
|
||||
/// otherwise, <paramref name="this."/></returns>
|
||||
public static string? NullIfEmpty(this string @this)
|
||||
=> string.IsNullOrEmpty(@this) ? null : @this;
|
||||
}
|
||||
}
|
||||
16
Vendor/EmbedIO-3.5.2/Utilities/UniqueIdGenerator.cs
vendored
Normal file
16
Vendor/EmbedIO-3.5.2/Utilities/UniqueIdGenerator.cs
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Generates locally unique string IDs, mainly for logging purposes.</para>
|
||||
/// </summary>
|
||||
public static class UniqueIdGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates and returns a unique ID.
|
||||
/// </summary>
|
||||
/// <returns>The generated ID.</returns>
|
||||
public static string GetNext() => Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 22);
|
||||
}
|
||||
}
|
||||
127
Vendor/EmbedIO-3.5.2/Utilities/UrlEncodedDataParser.cs
vendored
Normal file
127
Vendor/EmbedIO-3.5.2/Utilities/UrlEncodedDataParser.cs
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using EmbedIO.Internal;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses URL queries or URL-encoded HTML forms.
|
||||
/// </summary>
|
||||
public static class UrlEncodedDataParser
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Parses a URL query or URL-encoded HTML form.</para>
|
||||
/// <para>Unlike <see cref="HttpListenerRequest.QueryString" />, the returned
|
||||
/// <see cref="NameValueCollection" /> will have bracketed indexes stripped away;
|
||||
/// for example, <c>a[0]=1&a[1]=2</c> will yield the same result as <c>a=1&a=2</c>,
|
||||
/// i.e. a <see cref="NameValueCollection" /> with one key (<c>a</c>) associated with
|
||||
/// two values (<c>1</c> and <c>2</c>).</para>
|
||||
/// </summary>
|
||||
/// <param name="source">The string to parse.</param>
|
||||
/// <param name="groupFlags"><para>If this parameter is <see langword="true" />,
|
||||
/// tokens not followed by an equal sign (e.g. <c>this</c> in <c>a=1&this&b=2</c>)
|
||||
/// will be grouped as values of a <c>null</c> key.
|
||||
/// This is the same behavior as the <see cref="IHttpRequest.QueryString" /> and
|
||||
/// <see cref="HttpListenerRequest.QueryString" /> properties.</para>
|
||||
/// <para>If this parameter is <see langword="false" />, tokens not followed by an equal sign
|
||||
/// (e.g. <c>this</c> in <c>a=1&this&b=2</c>) will be considered keys with an empty
|
||||
/// value. This is the same behavior as the <see cref="HttpContextExtensions.GetRequestQueryData" />
|
||||
/// extension method.</para></param>
|
||||
/// <param name="mutableResult"><see langword="true" /> (the default) to return
|
||||
/// a mutable (non-read-only) collection; <see langword="false" /> to return a read-only collection.</param>
|
||||
/// <returns>A <see cref="NameValueCollection" /> containing the parsed data.</returns>
|
||||
public static NameValueCollection Parse(string source, bool groupFlags, bool mutableResult = true)
|
||||
{
|
||||
var result = new LockableNameValueCollection();
|
||||
|
||||
// Verify there is data to parse; otherwise, return an empty collection.
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
if (!mutableResult)
|
||||
result.MakeReadOnly();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddKeyValuePair(string? key, string value)
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
// Decode the key.
|
||||
key = WebUtility.UrlDecode(key);
|
||||
|
||||
// Discard bracketed index (used e.g. by PHP)
|
||||
var bracketPos = key.IndexOf("[", StringComparison.Ordinal);
|
||||
if (bracketPos > 0)
|
||||
key = key.Substring(0, bracketPos);
|
||||
}
|
||||
|
||||
// Decode the value.
|
||||
value = WebUtility.UrlDecode(value);
|
||||
|
||||
// Add the KVP to the collection.
|
||||
result.Add(key, value);
|
||||
}
|
||||
|
||||
// Skip the initial question mark,
|
||||
// in case source is the Query property of a Uri.
|
||||
var kvpPos = source[0] == '?' ? 1 : 0;
|
||||
var length = source.Length;
|
||||
while (kvpPos < length)
|
||||
{
|
||||
var separatorPos = kvpPos;
|
||||
var equalPos = -1;
|
||||
|
||||
while (separatorPos < length)
|
||||
{
|
||||
var c = source[separatorPos];
|
||||
if (c == '&')
|
||||
break;
|
||||
|
||||
if (c == '=' && equalPos < 0)
|
||||
equalPos = separatorPos;
|
||||
|
||||
separatorPos++;
|
||||
}
|
||||
|
||||
// Split by the equals char into key and value.
|
||||
// Some KVPS will have only their key, some will have both key and value
|
||||
// Some other might be repeated which really means an array
|
||||
if (equalPos < 0)
|
||||
{
|
||||
if (groupFlags)
|
||||
{
|
||||
AddKeyValuePair(null, source.Substring(kvpPos, separatorPos - kvpPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddKeyValuePair(source.Substring(kvpPos, separatorPos - kvpPos), string.Empty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddKeyValuePair(
|
||||
source.Substring(kvpPos, equalPos - kvpPos),
|
||||
source.Substring(equalPos + 1, separatorPos - equalPos - 1));
|
||||
}
|
||||
|
||||
// Edge case: if the last character in source is '&',
|
||||
// there's an empty KVP that we would otherwise skip.
|
||||
if (separatorPos == length - 1)
|
||||
{
|
||||
AddKeyValuePair(groupFlags ? null : string.Empty, string.Empty);
|
||||
break;
|
||||
}
|
||||
|
||||
// On to next KVP
|
||||
kvpPos = separatorPos + 1;
|
||||
}
|
||||
|
||||
if (!mutableResult)
|
||||
result.MakeReadOnly();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
315
Vendor/EmbedIO-3.5.2/Utilities/UrlPath.cs
vendored
Normal file
315
Vendor/EmbedIO-3.5.2/Utilities/UrlPath.cs
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utility methods to work with URL paths.
|
||||
/// </summary>
|
||||
public static class UrlPath
|
||||
{
|
||||
/// <summary>
|
||||
/// The root URL path value, i.e. <c>"/"</c>.
|
||||
/// </summary>
|
||||
public const string Root = "/";
|
||||
|
||||
private static readonly Regex MultipleSlashRegex = new Regex("//+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid URL path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the specified URL path is valid; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>For a string to be a valid URL path, it must not be <see langword="null"/>,
|
||||
/// must not be empty, and must start with a slash (<c>/</c>) character.</para>
|
||||
/// <para>To ensure that a method parameter is a valid URL path, use <see cref="Validate.UrlPath"/>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="UnsafeNormalize"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static bool IsValid(string urlPath) => ValidateInternal(nameof(urlPath), urlPath) == null;
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the specified URL path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="isBasePath">if set to <see langword="true"/>, treat the URL path
|
||||
/// as a base path, i.e. ensure it ends with a slash (<c>/</c>) character;
|
||||
/// otherwise, ensure that it does NOT end with a slash character.</param>
|
||||
/// <returns>The normalized path.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="urlPath"/> is not a valid URL path.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>A normalized URL path is one where each run of two or more slash
|
||||
/// (<c>/</c>) characters has been replaced with a single slash character.</para>
|
||||
/// <para>This method does NOT try to decode URL-encoded characters.</para>
|
||||
/// <para>If you are sure that <paramref name="urlPath"/> is a valid URL path,
|
||||
/// for example because you have called <see cref="IsValid"/> and it returned
|
||||
/// <see langword="true"/>, then you may call <see cref="UnsafeNormalize"/>
|
||||
/// instead of this method. <see cref="UnsafeNormalize"/> is slightly faster because
|
||||
/// it skips the initial validity check.</para>
|
||||
/// <para>There is no need to call this method for a method parameter
|
||||
/// for which you have already called <see cref="Validate.UrlPath"/>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnsafeNormalize"/>
|
||||
/// <seealso cref="IsValid"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static string Normalize(string urlPath, bool isBasePath)
|
||||
{
|
||||
var exception = ValidateInternal(nameof(urlPath), urlPath);
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return UnsafeNormalize(urlPath, isBasePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the specified URL path, assuming that it is valid.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="isBasePath">if set to <see langword="true"/>, treat the URL path
|
||||
/// as a base path, i.e. ensure it ends with a slash (<c>/</c>) character;
|
||||
/// otherwise, ensure that it does NOT end with a slash character.</param>
|
||||
/// <returns>The normalized path.</returns>
|
||||
/// <remarks>
|
||||
/// <para>A normalized URL path is one where each run of two or more slash
|
||||
/// (<c>/</c>) characters has been replaced with a single slash character.</para>
|
||||
/// <para>This method does NOT try to decode URL-encoded characters.</para>
|
||||
/// <para>If <paramref name="urlPath"/> is not valid, the behavior of
|
||||
/// this method is unspecified. You should call this method only after
|
||||
/// <see cref="IsValid"/> has returned <see langword="true"/>
|
||||
/// for the same <paramref name="urlPath"/>.</para>
|
||||
/// <para>You should call <see cref="Normalize"/> instead of this method
|
||||
/// if you are not sure that <paramref name="urlPath"/> is valid.</para>
|
||||
/// <para>There is no need to call this method for a method parameter
|
||||
/// for which you have already called <see cref="Validate.UrlPath"/>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="IsValid"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static string UnsafeNormalize(string urlPath, bool isBasePath)
|
||||
{
|
||||
// Replace each run of multiple slashes with a single slash
|
||||
urlPath = MultipleSlashRegex.Replace(urlPath, "/");
|
||||
|
||||
// The root path needs no further checking.
|
||||
var length = urlPath.Length;
|
||||
if (length == 1)
|
||||
return urlPath;
|
||||
|
||||
// Base URL paths must end with a slash;
|
||||
// non-base URL paths must NOT end with a slash.
|
||||
// The final slash is irrelevant for the URL itself
|
||||
// (it has to map the same way with or without it)
|
||||
// but makes comparing and mapping URLs a lot simpler.
|
||||
var finalPosition = length - 1;
|
||||
var endsWithSlash = urlPath[finalPosition] == '/';
|
||||
return isBasePath
|
||||
? (endsWithSlash ? urlPath : urlPath + "/")
|
||||
: (endsWithSlash ? urlPath.Substring(0, finalPosition) : urlPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL path is prefixed by the specified base URL path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="baseUrlPath">The base URL path.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="urlPath"/> is prefixed by <paramref name="baseUrlPath"/>;
|
||||
/// otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="urlPath"/> is not a valid URL path.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="baseUrlPath"/> is not a valid base URL path.</para>
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>This method returns <see langword="true"/> even if the two URL paths are equivalent,
|
||||
/// for example if both are <c>"/"</c>, or if <paramref name="urlPath"/> is <c>"/download"</c> and
|
||||
/// <paramref name="baseUrlPath"/> is <c>"/download/"</c>.</para>
|
||||
/// <para>If you are sure that both <paramref name="urlPath"/> and <paramref name="baseUrlPath"/>
|
||||
/// are valid and normalized, for example because you have called <see cref="Validate.UrlPath"/>,
|
||||
/// then you may call <see cref="UnsafeHasPrefix"/> instead of this method. <see cref="UnsafeHasPrefix"/>
|
||||
/// is slightly faster because it skips validity checks.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnsafeHasPrefix"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="StripPrefix"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static bool HasPrefix(string urlPath, string baseUrlPath)
|
||||
=> UnsafeHasPrefix(
|
||||
Validate.UrlPath(nameof(urlPath), urlPath, false),
|
||||
Validate.UrlPath(nameof(baseUrlPath), baseUrlPath, true));
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL path is prefixed by the specified base URL path,
|
||||
/// assuming both paths are valid and normalized.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="baseUrlPath">The base URL path.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="urlPath"/> is prefixed by <paramref name="baseUrlPath"/>;
|
||||
/// otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>Unless both <paramref name="urlPath"/> and <paramref name="baseUrlPath"/> are valid,
|
||||
/// normalized URL paths, the behavior of this method is unspecified. You should call this method
|
||||
/// only after calling either <see cref="Normalize"/> or <see cref="Validate.UrlPath"/>
|
||||
/// to check and normalize both parameters.</para>
|
||||
/// <para>If you are not sure about the validity and/or normalization of parameters,
|
||||
/// call <see cref="HasPrefix"/> instead of this method.</para>
|
||||
/// <para>This method returns <see langword="true"/> even if the two URL paths are equivalent,
|
||||
/// for example if both are <c>"/"</c>, or if <paramref name="urlPath"/> is <c>"/download"</c> and
|
||||
/// <paramref name="baseUrlPath"/> is <c>"/download/"</c>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="HasPrefix"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="StripPrefix"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static bool UnsafeHasPrefix(string urlPath, string baseUrlPath)
|
||||
=> urlPath.StartsWith(baseUrlPath, StringComparison.Ordinal)
|
||||
|| (urlPath.Length == baseUrlPath.Length - 1 && baseUrlPath.StartsWith(urlPath, StringComparison.Ordinal));
|
||||
|
||||
/// <summary>
|
||||
/// Strips a base URL path fom a URL path, obtaining a relative path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="baseUrlPath">The base URL path.</param>
|
||||
/// <returns>The relative path, or <see langword="null"/> if <paramref name="urlPath"/>
|
||||
/// is not prefixed by <paramref name="baseUrlPath"/>.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="urlPath"/> is not a valid URL path.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="baseUrlPath"/> is not a valid base URL path.</para>
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>The returned relative path is NOT prefixed by a slash (<c>/</c>) character.</para>
|
||||
/// <para>If <paramref name="urlPath"/> and <paramref name="baseUrlPath"/> are equivalent,
|
||||
/// for example if both are <c>"/"</c>, or if <paramref name="urlPath"/> is <c>"/download"</c>
|
||||
/// and <paramref name="baseUrlPath"/> is <c>"/download/"</c>, this method returns an empty string.</para>
|
||||
/// <para>If you are sure that both <paramref name="urlPath"/> and <paramref name="baseUrlPath"/>
|
||||
/// are valid and normalized, for example because you have called <see cref="Validate.UrlPath"/>,
|
||||
/// then you may call <see cref="UnsafeStripPrefix"/> instead of this method. <see cref="UnsafeStripPrefix"/>
|
||||
/// is slightly faster because it skips validity checks.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnsafeStripPrefix"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="HasPrefix"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static string? StripPrefix(string urlPath, string baseUrlPath)
|
||||
=> UnsafeStripPrefix(
|
||||
Validate.UrlPath(nameof(urlPath), urlPath, false),
|
||||
Validate.UrlPath(nameof(baseUrlPath), baseUrlPath, true));
|
||||
|
||||
/// <summary>
|
||||
/// Strips a base URL path fom a URL path, obtaining a relative path,
|
||||
/// assuming both paths are valid and normalized.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="baseUrlPath">The base URL path.</param>
|
||||
/// <returns>The relative path, or <see langword="null"/> if <paramref name="urlPath"/>
|
||||
/// is not prefixed by <paramref name="baseUrlPath"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Unless both <paramref name="urlPath"/> and <paramref name="baseUrlPath"/> are valid,
|
||||
/// normalized URL paths, the behavior of this method is unspecified. You should call this method
|
||||
/// only after calling either <see cref="Normalize"/> or <see cref="Validate.UrlPath"/>
|
||||
/// to check and normalize both parameters.</para>
|
||||
/// <para>If you are not sure about the validity and/or normalization of parameters,
|
||||
/// call <see cref="StripPrefix"/> instead of this method.</para>
|
||||
/// <para>The returned relative path is NOT prefixed by a slash (<c>/</c>) character.</para>
|
||||
/// <para>If <paramref name="urlPath"/> and <paramref name="baseUrlPath"/> are equivalent,
|
||||
/// for example if both are <c>"/"</c>, or if <paramref name="urlPath"/> is <c>"/download"</c>
|
||||
/// and <paramref name="baseUrlPath"/> is <c>"/download/"</c>, this method returns an empty string.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="StripPrefix"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="HasPrefix"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static string? UnsafeStripPrefix(string urlPath, string baseUrlPath)
|
||||
{
|
||||
if (!UnsafeHasPrefix(urlPath, baseUrlPath))
|
||||
return null;
|
||||
|
||||
// The only case where UnsafeHasPrefix returns true for a urlPath shorter than baseUrlPath
|
||||
// is urlPath == (baseUrlPath minus the final slash).
|
||||
return urlPath.Length < baseUrlPath.Length
|
||||
? string.Empty
|
||||
: urlPath.Substring(baseUrlPath.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified URL path into segments.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <returns>An enumeration of path segments.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="urlPath"/> is not a valid URL path.</exception>
|
||||
/// <remarks>
|
||||
/// <para>A root URL path (<c>/</c>) will result in an empty enumeration.</para>
|
||||
/// <para>The returned enumeration will be the same whether <paramref name="urlPath"/> is a base URL path or not.</para>
|
||||
/// <para>If you are sure that <paramref name="urlPath"/> is valid and normalized,
|
||||
/// for example because you have called <see cref="Validate.UrlPath"/>,
|
||||
/// then you may call <see cref="UnsafeSplit"/> instead of this method. <see cref="UnsafeSplit"/>
|
||||
/// is slightly faster because it skips validity checks.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="UnsafeSplit"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static IEnumerable<string> Split(string urlPath)
|
||||
=> UnsafeSplit(Validate.UrlPath(nameof(urlPath), urlPath, false));
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified URL path into segments, assuming it is valid and normalized.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <returns>An enumeration of path segments.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Unless <paramref name="urlPath"/> is a valid, normalized URL path,
|
||||
/// the behavior of this method is unspecified. You should call this method
|
||||
/// only after calling either <see cref="Normalize"/> or <see cref="Validate.UrlPath"/>
|
||||
/// to check and normalize both parameters.</para>
|
||||
/// <para>If you are not sure about the validity and/or normalization of <paramref name="urlPath"/>,
|
||||
/// call <see cref="StripPrefix"/> instead of this method.</para>
|
||||
/// <para>A root URL path (<c>/</c>) will result in an empty enumeration.</para>
|
||||
/// <para>The returned enumeration will be the same whether <paramref name="urlPath"/> is a base URL path or not.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Split"/>
|
||||
/// <seealso cref="Normalize"/>
|
||||
/// <seealso cref="Validate.UrlPath"/>
|
||||
public static IEnumerable<string> UnsafeSplit(string urlPath)
|
||||
{
|
||||
var length = urlPath.Length;
|
||||
var position = 1; // Skip initial slash
|
||||
while (position < length)
|
||||
{
|
||||
var slashPosition = urlPath.IndexOf('/', position);
|
||||
if (slashPosition < 0)
|
||||
{
|
||||
yield return urlPath.Substring(position);
|
||||
break;
|
||||
}
|
||||
|
||||
yield return urlPath.Substring(position, slashPosition - position);
|
||||
position = slashPosition + 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Exception? ValidateInternal(string argumentName, string value)
|
||||
{
|
||||
if (value == null)
|
||||
return new ArgumentNullException(argumentName);
|
||||
|
||||
if (value.Length == 0)
|
||||
return new ArgumentException("URL path is empty.", argumentName);
|
||||
|
||||
if (value[0] != '/')
|
||||
return new ArgumentException("URL path does not start with a slash.", argumentName);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Vendor/EmbedIO-3.5.2/Utilities/Validate-MimeType.cs
vendored
Normal file
32
Vendor/EmbedIO-3.5.2/Utilities/Validate-MimeType.cs
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
partial class Validate
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Ensures that a <see langword="string"/> argument is valid as MIME type or media range as defined by
|
||||
/// <see href="https://tools.ietf.org/html/rfc7231#section-5.3.2">RFC7231, Section 5,3.2</see>.</para>
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <param name="acceptMediaRange">If <see langword="true"/>, media ranges (i.e. strings of the form <c>*/*</c>
|
||||
/// and <c>type/*</c>) are considered valid; otherwise, they are rejected as invalid.</param>
|
||||
/// <returns><paramref name="value"/>, if it is a valid MIME type or media range.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> is not a valid MIME type or media range.</para>
|
||||
/// </exception>
|
||||
public static string MimeType(string argumentName, string value, bool acceptMediaRange)
|
||||
{
|
||||
value = NotNullOrEmpty(argumentName, value);
|
||||
|
||||
if (!EmbedIO.MimeType.IsMimeType(value, acceptMediaRange))
|
||||
throw new ArgumentException("MIME type is not valid.", argumentName);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Vendor/EmbedIO-3.5.2/Utilities/Validate-Paths.cs
vendored
Normal file
95
Vendor/EmbedIO-3.5.2/Utilities/Validate-Paths.cs
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
partial class Validate
|
||||
{
|
||||
private static readonly char[] InvalidLocalPathChars = GetInvalidLocalPathChars();
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the value of an argument is a valid URL path
|
||||
/// and normalizes it.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <param name="isBasePath">If set to <see langword="true"/><c>true</c>, the returned path
|
||||
/// is ensured to end in a slash (<c>/</c>) character; otherwise, the returned path is
|
||||
/// ensured to not end in a slash character unless it is <c>"/"</c>.</param>
|
||||
/// <returns>The normalized URL path.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is empty.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> does not start with a slash (<c>/</c>) character.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="Utilities.UrlPath.Normalize"/>
|
||||
public static string UrlPath(string argumentName, string value, bool isBasePath)
|
||||
{
|
||||
var exception = Utilities.UrlPath.ValidateInternal(argumentName, value);
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return Utilities.UrlPath.Normalize(value, isBasePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the value of an argument is a valid local path
|
||||
/// and, optionally, gets the corresponding full path.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <param name="getFullPath"><see langword="true"/> to get the full path, <see langword="false"/> to leave the path as is..</param>
|
||||
/// <returns>The local path, or the full path if <paramref name="getFullPath"/> is <see langword="true"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is empty.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> contains only white space.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> contains one or more invalid characters.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="getFullPath"/> is <see langword="true"/> and the full path could not be obtained.</para>
|
||||
/// </exception>
|
||||
public static string LocalPath(string argumentName, string value, bool getFullPath)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(argumentName);
|
||||
|
||||
if (value.Length == 0)
|
||||
throw new ArgumentException("Local path is empty.", argumentName);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
throw new ArgumentException("Local path contains only white space.", argumentName);
|
||||
|
||||
if (value.IndexOfAny(InvalidLocalPathChars) >= 0)
|
||||
throw new ArgumentException("Local path contains one or more invalid characters.", argumentName);
|
||||
|
||||
if (getFullPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = Path.GetFullPath(value);
|
||||
}
|
||||
catch (Exception e) when (e is ArgumentException || e is SecurityException || e is NotSupportedException || e is PathTooLongException)
|
||||
{
|
||||
throw new ArgumentException("Could not get the full local path.", argumentName, e);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static char[] GetInvalidLocalPathChars()
|
||||
{
|
||||
var systemChars = Path.GetInvalidPathChars();
|
||||
var p = systemChars.Length;
|
||||
var result = new char[p + 2];
|
||||
Array.Copy(systemChars, result, p);
|
||||
result[p++] = '*';
|
||||
result[p] = '?';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/Utilities/Validate-Rfc2616.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/Utilities/Validate-Rfc2616.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
partial class Validate
|
||||
{
|
||||
private static readonly char[] ValidRfc2616TokenChars = GetValidRfc2616TokenChars();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Ensures that a <see langword="string"/> argument is valid as a token as defined by
|
||||
/// <see href="https://tools.ietf.org/html/rfc2616#section-2.2">RFC2616, Section 2.2</see>.</para>
|
||||
/// <para>RFC2616 tokens are used, for example, as:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cookie names, as stated in <see href="https://tools.ietf.org/html/rfc6265#section-4.1.1">RFC6265, Section 4.1.1</see>;</description></item>
|
||||
/// <item><description>WebSocket protocol names, as stated in <see href="https://tools.ietf.org/html/rfc6455#section-4.3">RFC6455, Section 4.3</see>.</description></item>
|
||||
/// </list>
|
||||
/// <para>Only a restricted set of characters are allowed in tokens, including:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>upper- and lower-case letters of the English alphabet;</description></item>
|
||||
/// <item><description>decimal digits;</description></item>
|
||||
/// <item><description>the following non-alphanumeric characters:
|
||||
/// <c>!</c>, <c>#</c>, <c>$</c>, <c>%</c>, <c>&</c>, <c>'</c>, <c>*</c>, <c>+</c>,
|
||||
/// <c>-</c>, <c>.</c>, <c>^</c>, <c>_</c>, <c>`</c>, <c>|</c>, <c>~</c>.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <returns><paramref name="value"/>, if it is a valid token.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> contains one or more characters that are not allowed in a token.</para>
|
||||
/// </exception>
|
||||
public static string Rfc2616Token(string argumentName, string value)
|
||||
{
|
||||
value = NotNullOrEmpty(argumentName, value);
|
||||
|
||||
if (!IsRfc2616Token(value))
|
||||
throw new ArgumentException("Token contains one or more invalid characters.", argumentName);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
internal static bool IsRfc2616Token(string value)
|
||||
=> !string.IsNullOrEmpty(value)
|
||||
&& !value.Any(c => c < '\x21' || c > '\x7E' || Array.BinarySearch(ValidRfc2616TokenChars, c) < 0);
|
||||
|
||||
private static char[] GetValidRfc2616TokenChars()
|
||||
=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&'*+-.^_`|~"
|
||||
.OrderBy(c => c)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
33
Vendor/EmbedIO-3.5.2/Utilities/Validate-Route.cs
vendored
Normal file
33
Vendor/EmbedIO-3.5.2/Utilities/Validate-Route.cs
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
partial class Validate
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the value of an argument is a valid route.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <param name="isBaseRoute"><see langword="true"/> if the argument must be a base route;
|
||||
/// <see langword="false"/> if the argument must be a non-base route.</param>
|
||||
/// <returns><paramref name="value"/>, if it is a valid route.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is empty.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> does not start with a slash (<c>/</c>) character.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> does not comply with route syntax.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="Routing.Route.IsValid"/>
|
||||
public static string Route(string argumentName, string value, bool isBaseRoute)
|
||||
{
|
||||
var exception = Routing.Route.ValidateInternal(argumentName, value, isBaseRoute);
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return Utilities.UrlPath.UnsafeNormalize(value, isBaseRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Vendor/EmbedIO-3.5.2/Utilities/Validate.cs
vendored
Normal file
125
Vendor/EmbedIO-3.5.2/Utilities/Validate.cs
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides validation methods for method arguments.
|
||||
/// </summary>
|
||||
public static partial class Validate
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that an argument is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the argument to validate.</typeparam>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <returns><paramref name="value"/> if not <see langword="null"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
public static T NotNull<T>(string argumentName, T? value)
|
||||
where T : class
|
||||
=> value ?? throw new ArgumentNullException(argumentName);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a <see langword="string"/> argument is neither <see langword="null"/> nor the empty string.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">The name of the argument to validate.</param>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <returns><paramref name="value"/> if neither <see langword="null"/> nor the empty string.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="value"/> is the empty string.</exception>
|
||||
public static string NotNullOrEmpty(string argumentName, string? value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(argumentName);
|
||||
|
||||
if (value.Length == 0)
|
||||
throw new ArgumentException("String is empty.", argumentName);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a valid URL can be constructed from a <see langword="string"/> argument.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">Name of the argument.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="uriKind">Specifies whether <paramref name="value"/> is a relative URL, absolute URL, or is indeterminate.</param>
|
||||
/// <param name="enforceHttp">Ensure that, if <paramref name="value"/> is an absolute URL, its scheme is either <c>http</c> or <c>https</c>.</param>
|
||||
/// <returns>The string representation of the constructed URL.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="value"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="enforceHttp"/> is <see langword="true"/>, <paramref name="value"/> is an absolute URL,
|
||||
/// and <paramref name="value"/>'s scheme is neither <c>http</c> nor <c>https</c>.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="Url(string,string,Uri,bool)"/>
|
||||
public static string Url(
|
||||
string argumentName,
|
||||
string value,
|
||||
UriKind uriKind = UriKind.RelativeOrAbsolute,
|
||||
bool enforceHttp = false)
|
||||
{
|
||||
Uri uri;
|
||||
try
|
||||
{
|
||||
uri = new Uri(NotNull(argumentName, value), uriKind);
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
throw new ArgumentException("URL is not valid.", argumentName, e);
|
||||
}
|
||||
|
||||
if (enforceHttp && uri.IsAbsoluteUri && uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
|
||||
throw new ArgumentException("URL scheme is neither HTTP nor HTTPS.", argumentName);
|
||||
|
||||
return uri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a valid URL, either absolute or relative to the given <paramref name="baseUri"/>,
|
||||
/// can be constructed from a <see langword="string"/> argument and returns the absolute URL
|
||||
/// obtained by combining <paramref name="baseUri"/> and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="argumentName">Name of the argument.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="baseUri">The base URI for relative URLs.</param>
|
||||
/// <param name="enforceHttp">Ensure that the resulting URL's scheme is either <c>http</c> or <c>https</c>.</param>
|
||||
/// <returns>The string representation of the constructed URL.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="baseUri"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="baseUri"/> is not an absolute URI.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="value"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="enforceHttp"/> is <see langword="true"/>,
|
||||
/// and the combination of <paramref name="baseUri"/> and <paramref name="value"/> has a scheme
|
||||
/// that is neither <c>http</c> nor <c>https</c>.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="Url(string,string,UriKind,bool)"/>
|
||||
public static string Url(string argumentName, string value, Uri baseUri, bool enforceHttp = false)
|
||||
{
|
||||
if (!NotNull(nameof(baseUri), baseUri).IsAbsoluteUri)
|
||||
throw new ArgumentException("Base URI is not an absolute URI.", nameof(baseUri));
|
||||
|
||||
Uri uri;
|
||||
try
|
||||
{
|
||||
uri = new Uri(baseUri, new Uri(NotNull(argumentName, value), UriKind.RelativeOrAbsolute));
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
throw new ArgumentException("URL is not valid.", argumentName, e);
|
||||
}
|
||||
|
||||
if (enforceHttp && uri.IsAbsoluteUri && uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
|
||||
throw new ArgumentException("URL scheme is neither HTTP nor HTTPS.", argumentName);
|
||||
|
||||
return uri.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user