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:
54
Vendor/EmbedIO-3.5.2/Actions/ActionModule.cs
vendored
Normal file
54
Vendor/EmbedIO-3.5.2/Actions/ActionModule.cs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// A module that passes requests to a callback.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class ActionModule : WebModuleBase
|
||||
{
|
||||
private readonly HttpVerbs _verb;
|
||||
|
||||
private readonly RequestHandlerCallback _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ActionModule" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="verb">The HTTP verb that will be served by this module.</param>
|
||||
/// <param name="handler">The callback used to handle requests.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public ActionModule(string baseRoute, HttpVerbs verb, RequestHandlerCallback handler)
|
||||
: base(baseRoute)
|
||||
{
|
||||
_verb = verb;
|
||||
_handler = Validate.NotNull(nameof(handler), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ActionModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public ActionModule(RequestHandlerCallback handler)
|
||||
: this("/", HttpVerbs.Any, handler)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (_verb != HttpVerbs.Any && context.Request.HttpVerb != _verb)
|
||||
return;
|
||||
|
||||
await _handler(context).ConfigureAwait(false);
|
||||
context.SetHandled();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Vendor/EmbedIO-3.5.2/Actions/RedirectModule.cs
vendored
Normal file
99
Vendor/EmbedIO-3.5.2/Actions/RedirectModule.cs
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// A module that redirects requests.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class RedirectModule : WebModuleBase
|
||||
{
|
||||
private readonly Func<IHttpContext, bool>? _shouldRedirect;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedirectModule"/> class
|
||||
/// that will redirect all served requests.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="redirectUrl">The redirect URL.</param>
|
||||
/// <param name="statusCode">The response status code; default is <c>302 - Found</c>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="redirectUrl"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="redirectUrl"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public RedirectModule(string baseRoute, string redirectUrl, HttpStatusCode statusCode = HttpStatusCode.Found)
|
||||
: this(baseRoute, redirectUrl, null, statusCode, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedirectModule"/> class
|
||||
/// that will redirect all requests for which the <paramref name="shouldRedirect"/> callback
|
||||
/// returns <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="redirectUrl">The redirect URL.</param>
|
||||
/// <param name="shouldRedirect">A callback function that returns <see langword="true"/>
|
||||
/// if a request must be redirected.</param>
|
||||
/// <param name="statusCode">The response status code; default is <c>302 - Found</c>.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="redirectUrl"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="shouldRedirect"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="redirectUrl"/> is not a valid URL.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
/// <seealso cref="WebModuleBase(string)"/>
|
||||
public RedirectModule(string baseRoute, string redirectUrl, Func<IHttpContext, bool>? shouldRedirect, HttpStatusCode statusCode = HttpStatusCode.Found)
|
||||
: this(baseRoute, redirectUrl, shouldRedirect, statusCode, true)
|
||||
{
|
||||
}
|
||||
|
||||
private RedirectModule(string baseRoute, string redirectUrl, Func<IHttpContext, bool>? shouldRedirect, HttpStatusCode statusCode, bool useCallback)
|
||||
: base(baseRoute)
|
||||
{
|
||||
RedirectUrl = Validate.Url(nameof(redirectUrl), redirectUrl);
|
||||
|
||||
var status = (int)statusCode;
|
||||
if (status < 300 || status > 399)
|
||||
throw new ArgumentException("Status code does not imply a redirection.", nameof(statusCode));
|
||||
|
||||
StatusCode = statusCode;
|
||||
_shouldRedirect = useCallback ? Validate.NotNull(nameof(shouldRedirect), shouldRedirect) : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the redirect URL.
|
||||
/// </summary>
|
||||
public string RedirectUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response status code.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (_shouldRedirect?.Invoke(context) ?? true)
|
||||
{
|
||||
context.Redirect(RedirectUrl, (int)StatusCode);
|
||||
context.SetHandled();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Vendor/EmbedIO-3.5.2/Authentication/Auth.cs
vendored
Normal file
31
Vendor/EmbedIO-3.5.2/Authentication/Auth.cs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides useful authentication-related constants.
|
||||
/// </summary>
|
||||
public static class Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IPrincipal"/> interface representing
|
||||
/// no user. To be used instead of <see langword="null"/>
|
||||
/// to initialize or set properties of type <see cref="IPrincipal"/>.
|
||||
/// </summary>
|
||||
public static IPrincipal NoUser { get; } = new GenericPrincipal(
|
||||
new GenericIdentity(string.Empty, string.Empty),
|
||||
null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an <see cref="IPrincipal"/> interface
|
||||
/// representing an unauthenticated user, with the given
|
||||
/// authentication type.
|
||||
/// </summary>
|
||||
/// <param name="authenticationType">The type of authentication used to identify the user.</param>
|
||||
/// <returns>An <see cref="IPrincipal"/> interface.</returns>
|
||||
public static IPrincipal CreateUnauthenticatedPrincipal(string authenticationType)
|
||||
=> new GenericPrincipal(
|
||||
new GenericIdentity(string.Empty, authenticationType),
|
||||
null);
|
||||
}
|
||||
}
|
||||
46
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModule.cs
vendored
Normal file
46
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModule.cs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple HTTP basic authentication module that stores credentials
|
||||
/// in a <seealso cref="ConcurrentDictionary{TKey,TValue}"/>.
|
||||
/// </summary>
|
||||
public class BasicAuthenticationModule : BasicAuthenticationModuleBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthenticationModule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="realm">The authentication realm.</param>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="realm"/> is <see langword="null"/> or the empty string,
|
||||
/// the <see cref="BasicAuthenticationModuleBase.Realm">Realm</see> property will be set equal to
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see>.</para>
|
||||
/// </remarks>
|
||||
public BasicAuthenticationModule(string baseRoute, string? realm = null)
|
||||
: base(baseRoute, realm)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary of valid user names and passwords.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The accounts.
|
||||
/// </value>
|
||||
public ConcurrentDictionary<string, string> Accounts { get; } = new ConcurrentDictionary<string, string>(StringComparer.InvariantCulture);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<bool> VerifyCredentialsAsync(string path, string userName, string password, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(VerifyCredentialsInternal(userName, password));
|
||||
|
||||
private bool VerifyCredentialsInternal(string userName, string password)
|
||||
=> userName != null
|
||||
&& Accounts.TryGetValue(userName, out var storedPassword)
|
||||
&& string.Equals(password, storedPassword, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
103
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleBase.cs
vendored
Normal file
103
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleBase.cs
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see href="https://tools.ietf.org/html/rfc7617">HTTP basic authentication</see>.
|
||||
/// </summary>
|
||||
public abstract class BasicAuthenticationModuleBase : WebModuleBase
|
||||
{
|
||||
private readonly string _wwwAuthenticateHeaderValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BasicAuthenticationModuleBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base URL path.</param>
|
||||
/// <param name="realm">The authentication realm.</param>
|
||||
/// <remarks>
|
||||
/// <para>If <paramref name="realm"/> is <see langword="null"/> or the empty string,
|
||||
/// the <see cref="Realm"/> property will be set equal to
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see>.</para>
|
||||
/// </remarks>
|
||||
protected BasicAuthenticationModuleBase(string baseRoute, string? realm)
|
||||
: base(baseRoute)
|
||||
{
|
||||
Realm = string.IsNullOrEmpty(realm) ? BaseRoute : realm;
|
||||
|
||||
_wwwAuthenticateHeaderValue = $"Basic realm=\"{Realm}\" charset=UTF-8";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsFinalHandler => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication realm.
|
||||
/// </summary>
|
||||
public string Realm { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected sealed override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
async Task<bool> IsAuthenticatedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (userName, password) = GetCredentials(context.Request);
|
||||
return await VerifyCredentialsAsync(context.RequestedPath, userName, password, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Credentials were not formatted correctly.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.Headers.Set(HttpHeaderNames.WWWAuthenticate, _wwwAuthenticateHeaderValue);
|
||||
|
||||
if (!await IsAuthenticatedAsync().ConfigureAwait(false))
|
||||
throw HttpException.Unauthorized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the credentials given in the <c>Authentication</c> request header.
|
||||
/// </summary>
|
||||
/// <param name="path">The URL path requested by the client. Note that this is relative
|
||||
/// to the module's <see cref="WebModuleBase.BaseRoute">BaseRoute</see>.</param>
|
||||
/// <param name="userName">The user name, or <see langword="null" /> if none has been given.</param>
|
||||
/// <param name="password">The password, or <see langword="null" /> if none has been given.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken" /> use to cancel the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> whose result will be <see langword="true" /> if the given credentials
|
||||
/// are valid, <see langword="false" /> if they are not.</returns>
|
||||
protected abstract Task<bool> VerifyCredentialsAsync(string path, string userName, string password, CancellationToken cancellationToken);
|
||||
|
||||
private static (string UserName, string Password) GetCredentials(IHttpRequest request)
|
||||
{
|
||||
var authHeader = request.Headers[HttpHeaderNames.Authorization];
|
||||
|
||||
if (authHeader == null)
|
||||
return default;
|
||||
|
||||
if (!authHeader.StartsWith("basic ", StringComparison.OrdinalIgnoreCase))
|
||||
return default;
|
||||
|
||||
string credentials;
|
||||
try
|
||||
{
|
||||
credentials = WebServer.DefaultEncoding.GetString(Convert.FromBase64String(authHeader.Substring(6).Trim()));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var separatorPos = credentials.IndexOf(':');
|
||||
return separatorPos < 0
|
||||
? (credentials, string.Empty)
|
||||
: (credentials.Substring(0, separatorPos), credentials.Substring(separatorPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleExtensions.cs
vendored
Normal file
34
Vendor/EmbedIO-3.5.2/Authentication/BasicAuthenticationModuleExtensions.cs
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="BasicAuthenticationModule"/>.
|
||||
/// </summary>
|
||||
public static class BasicAuthenticationModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a username and password to the <see cref="BasicAuthenticationModule.Accounts">Accounts</see> dictionary.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="BasicAuthenticationModule"/> on which this method is called.</param>
|
||||
/// <param name="userName">The user name.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <returns><paramref name="this"/>, with the user name and password added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="userName"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="OverflowException">
|
||||
/// <para>The <see cref="BasicAuthenticationModule.Accounts">Accounts</see> dictionary already contains
|
||||
/// the maximum number of elements (<see cref="int.MaxValue">MaxValue</see>).</para>
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>If a <paramref name="userName"/> account already exists,
|
||||
/// its password is replaced with <paramref name="password"/>.</para>
|
||||
/// </remarks>
|
||||
public static BasicAuthenticationModule WithAccount(this BasicAuthenticationModule @this, string userName, string password)
|
||||
{
|
||||
@this.Accounts.AddOrUpdate(userName, password, (_, __) => password);
|
||||
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Vendor/EmbedIO-3.5.2/CompressionMethod.cs
vendored
Normal file
29
Vendor/EmbedIO-3.5.2/CompressionMethod.cs
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the compression method used to compress a message on
|
||||
/// the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The compression methods that can be used are defined in
|
||||
/// <see href="https://tools.ietf.org/html/rfc7692">
|
||||
/// Compression Extensions for WebSocket</see>.
|
||||
/// </remarks>
|
||||
public enum CompressionMethod : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies no compression.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies "Deflate" compression.
|
||||
/// </summary>
|
||||
Deflate,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies GZip compression.
|
||||
/// </summary>
|
||||
Gzip,
|
||||
}
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/CompressionMethodNames.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/CompressionMethodNames.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes constants for possible values of the <c>Content-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod"/>
|
||||
public static class CompressionMethodNames
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies no compression.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.None"/>
|
||||
public const string None = "identity";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the "Deflate" compression method.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.Deflate"/>
|
||||
public const string Deflate = "deflate";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the GZip compression method.
|
||||
/// </summary>
|
||||
/// <see cref="CompressionMethod.Gzip"/>
|
||||
public const string Gzip = "gzip";
|
||||
}
|
||||
}
|
||||
130
Vendor/EmbedIO-3.5.2/Cors/CorsModule.cs
vendored
Normal file
130
Vendor/EmbedIO-3.5.2/Cors/CorsModule.cs
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Cors
|
||||
{
|
||||
/// <summary>
|
||||
/// Cross-origin resource sharing (CORS) control Module.
|
||||
/// CORS is a mechanism that allows restricted resources (e.g. fonts)
|
||||
/// on a web page to be requested from another domain outside the domain from which the resource originated.
|
||||
/// </summary>
|
||||
public class CorsModule : WebModuleBase
|
||||
{
|
||||
/// <summary>
|
||||
/// A string meaning "All" in CORS headers.
|
||||
/// </summary>
|
||||
public const string All = "*";
|
||||
|
||||
private readonly string _origins;
|
||||
private readonly string _headers;
|
||||
private readonly string _methods;
|
||||
private readonly string[] _validOrigins;
|
||||
private readonly string[] _validMethods;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CorsModule" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="origins">The valid origins. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <param name="headers">The valid headers. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <param name="methods">The valid methods. The default is <see cref="All"/> (<c>*</c>).</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// origins
|
||||
/// or
|
||||
/// headers
|
||||
/// or
|
||||
/// methods
|
||||
/// </exception>
|
||||
public CorsModule(
|
||||
string baseRoute,
|
||||
string origins = All,
|
||||
string headers = All,
|
||||
string methods = All)
|
||||
: base(baseRoute)
|
||||
{
|
||||
_origins = origins ?? throw new ArgumentNullException(nameof(origins));
|
||||
_headers = headers ?? throw new ArgumentNullException(nameof(headers));
|
||||
_methods = methods ?? throw new ArgumentNullException(nameof(methods));
|
||||
|
||||
_validOrigins =
|
||||
origins.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
_validMethods =
|
||||
methods.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
var isOptions = context.Request.HttpVerb == HttpVerbs.Options;
|
||||
|
||||
// If we allow all we don't need to filter
|
||||
if (_origins == All && _headers == All && _methods == All)
|
||||
{
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, All);
|
||||
|
||||
if (isOptions)
|
||||
{
|
||||
ValidateHttpOptions(context);
|
||||
context.SetHandled();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var currentOrigin = context.Request.Headers[HttpHeaderNames.Origin];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentOrigin) && context.Request.IsLocal)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_origins == All)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_validOrigins.Contains(currentOrigin))
|
||||
{
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, currentOrigin);
|
||||
|
||||
if (isOptions)
|
||||
{
|
||||
ValidateHttpOptions(context);
|
||||
context.SetHandled();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ValidateHttpOptions(IHttpContext context)
|
||||
{
|
||||
var requestHeadersHeader = context.Request.Headers[HttpHeaderNames.AccessControlRequestHeaders];
|
||||
if (!string.IsNullOrWhiteSpace(requestHeadersHeader))
|
||||
{
|
||||
// TODO: Remove unwanted headers from request
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowHeaders, requestHeadersHeader);
|
||||
}
|
||||
|
||||
var requestMethodHeader = context.Request.Headers[HttpHeaderNames.AccessControlRequestMethod];
|
||||
if (string.IsNullOrWhiteSpace(requestMethodHeader))
|
||||
return;
|
||||
|
||||
var currentMethods = requestMethodHeader.ToLowerInvariant()
|
||||
.SplitByComma(StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim());
|
||||
|
||||
if (_methods != All && !currentMethods.Any(_validMethods.Contains))
|
||||
throw HttpException.BadRequest();
|
||||
|
||||
context.Response.Headers.Set(HttpHeaderNames.AccessControlAllowMethods, requestMethodHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Vendor/EmbedIO-3.5.2/EmbedIOInternalErrorException.cs
vendored
Normal file
64
Vendor/EmbedIO-3.5.2/EmbedIOInternalErrorException.cs
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/*
|
||||
* NOTE TO CONTRIBUTORS:
|
||||
*
|
||||
* Never use this exception directly.
|
||||
* Use the methods in EmbedIO.Internal.SelfCheck instead.
|
||||
*/
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
#pragma warning disable SA1642 // Constructor summary documentation should begin with standard text
|
||||
/// <summary>
|
||||
/// <para>The exception that is thrown by EmbedIO's internal diagnostic checks to signal a condition
|
||||
/// most probably caused by an error in EmbedIO.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EmbedIOInternalErrorException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
public EmbedIOInternalErrorException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public EmbedIOInternalErrorException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception,
|
||||
/// or <see langword="null"/> if no inner exception is specified.</param>
|
||||
public EmbedIOInternalErrorException(string message, Exception? innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmbedIOInternalErrorException"/> class.
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="SerializationInfo"></see> that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The <see cref="StreamingContext"></see> that contains contextual information about the source or destination.</param>
|
||||
protected EmbedIOInternalErrorException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1642
|
||||
}
|
||||
165
Vendor/EmbedIO-3.5.2/ExceptionHandler.cs
vendored
Normal file
165
Vendor/EmbedIO-3.5.2/ExceptionHandler.cs
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Util;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handlers for unhandled exceptions at both module and server level.
|
||||
/// </summary>
|
||||
/// <seealso cref="IWebServer.OnUnhandledException"/>
|
||||
/// <seealso cref="IWebModule.OnUnhandledException"/>
|
||||
public static class ExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
||||
/// handler to transmit the type of the exception to the client.
|
||||
/// </summary>
|
||||
public const string ExceptionTypeHeaderName = "X-Exception-Type";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the response header used by the <see cref="EmptyResponseWithHeaders" />
|
||||
/// handler to transmit the message of the exception to the client.
|
||||
/// </summary>
|
||||
public const string ExceptionMessageHeaderName = "X-Exception-Message";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the contact information to include in exception responses.
|
||||
/// </summary>
|
||||
public static string? ContactInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to include stack traces
|
||||
/// in exception responses.
|
||||
/// </summary>
|
||||
public static bool IncludeStackTraces { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets the default handler used by <see cref="WebServerBase{TOptions}"/>.</para>
|
||||
/// <para>This is the same as <see cref="HtmlResponse"/>.</para>
|
||||
/// </summary>
|
||||
public static ExceptionHandlerCallback Default { get; } = HtmlResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Sends an empty <c>500 Internal Server Error</c> response.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
public static Task EmptyResponse(IHttpContext context, Exception exception)
|
||||
#pragma warning restore CA1801
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends an empty <c>500 Internal Server Error</c> response,
|
||||
/// with the following additional headers:</para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Header</term>
|
||||
/// <description>Value</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term><c>X-Exception-Type</c></term>
|
||||
/// <description>The name (without namespace) of the type of exception that was thrown.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><c>X-Exception-Message</c></term>
|
||||
/// <description>The <see cref="Exception.Message">Message</see> property of the exception.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <para>The aforementioned header names are available as the <see cref="ExceptionTypeHeaderName" /> and
|
||||
/// <see cref="ExceptionMessageHeaderName" /> properties, respectively.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task EmptyResponseWithHeaders(IHttpContext context, Exception exception)
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
context.Response.Headers[ExceptionTypeHeaderName] = Uri.EscapeDataString(exception.GetType().Name);
|
||||
context.Response.Headers[ExceptionMessageHeaderName] = Uri.EscapeDataString(exception.Message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a <c>500 Internal Server Error</c> response with a HTML payload
|
||||
/// briefly describing the error, including contact information and/or a stack trace
|
||||
/// if specified via the <see cref="ContactInformation"/> and <see cref="IncludeStackTraces"/>
|
||||
/// properties, respectively.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task HtmlResponse(IHttpContext context, Exception exception)
|
||||
=> context.SendStandardHtmlAsync(
|
||||
(int)HttpStatusCode.InternalServerError,
|
||||
text =>
|
||||
{
|
||||
text.Write("<p>The server has encountered an error and was not able to process your request.</p>");
|
||||
text.Write("<p>Please contact the server administrator");
|
||||
|
||||
if (!string.IsNullOrEmpty(ContactInformation))
|
||||
text.Write(" ({0})", WebUtility.HtmlEncode(ContactInformation));
|
||||
|
||||
text.Write(", informing them of the time this error occurred and the action(s) you performed that resulted in this error.</p>");
|
||||
text.Write("<p>The following information may help them in finding out what happened and restoring full functionality.</p>");
|
||||
text.Write(
|
||||
"<p><strong>Exception type:</strong> {0}<p><strong>Message:</strong> {1}",
|
||||
WebUtility.HtmlEncode(exception.GetType().FullName ?? "<unknown>"),
|
||||
WebUtility.HtmlEncode(exception.Message));
|
||||
|
||||
if (IncludeStackTraces)
|
||||
{
|
||||
text.Write(
|
||||
"</p><p><strong>Stack trace:</strong></p><br><pre>{0}</pre>",
|
||||
WebUtility.HtmlEncode(exception.StackTrace));
|
||||
}
|
||||
});
|
||||
|
||||
internal static async Task Handle(string logSource, IHttpContext context, Exception exception, ExceptionHandlerCallback? handler, HttpExceptionHandlerCallback? httpHandler)
|
||||
{
|
||||
if (handler == null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
return;
|
||||
}
|
||||
|
||||
exception.Log(logSource, $"[{context.Id}] Unhandled exception.");
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.SetEmptyResponse((int)HttpStatusCode.InternalServerError);
|
||||
context.Response.DisableCaching();
|
||||
await handler(context, exception)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception httpException) when (httpException is IHttpException httpException1)
|
||||
{
|
||||
if (httpHandler == null)
|
||||
throw;
|
||||
|
||||
await httpHandler(context, httpException1).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
exception2.Log(logSource, $"[{context.Id}] Unhandled exception while handling exception.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Vendor/EmbedIO-3.5.2/ExceptionHandlerCallback.cs
vendored
Normal file
22
Vendor/EmbedIO-3.5.2/ExceptionHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to provide information about an unhandled exception occurred while processing a request.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="exception">The unhandled exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>When this delegate is called, the response's status code has already been set to
|
||||
/// <see cref="HttpStatusCode.InternalServerError" />.</para>
|
||||
/// <para>Any exception thrown by a handler (even a HTTP exception) will go unhandled: the web server
|
||||
/// will not crash, but processing of the request will be aborted, and the response will be flushed as-is.
|
||||
/// In other words, it is not a good ides to <c>throw HttpException.NotFound()</c> (or similar)
|
||||
/// from a handler.</para>
|
||||
/// </remarks>
|
||||
public delegate Task ExceptionHandlerCallback(IHttpContext context, Exception exception);
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/Files/DirectoryLister.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/Files/DirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
using EmbedIO.Files.Internal;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard directory listers for <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDirectoryLister"/>
|
||||
public static class DirectoryLister
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets an <see cref="IDirectoryLister"/> interface
|
||||
/// that produces a HTML listing of a directory.</para>
|
||||
/// <para>The output of the returned directory lister
|
||||
/// is the same as a directory listing obtained
|
||||
/// by EmbedIO version 2.</para>
|
||||
/// </summary>
|
||||
public static IDirectoryLister Html => HtmlDirectoryLister.Instance;
|
||||
}
|
||||
}
|
||||
185
Vendor/EmbedIO-3.5.2/Files/FileCache.Section.cs
vendored
Normal file
185
Vendor/EmbedIO-3.5.2/Files/FileCache.Section.cs
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using EmbedIO.Files.Internal;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
public sealed partial class FileCache
|
||||
{
|
||||
internal class Section
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly Dictionary<string, FileCacheItem> _items = new Dictionary<string, FileCacheItem>(StringComparer.Ordinal);
|
||||
private long _totalSize;
|
||||
private string? _oldestKey;
|
||||
private string? _newestKey;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
ClearCore();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string path, FileCacheItem item)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
AddItemCore(path, item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string path)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
RemoveItemCore(path);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(string path, out FileCacheItem item)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!_items.TryGetValue(path, out item))
|
||||
return false;
|
||||
|
||||
RefreshItemCore(path, item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal long GetLeastRecentUseTime()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _oldestKey == null ? long.MaxValue : _items[_oldestKey].LastUsedAt;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes least recently used item.
|
||||
// Returns size of removed item.
|
||||
internal long RemoveLeastRecentItem()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return RemoveLeastRecentItemCore();
|
||||
}
|
||||
}
|
||||
|
||||
internal long GetTotalSize()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _totalSize;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateTotalSize(long delta)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_totalSize += delta;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCore()
|
||||
{
|
||||
_items.Clear();
|
||||
_totalSize = 0;
|
||||
_oldestKey = null;
|
||||
_newestKey = null;
|
||||
}
|
||||
|
||||
// Adds an item as most recently used.
|
||||
private void AddItemCore(string path, FileCacheItem item)
|
||||
{
|
||||
item.PreviousKey = _newestKey;
|
||||
item.NextKey = null;
|
||||
item.LastUsedAt = TimeBase.ElapsedTicks;
|
||||
|
||||
if (_newestKey != null)
|
||||
_items[_newestKey].NextKey = path;
|
||||
|
||||
_newestKey = path;
|
||||
|
||||
_items[path] = item;
|
||||
_totalSize += item.SizeInCache;
|
||||
}
|
||||
|
||||
// Removes an item.
|
||||
private void RemoveItemCore(string path)
|
||||
{
|
||||
if (!_items.TryGetValue(path, out var item))
|
||||
return;
|
||||
|
||||
if (_oldestKey == path)
|
||||
_oldestKey = item.NextKey;
|
||||
|
||||
if (_newestKey == path)
|
||||
_newestKey = item.PreviousKey;
|
||||
|
||||
if (item.PreviousKey != null)
|
||||
_items[item.PreviousKey].NextKey = item.NextKey;
|
||||
|
||||
if (item.NextKey != null)
|
||||
_items[item.NextKey].PreviousKey = item.PreviousKey;
|
||||
|
||||
item.PreviousKey = null;
|
||||
item.NextKey = null;
|
||||
|
||||
_items.Remove(path);
|
||||
_totalSize -= item.SizeInCache;
|
||||
}
|
||||
|
||||
// Removes the least recently used item.
|
||||
// returns size of removed item.
|
||||
private long RemoveLeastRecentItemCore()
|
||||
{
|
||||
var path = _oldestKey;
|
||||
if (path == null)
|
||||
return 0;
|
||||
|
||||
var item = _items[path];
|
||||
|
||||
if ((_oldestKey = item.NextKey) != null)
|
||||
_items[_oldestKey].PreviousKey = null;
|
||||
|
||||
if (_newestKey == path)
|
||||
_newestKey = null;
|
||||
|
||||
item.PreviousKey = null;
|
||||
item.NextKey = null;
|
||||
|
||||
_items.Remove(path);
|
||||
_totalSize -= item.SizeInCache;
|
||||
return item.SizeInCache;
|
||||
}
|
||||
|
||||
// Moves an item to most recently used.
|
||||
private void RefreshItemCore(string path, FileCacheItem item)
|
||||
{
|
||||
item.LastUsedAt = TimeBase.ElapsedTicks;
|
||||
|
||||
if (_newestKey == path)
|
||||
return;
|
||||
|
||||
if (_oldestKey == path)
|
||||
_oldestKey = item.NextKey;
|
||||
|
||||
if (item.PreviousKey != null)
|
||||
_items[item.PreviousKey].NextKey = item.NextKey;
|
||||
|
||||
if (item.NextKey != null)
|
||||
_items[item.NextKey].PreviousKey = item.PreviousKey;
|
||||
|
||||
item.PreviousKey = _newestKey;
|
||||
item.NextKey = null;
|
||||
|
||||
_items[_newestKey!].NextKey = path;
|
||||
_newestKey = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Vendor/EmbedIO-3.5.2/Files/FileCache.cs
vendored
Normal file
178
Vendor/EmbedIO-3.5.2/Files/FileCache.cs
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Internal;
|
||||
using Swan.Threading;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
#pragma warning disable CA1001 // Type owns disposable field '_cleaner' but is not disposable - _cleaner has its own dispose semantics.
|
||||
/// <summary>
|
||||
/// A cache where one or more instances of <see cref="FileModule"/> can store hashes and file contents.
|
||||
/// </summary>
|
||||
public sealed partial class FileCache
|
||||
#pragma warning restore CA1001
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value for the <see cref="MaxSizeKb"/> property.
|
||||
/// </summary>
|
||||
public const int DefaultMaxSizeKb = 10240;
|
||||
|
||||
/// <summary>
|
||||
/// The default value for the <see cref="MaxFileSizeKb"/> property.
|
||||
/// </summary>
|
||||
public const int DefaultMaxFileSizeKb = 200;
|
||||
|
||||
private static readonly Stopwatch TimeBase = Stopwatch.StartNew();
|
||||
|
||||
private static readonly object DefaultSyncRoot = new object();
|
||||
private static FileCache? _defaultInstance;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Section> _sections = new ConcurrentDictionary<string, Section>(StringComparer.Ordinal);
|
||||
private int _sectionCount; // Because ConcurrentDictionary<,>.Count is locking.
|
||||
private int _maxSizeKb = DefaultMaxSizeKb;
|
||||
private int _maxFileSizeKb = DefaultMaxFileSizeKb;
|
||||
private PeriodicTask? _cleaner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="FileCache"/> instance used by <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
public static FileCache Default
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultInstance != null)
|
||||
return _defaultInstance;
|
||||
|
||||
lock (DefaultSyncRoot)
|
||||
{
|
||||
if (_defaultInstance == null)
|
||||
_defaultInstance = new FileCache();
|
||||
}
|
||||
|
||||
return _defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the maximum total size of cached data in kilobytes (1 kilobyte = 1024 bytes).</para>
|
||||
/// <para>The default value for this property is stored in the <see cref="DefaultMaxSizeKb"/> constant field.</para>
|
||||
/// <para>Setting this property to a value less lower han 1 has the same effect as setting it to 1.</para>
|
||||
/// </summary>
|
||||
public int MaxSizeKb
|
||||
{
|
||||
get => _maxSizeKb;
|
||||
set => _maxSizeKb = Math.Max(value, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the maximum size of a single cached file in kilobytes (1 kilobyte = 1024 bytes).</para>
|
||||
/// <para>A single file's contents may be present in a cache more than once, if the file
|
||||
/// is requested with different <c>Accept-Encoding</c> request headers. This property acts as a threshold
|
||||
/// for the uncompressed size of a file.</para>
|
||||
/// <para>The default value for this property is stored in the <see cref="DefaultMaxFileSizeKb"/> constant field.</para>
|
||||
/// <para>Setting this property to a value lower than 0 has the same effect as setting it to 0, in fact
|
||||
/// completely disabling the caching of file contents for this cache.</para>
|
||||
/// <para>This property cannot be set to a value higher than 2097151; in other words, it is not possible
|
||||
/// to cache files bigger than two Gigabytes (1 Gigabyte = 1048576 kilobytes) minus 1 kilobyte.</para>
|
||||
/// </summary>
|
||||
public int MaxFileSizeKb
|
||||
{
|
||||
get => _maxFileSizeKb;
|
||||
set => _maxFileSizeKb = Math.Min(Math.Max(value, 0), 2097151);
|
||||
}
|
||||
|
||||
// Cast as IDictionary because we WANT an exception to be thrown if the name exists.
|
||||
// It would mean that something is very, very wrong.
|
||||
internal Section AddSection(string name)
|
||||
{
|
||||
var section = new Section();
|
||||
(_sections as IDictionary<string, Section>).Add(name, section);
|
||||
|
||||
if (Interlocked.Increment(ref _sectionCount) == 1)
|
||||
_cleaner = new PeriodicTask(TimeSpan.FromMinutes(1), CheckMaxSize);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
internal void RemoveSection(string name)
|
||||
{
|
||||
_sections.TryRemove(name, out _);
|
||||
|
||||
if (Interlocked.Decrement(ref _sectionCount) == 0)
|
||||
{
|
||||
_cleaner?.Dispose();
|
||||
_cleaner = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckMaxSize(CancellationToken cancellationToken)
|
||||
{
|
||||
var timeKeeper = new TimeKeeper();
|
||||
var maxSizeKb = _maxSizeKb;
|
||||
var initialSizeKb = ComputeTotalSize() / 1024L;
|
||||
|
||||
if (initialSizeKb <= maxSizeKb)
|
||||
{
|
||||
$"Total size = {initialSizeKb}/{_maxSizeKb}kb, not purging.".Debug(nameof(FileCache));
|
||||
return;
|
||||
}
|
||||
|
||||
$"Total size = {initialSizeKb}/{_maxSizeKb}kb, purging...".Debug(nameof(FileCache));
|
||||
|
||||
var removedCount = 0;
|
||||
var removedSize = 0L;
|
||||
var totalSizeKb = initialSizeKb;
|
||||
var threshold = 973L * maxSizeKb / 1024L; // About 95% of maximum allowed size
|
||||
while (totalSizeKb > threshold)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var section = GetSectionWithLeastRecentItem();
|
||||
if (section == null)
|
||||
return;
|
||||
|
||||
removedSize += section.RemoveLeastRecentItem();
|
||||
removedCount++;
|
||||
|
||||
await Task.Yield();
|
||||
|
||||
totalSizeKb = ComputeTotalSize() / 1024L;
|
||||
}
|
||||
|
||||
$"Purge completed in {timeKeeper.ElapsedTime}ms: removed {removedCount} items ({removedSize / 1024L}kb). Total size is now {totalSizeKb}kb."
|
||||
.Debug(nameof(FileCache));
|
||||
}
|
||||
|
||||
// Enumerate key / value pairs because the Keys and Values property
|
||||
// of ConcurrentDictionary<,> have snapshot semantics,
|
||||
// while GetEnumerator enumerates without locking.
|
||||
private long ComputeTotalSize()
|
||||
=> _sections.Sum(pair => pair.Value.GetTotalSize());
|
||||
|
||||
private Section? GetSectionWithLeastRecentItem()
|
||||
{
|
||||
Section? result = null;
|
||||
var earliestTime = long.MaxValue;
|
||||
foreach (var pair in _sections)
|
||||
{
|
||||
var section = pair.Value;
|
||||
var time = section.GetLeastRecentUseTime();
|
||||
|
||||
if (time < earliestTime)
|
||||
{
|
||||
result = section;
|
||||
earliestTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
635
Vendor/EmbedIO-3.5.2/Files/FileModule.cs
vendored
Normal file
635
Vendor/EmbedIO-3.5.2/Files/FileModule.cs
vendored
Normal file
@@ -0,0 +1,635 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Files.Internal;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// A module serving files and directory listings from an <see cref="IFileProvider"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class FileModule : WebModuleBase, IDisposable, IMimeTypeCustomizer
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Default value for <see cref="DefaultDocument"/>.</para>
|
||||
/// </summary>
|
||||
public const string DefaultDocumentName = "index.html";
|
||||
|
||||
private readonly string _cacheSectionName = UniqueIdGenerator.GetNext();
|
||||
private readonly MimeTypeCustomizer _mimeTypeCustomizer = new MimeTypeCustomizer();
|
||||
private readonly ConcurrentDictionary<string, MappedResourceInfo>? _mappingCache;
|
||||
|
||||
private FileCache _cache = FileCache.Default;
|
||||
private bool _contentCaching = true;
|
||||
private string? _defaultDocument = DefaultDocumentName;
|
||||
private string? _defaultExtension;
|
||||
private IDirectoryLister? _directoryLister;
|
||||
private FileRequestHandlerCallback _onMappingFailed = FileRequestHandler.ThrowNotFound;
|
||||
private FileRequestHandlerCallback _onDirectoryNotListable = FileRequestHandler.ThrowUnauthorized;
|
||||
private FileRequestHandlerCallback _onMethodNotAllowed = FileRequestHandler.ThrowMethodNotAllowed;
|
||||
|
||||
private FileCache.Section? _cacheSection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileModule"/> class,
|
||||
/// using the specified cache.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="provider">An <see cref="IFileProvider"/> interface that provides access
|
||||
/// to actual files and directories.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
|
||||
public FileModule(string baseRoute, IFileProvider provider)
|
||||
: base(baseRoute)
|
||||
{
|
||||
Provider = Validate.NotNull(nameof(provider), provider);
|
||||
_mappingCache = Provider.IsImmutable
|
||||
? new ConcurrentDictionary<string, MappedResourceInfo>()
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="FileModule"/> class.
|
||||
/// </summary>
|
||||
~FileModule()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFileProvider"/>interface that provides access
|
||||
/// to actual files and directories served by this module.
|
||||
/// </summary>
|
||||
public IFileProvider Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="FileCache"/> used by this module to store hashes and,
|
||||
/// optionally, file contents and rendered directory listings.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
public FileCache Cache
|
||||
{
|
||||
get => _cache;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_cache = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a value indicating whether this module caches the contents of files
|
||||
/// and directory listings.</para>
|
||||
/// <para>Note that the actual representations of files are stored in <see cref="FileCache"/>;
|
||||
/// thus, for example, if a file is always requested with an <c>Accept-Encoding</c> of <c>gzip</c>,
|
||||
/// only the gzipped contents of the file will be cached.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public bool ContentCaching
|
||||
{
|
||||
get => _contentCaching;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_contentCaching = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the name of the default document served, if it exists, instead of a directory listing
|
||||
/// when the path of a requested URL maps to a directory.</para>
|
||||
/// <para>The default value for this property is the <see cref="DefaultDocumentName"/> constant.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public string? DefaultDocument
|
||||
{
|
||||
get => _defaultDocument;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_defaultDocument = string.IsNullOrEmpty(value) ? null : value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the default extension appended to requested URL paths that do not map
|
||||
/// to any file or directory. Defaults to <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentException">This property is being set to a non-<see langword="null"/>,
|
||||
/// non-empty string that does not start with a period (<c>.</c>).</exception>
|
||||
public string? DefaultExtension
|
||||
{
|
||||
get => _defaultExtension;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
_defaultExtension = null;
|
||||
}
|
||||
else if (value![0] != '.')
|
||||
{
|
||||
throw new ArgumentException("Default extension does not start with a period.", nameof(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaultExtension = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the <see cref="IDirectoryLister"/> interface used to generate
|
||||
/// directory listing in this module.</para>
|
||||
/// <para>A value of <see langword="null"/> (the default) disables the generation
|
||||
/// of directory listings.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
public IDirectoryLister? DirectoryLister
|
||||
{
|
||||
get => _directoryLister;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_directoryLister = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path could not be mapped to any file or directory.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowNotFound"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnMappingFailed
|
||||
{
|
||||
get => _onMappingFailed;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onMappingFailed = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path has been mapped to a directory, but directory listing has been
|
||||
/// disabled by setting <see cref="DirectoryLister"/> to <see langword="null"/>.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowUnauthorized"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnDirectoryNotListable
|
||||
{
|
||||
get => _onDirectoryNotListable;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onDirectoryNotListable = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="FileRequestHandlerCallback"/> that is called whenever
|
||||
/// the requested URL path has been mapped to a file or directory, but the request's
|
||||
/// HTTP method is neither <c>GET</c> nor <c>HEAD</c>.</para>
|
||||
/// <para>The default is <see cref="FileRequestHandler.ThrowMethodNotAllowed"/>.</para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The module's configuration is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public FileRequestHandlerCallback OnMethodNotAllowed
|
||||
{
|
||||
get => _onMethodNotAllowed;
|
||||
set
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_onMethodNotAllowed = Validate.NotNull(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
string IMimeTypeProvider.GetMimeType(string extension)
|
||||
=> _mimeTypeCustomizer.GetMimeType(extension);
|
||||
|
||||
bool IMimeTypeProvider.TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> _mimeTypeCustomizer.TryDetermineCompression(mimeType, out preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
=> _mimeTypeCustomizer.AddCustomMimeType(extension, mimeType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
=> _mimeTypeCustomizer.PreferCompression(mimeType, preferCompression);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the part of <see cref="Cache"/> used by this module.
|
||||
/// </summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
_mappingCache?.Clear();
|
||||
_cacheSection?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_cacheSection != null)
|
||||
Provider.ResourceChanged -= _cacheSection.Remove;
|
||||
|
||||
if (Provider is IDisposable disposableProvider)
|
||||
disposableProvider.Dispose();
|
||||
|
||||
if (_cacheSection != null)
|
||||
Cache.RemoveSection(_cacheSectionName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnBeforeLockConfiguration()
|
||||
{
|
||||
base.OnBeforeLockConfiguration();
|
||||
|
||||
_mimeTypeCustomizer.Lock();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(CancellationToken cancellationToken)
|
||||
{
|
||||
base.OnStart(cancellationToken);
|
||||
|
||||
_cacheSection = Cache.AddSection(_cacheSectionName);
|
||||
Provider.ResourceChanged += _cacheSection.Remove;
|
||||
Provider.Start(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
MappedResourceInfo? info;
|
||||
|
||||
var path = context.RequestedPath;
|
||||
|
||||
// Map the URL path to a mapped resource.
|
||||
// DefaultDocument and DefaultExtension are handled here.
|
||||
// Use the mapping cache if it exists.
|
||||
if (_mappingCache == null)
|
||||
{
|
||||
info = MapUrlPath(path, context);
|
||||
}
|
||||
else if (!_mappingCache.TryGetValue(path, out info))
|
||||
{
|
||||
info = MapUrlPath(path, context);
|
||||
if (info != null)
|
||||
_ = _mappingCache.AddOrUpdate(path, info, (_, __) => info);
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
// If mapping failed, send a "404 Not Found" response, or whatever OnMappingFailed chooses to do.
|
||||
// For example, it may return a default resource (think a folder of images and an imageNotFound.jpg),
|
||||
// or redirect the request.
|
||||
await OnMappingFailed(context, null).ConfigureAwait(false);
|
||||
}
|
||||
else if (!IsHttpMethodAllowed(context.Request, out var sendResponseBody))
|
||||
{
|
||||
// If there is a mapped resource, check that the HTTP method is either GET or HEAD.
|
||||
// Otherwise, send a "405 Method Not Allowed" response, or whatever OnMethodNotAllowed chooses to do.
|
||||
await OnMethodNotAllowed(context, info).ConfigureAwait(false);
|
||||
}
|
||||
else if (info.IsDirectory && DirectoryLister == null)
|
||||
{
|
||||
// If a directory listing was requested, but there is no DirectoryLister,
|
||||
// send a "403 Unauthorized" response, or whatever OnDirectoryNotListable chooses to do.
|
||||
// For example, one could prefer to send "404 Not Found" instead.
|
||||
await OnDirectoryNotListable(context, info).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleResource(context, info, sendResponseBody).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Tells whether a request's HTTP method is suitable for processing by FileModule
|
||||
// and, if so, whether a response body must be sent.
|
||||
private static bool IsHttpMethodAllowed(IHttpRequest request, out bool sendResponseBody)
|
||||
{
|
||||
switch (request.HttpVerb)
|
||||
{
|
||||
case HttpVerbs.Head:
|
||||
sendResponseBody = false;
|
||||
return true;
|
||||
case HttpVerbs.Get:
|
||||
sendResponseBody = true;
|
||||
return true;
|
||||
default:
|
||||
sendResponseBody = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepares response headers for a "200 OK" or "304 Not Modified" response.
|
||||
// RFC7232, Section 4.1
|
||||
private static void PreparePositiveResponse(IHttpResponse response, MappedResourceInfo info, string contentType, string entityTag, Action<IHttpResponse> setCompression)
|
||||
{
|
||||
setCompression(response);
|
||||
response.ContentType = contentType;
|
||||
response.Headers.Set(HttpHeaderNames.ETag, entityTag);
|
||||
response.Headers.Set(HttpHeaderNames.LastModified, HttpDate.Format(info.LastModifiedUtc));
|
||||
response.Headers.Set(HttpHeaderNames.CacheControl, "max-age=0, must-revalidate");
|
||||
response.Headers.Set(HttpHeaderNames.AcceptRanges, "bytes");
|
||||
}
|
||||
|
||||
// Attempts to map a module-relative URL path to a mapped resource,
|
||||
// handling DefaultDocument and DefaultExtension.
|
||||
// Returns null if not found.
|
||||
// Directories mus be returned regardless of directory listing being enabled.
|
||||
private MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
var result = Provider.MapUrlPath(urlPath, mimeTypeProvider);
|
||||
|
||||
// If urlPath maps to a file, no further searching is needed.
|
||||
if (result?.IsFile ?? false)
|
||||
return result;
|
||||
|
||||
// Look for a default document.
|
||||
// Don't append an additional slash if the URL path is "/".
|
||||
// The default document, if found, must be a file, not a directory.
|
||||
if (DefaultDocument != null)
|
||||
{
|
||||
var defaultDocumentPath = urlPath + (urlPath.Length > 1 ? "/" : string.Empty) + DefaultDocument;
|
||||
var defaultDocumentResult = Provider.MapUrlPath(defaultDocumentPath, mimeTypeProvider);
|
||||
if (defaultDocumentResult?.IsFile ?? false)
|
||||
return defaultDocumentResult;
|
||||
}
|
||||
|
||||
// Try to apply default extension (but not if the URL path is "/",
|
||||
// i.e. the only normalized, non-base URL path that ends in a slash).
|
||||
// When the default extension is applied, the result must be a file.
|
||||
if (DefaultExtension != null && urlPath.Length > 1)
|
||||
{
|
||||
var defaultExtensionResult = Provider.MapUrlPath(urlPath + DefaultExtension, mimeTypeProvider);
|
||||
if (defaultExtensionResult?.IsFile ?? false)
|
||||
return defaultExtensionResult;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task HandleResource(IHttpContext context, MappedResourceInfo info, bool sendResponseBody)
|
||||
{
|
||||
// Try to extract resource information from cache.
|
||||
var cachingThreshold = 1024L * Cache.MaxFileSizeKb;
|
||||
if (!_cacheSection!.TryGet(info.Path, out var cacheItem))
|
||||
{
|
||||
// Resource information not yet cached
|
||||
cacheItem = new FileCacheItem(_cacheSection, info.LastModifiedUtc, info.Length);
|
||||
_cacheSection.Add(info.Path, cacheItem);
|
||||
}
|
||||
else if (!Provider.IsImmutable)
|
||||
{
|
||||
// Check whether the resource has changed.
|
||||
// If so, discard the cache item and create a new one.
|
||||
if (cacheItem.LastModifiedUtc != info.LastModifiedUtc || cacheItem.Length != info.Length)
|
||||
{
|
||||
_cacheSection.Remove(info.Path);
|
||||
cacheItem = new FileCacheItem(_cacheSection, info.LastModifiedUtc, info.Length);
|
||||
_cacheSection.Add(info.Path, cacheItem);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we have a cacheItem for the resource.
|
||||
* It may have been just created, or it may or may not have a cached content,
|
||||
* depending upon the value of the ContentCaching property,
|
||||
* the size of the resource, and the value of the
|
||||
* MaxFileSizeKb of our Cache.
|
||||
*/
|
||||
|
||||
// If the content type is not a valid MIME type, assume the default.
|
||||
var contentType = info.ContentType ?? DirectoryLister?.ContentType ?? MimeType.Default;
|
||||
var mimeType = MimeType.StripParameters(contentType);
|
||||
if (!MimeType.IsMimeType(mimeType, false))
|
||||
contentType = mimeType = MimeType.Default;
|
||||
|
||||
// Next we're going to apply proactive negotiation
|
||||
// to determine whether we agree with the client upon the compression
|
||||
// (or lack of it) to use for the resource.
|
||||
//
|
||||
// The combination of partial responses and entity compression
|
||||
// is not really standardized and could lead to a world of pain.
|
||||
// Thus, if there is a Range header in the request, try to negotiate for no compression.
|
||||
// Later, if there is compression anyway, we will ignore the Range header.
|
||||
if (!context.TryDetermineCompression(mimeType, out var preferCompression))
|
||||
preferCompression = true;
|
||||
preferCompression &= context.Request.Headers.Get(HttpHeaderNames.Range) == null;
|
||||
if (!context.Request.TryNegotiateContentEncoding(preferCompression, out var compressionMethod, out var setCompressionInResponse))
|
||||
{
|
||||
// If negotiation failed, the returned callback will do the right thing.
|
||||
setCompressionInResponse(context.Response);
|
||||
return;
|
||||
}
|
||||
|
||||
var entityTag = info.GetEntityTag(compressionMethod);
|
||||
|
||||
// Send a "304 Not Modified" response if applicable.
|
||||
//
|
||||
// RFC7232, Section 3.3: "A recipient MUST ignore If-Modified-Since
|
||||
// if the request contains an If-None-Match header field."
|
||||
if (context.Request.CheckIfNoneMatch(entityTag, out var ifNoneMatchExists)
|
||||
|| (!ifNoneMatchExists && context.Request.CheckIfModifiedSince(info.LastModifiedUtc, out _)))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point we know the response is "200 OK",
|
||||
* unless the request is a range request.
|
||||
*
|
||||
* RFC7233, Section 3.1: "The Range header field is evaluated after evaluating the precondition
|
||||
* header fields defined in RFC7232, and only if the result in absence
|
||||
* of the Range header field would be a 200 (OK) response. In other
|
||||
* words, Range is ignored when a conditional GET would result in a 304
|
||||
* (Not Modified) response."
|
||||
*/
|
||||
|
||||
// Before evaluating ranges, we must know the content length.
|
||||
// This is easy for files, as it is stored in info.Length.
|
||||
// Directories always have info.Length == 0; therefore,
|
||||
// unless the directory listing is cached, we must generate it now
|
||||
// (and cache it while we're there, if applicable).
|
||||
var content = cacheItem.GetContent(compressionMethod);
|
||||
if (info.IsDirectory && content == null)
|
||||
{
|
||||
long uncompressedLength;
|
||||
(content, uncompressedLength) = await GenerateDirectoryListingAsync(context, info, compressionMethod)
|
||||
.ConfigureAwait(false);
|
||||
if (ContentCaching && uncompressedLength <= cachingThreshold)
|
||||
_ = cacheItem.SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
var contentLength = content?.Length ?? info.Length;
|
||||
|
||||
// Ignore range request is compression is enabled
|
||||
// (or should I say forced, since negotiation has tried not to use it).
|
||||
var partialStart = 0L;
|
||||
var partialUpperBound = contentLength - 1;
|
||||
var isPartial = compressionMethod == CompressionMethod.None
|
||||
&& context.Request.IsRangeRequest(contentLength, entityTag, info.LastModifiedUtc, out partialStart, out partialUpperBound);
|
||||
var responseContentLength = contentLength;
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
// Prepare a "206 Partial Content" response.
|
||||
responseContentLength = partialUpperBound - partialStart + 1;
|
||||
context.Response.StatusCode = (int)HttpStatusCode.PartialContent;
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
context.Response.Headers.Set(HttpHeaderNames.ContentRange, $"bytes {partialStart}-{partialUpperBound}/{contentLength}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prepare a "200 OK" response.
|
||||
PreparePositiveResponse(context.Response, info, contentType, entityTag, setCompressionInResponse);
|
||||
}
|
||||
|
||||
// If it's a HEAD request, we're done.
|
||||
if (!sendResponseBody)
|
||||
return;
|
||||
|
||||
// If content must be sent AND cached, first read it and store it.
|
||||
// If the requested resource is a directory, we have already listed it by now,
|
||||
// so it must be a file for content to be null.
|
||||
if (content == null && ContentCaching && contentLength <= cachingThreshold)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var compressor = new CompressionStream(memoryStream, compressionMethod))
|
||||
{
|
||||
using var source = Provider.OpenFile(info.Path);
|
||||
await source.CopyToAsync(compressor, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
content = memoryStream.ToArray();
|
||||
responseContentLength = content.Length;
|
||||
}
|
||||
|
||||
_ = cacheItem.SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
// Transfer cached content if present.
|
||||
if (content != null)
|
||||
{
|
||||
context.Response.ContentLength64 = responseContentLength;
|
||||
var offset = isPartial ? (int) partialStart : 0;
|
||||
await context.Response.OutputStream.WriteAsync(content, offset, (int)responseContentLength, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Read and transfer content without caching.
|
||||
using (var source = Provider.OpenFile(info.Path))
|
||||
{
|
||||
context.Response.SendChunked = true;
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
var buffer = new byte[WebServer.StreamCopyBufferSize];
|
||||
if (source.CanSeek)
|
||||
{
|
||||
source.Position = partialStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
var skipLength = (int)partialStart;
|
||||
while (skipLength > 0)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, Math.Min(skipLength, buffer.Length), context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
skipLength -= read;
|
||||
}
|
||||
}
|
||||
|
||||
var transferSize = responseContentLength;
|
||||
while (transferSize >= WebServer.StreamCopyBufferSize)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.Response.OutputStream.WriteAsync(buffer, 0, read, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
transferSize -= read;
|
||||
}
|
||||
|
||||
if (transferSize > 0)
|
||||
{
|
||||
var read = await source.ReadAsync(buffer, 0, (int)transferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await context.Response.OutputStream.WriteAsync(buffer, 0, read, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using var compressor = new CompressionStream(context.Response.OutputStream, compressionMethod);
|
||||
await source.CopyToAsync(compressor, WebServer.StreamCopyBufferSize, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uses DirectoryLister to generate a directory listing asynchronously.
|
||||
// Returns a tuple of the generated content and its *uncompressed* length
|
||||
// (useful to decide whether it can be cached).
|
||||
private async Task<(byte[], long)> GenerateDirectoryListingAsync(
|
||||
IHttpContext context,
|
||||
MappedResourceInfo info,
|
||||
CompressionMethod compressionMethod)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var stream = new CompressionStream(memoryStream, compressionMethod);
|
||||
|
||||
await DirectoryLister!.ListDirectoryAsync(
|
||||
info,
|
||||
context.Request.Url.AbsolutePath,
|
||||
Provider.GetDirectoryEntries(info.Path, context),
|
||||
stream,
|
||||
context.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (memoryStream.ToArray(), stream.UncompressedLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
282
Vendor/EmbedIO-3.5.2/Files/FileModuleExtensions.cs
vendored
Normal file
282
Vendor/EmbedIO-3.5.2/Files/FileModuleExtensions.cs
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="FileModule"/> and derived classes.
|
||||
/// </summary>
|
||||
public static class FileModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the <see cref="FileCache"/> used by a module to store hashes and,
|
||||
/// optionally, file contents and rendered directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">An instance of <see cref="FileCache"/>.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.Cache">Cache</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.Cache"/>
|
||||
public static TModule WithCache<TModule>(this TModule @this, FileCache value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.Cache = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value indicating whether a module caches the contents of files
|
||||
/// and directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value"><see langword="true"/> to enable caching of contents;
|
||||
/// <see langword="false"/> to disable it.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this, bool value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="true"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = true;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="maxFileSizeKb"><see langword="true"/> sets the maximum size of a single cached file in kilobytes</param>
|
||||
/// <param name="maxSizeKb"><see langword="true"/> sets the maximum total size of cached data in kilobytes</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="true"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithContentCaching<TModule>(this TModule @this, int maxFileSizeKb, int maxSizeKb)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = true;
|
||||
@this.Cache.MaxFileSizeKb = maxFileSizeKb;
|
||||
@this.Cache.MaxSizeKb = maxSizeKb;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables caching of file contents and directory listings on a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.ContentCaching">ContentCaching</see> property
|
||||
/// set to <see langword="false"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.ContentCaching"/>
|
||||
public static TModule WithoutContentCaching<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.ContentCaching = false;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the default document served, if it exists, instead of a directory listing
|
||||
/// when the path of a requested URL maps to a directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">The name of the default document.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultDocument">DefaultDocument</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultDocument"/>
|
||||
public static TModule WithDefaultDocument<TModule>(this TModule @this, string value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultDocument = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the default document to <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultDocument">DefaultDocument</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultDocument"/>
|
||||
public static TModule WithoutDefaultDocument<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultDocument = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default extension appended to requested URL paths that do not map
|
||||
/// to any file or directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">The default extension.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultExtension">DefaultExtension</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="value"/> is a non-<see langword="null"/>,
|
||||
/// non-empty string that does not start with a period (<c>.</c>).</exception>
|
||||
/// <seealso cref="FileModule.DefaultExtension"/>
|
||||
public static TModule WithDefaultExtension<TModule>(this TModule @this, string value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultExtension = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default extension to <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DefaultExtension">DefaultExtension</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DefaultExtension"/>
|
||||
public static TModule WithoutDefaultExtension<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DefaultExtension = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="IDirectoryLister"/> interface used to generate
|
||||
/// directory listing in a module.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">An <see cref="IDirectoryLister"/> interface, or <see langword="null"/>
|
||||
/// to disable the generation of directory listings.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// set to <paramref name="value"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DirectoryLister"/>
|
||||
public static TModule WithDirectoryLister<TModule>(this TModule @this, IDirectoryLister value)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DirectoryLister = value;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a module's <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// to <see langword="null"/>, disabling the generation of directory listings.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.DirectoryLister">DirectoryLister</see> property
|
||||
/// set to <see langword="null"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <seealso cref="FileModule.DirectoryLister"/>
|
||||
public static TModule WithoutDirectoryLister<TModule>(this TModule @this)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.DirectoryLister = null;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path could not be mapped to any file or directory.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnMappingFailed">OnMappingFailed</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnMappingFailed"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleMappingFailed<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnMappingFailed = callback;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path has been mapped to a directory, but directory listing has been
|
||||
/// disabled.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnDirectoryNotListable">OnDirectoryNotListable</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnDirectoryNotListable"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleDirectoryNotListable<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnDirectoryNotListable = callback;
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="FileRequestHandlerCallback"/> that is called by a module whenever
|
||||
/// the requested URL path has been mapped to a file or directory, but the request's
|
||||
/// HTTP method is neither <c>GET</c> nor <c>HEAD</c>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module on which this method is called.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="callback">The method to call.</param>
|
||||
/// <returns><paramref name="this"/> with its <see cref="FileModule.OnMethodNotAllowed">OnMethodNotAllowed</see> property
|
||||
/// set to <paramref name="callback"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The configuration of <paramref name="this"/> is locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="FileModule.OnMethodNotAllowed"/>
|
||||
/// <seealso cref="FileRequestHandler"/>
|
||||
public static TModule HandleMethodNotAllowed<TModule>(this TModule @this, FileRequestHandlerCallback callback)
|
||||
where TModule : FileModule
|
||||
{
|
||||
@this.OnMethodNotAllowed = callback;
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Vendor/EmbedIO-3.5.2/Files/FileRequestHandler.cs
vendored
Normal file
53
Vendor/EmbedIO-3.5.2/Files/FileRequestHandler.cs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handler callbacks for <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="FileRequestHandlerCallback"/>
|
||||
public static class FileRequestHandler
|
||||
{
|
||||
#pragma warning disable CA1801 // Unused parameters - Must respect FileRequestHandlerCallback signature.
|
||||
/// <summary>
|
||||
/// <para>Unconditionally passes a request down the module chain.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws an exception instead.</returns>
|
||||
public static Task PassThrough(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw RequestHandler.PassThrough();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>403 Unauthorized</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowUnauthorized(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.Unauthorized();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>404 Not Found</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowNotFound(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.NotFound();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Unconditionally sends a <c>405 Method Not Allowed</c> response.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>This method never returns; it throws a <see cref="HttpException"/> instead.</returns>
|
||||
public static Task ThrowMethodNotAllowed(IHttpContext context, MappedResourceInfo? info)
|
||||
=> throw HttpException.MethodNotAllowed();
|
||||
#pragma warning restore CA1801
|
||||
}
|
||||
}
|
||||
13
Vendor/EmbedIO-3.5.2/Files/FileRequestHandlerCallback.cs
vendored
Normal file
13
Vendor/EmbedIO-3.5.2/Files/FileRequestHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to handle a request in <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext"/> interface representing the context of the request.</param>
|
||||
/// <param name="info">If the requested path has been successfully mapped to a resource (file or directory), the result of the mapping;
|
||||
/// otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
public delegate Task FileRequestHandlerCallback(IHttpContext context, MappedResourceInfo? info);
|
||||
}
|
||||
202
Vendor/EmbedIO-3.5.2/Files/FileSystemProvider.cs
vendored
Normal file
202
Vendor/EmbedIO-3.5.2/Files/FileSystemProvider.cs
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the local file system to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class FileSystemProvider : IDisposable, IFileProvider
|
||||
{
|
||||
private readonly FileSystemWatcher? _watcher;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileSystemProvider"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// OSX doesn't support <see cref="FileSystemWatcher" />, the parameter <paramref name="isImmutable" /> will be always <see langword="true"/>.
|
||||
/// </remarks>
|
||||
/// <param name="fileSystemPath">The file system path.</param>
|
||||
/// <param name="isImmutable"><see langword="true"/> if files and directories in
|
||||
/// <paramref name="fileSystemPath"/> are not expected to change during a web server's
|
||||
/// lifetime; <see langword="false"/> otherwise.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="fileSystemPath"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="fileSystemPath"/> is not a valid local path.</exception>
|
||||
/// <seealso cref="Validate.LocalPath"/>
|
||||
public FileSystemProvider(string fileSystemPath, bool isImmutable)
|
||||
{
|
||||
FileSystemPath = Validate.LocalPath(nameof(fileSystemPath), fileSystemPath, true);
|
||||
IsImmutable = isImmutable || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
try
|
||||
{
|
||||
if (!IsImmutable)
|
||||
_watcher = new FileSystemWatcher(FileSystemPath);
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
IsImmutable = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="FileSystemProvider"/> class.
|
||||
/// </summary>
|
||||
~FileSystemProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string>? ResourceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system path from which files are retrieved.
|
||||
/// </summary>
|
||||
public string FileSystemPath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.Changed += Watcher_ChangedOrDeleted;
|
||||
_watcher.Deleted += Watcher_ChangedOrDeleted;
|
||||
_watcher.Renamed += Watcher_Renamed;
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
urlPath = urlPath.Substring(1); // Drop the initial slash
|
||||
string localPath;
|
||||
|
||||
// Disable CA1031 as there's little we can do if IsPathRooted or GetFullPath fails.
|
||||
#pragma warning disable CA1031
|
||||
try
|
||||
{
|
||||
// Unescape the url before continue
|
||||
urlPath = Uri.UnescapeDataString(urlPath);
|
||||
|
||||
// Bail out early if the path is a rooted path,
|
||||
// as Path.Combine would ignore our base path.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine
|
||||
// (particularly the Remarks section).
|
||||
//
|
||||
// Under Windows, a relative URL path may be a full filesystem path
|
||||
// (e.g. "D:\foo\bar" or "\\192.168.0.1\Shared\MyDocuments\BankAccounts.docx").
|
||||
// Under Unix-like operating systems we have no such problems, as relativeUrlPath
|
||||
// can never start with a slash; however, loading one more class from Swan
|
||||
// just to check the OS type would probably outweigh calling IsPathRooted.
|
||||
if (Path.IsPathRooted(urlPath))
|
||||
return null;
|
||||
|
||||
// Convert the relative URL path to a relative filesystem path
|
||||
// (practically a no-op under Unix-like operating systems)
|
||||
// and combine it with our base local path to obtain a full path.
|
||||
localPath = Path.Combine(FileSystemPath, urlPath.Replace('/', Path.DirectorySeparatorChar));
|
||||
|
||||
// Use GetFullPath as an additional safety check
|
||||
// for relative paths that contain a rooted path
|
||||
// (e.g. "valid/path/C:\Windows\System.ini")
|
||||
localPath = Path.GetFullPath(localPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Both IsPathRooted and GetFullPath throw exceptions
|
||||
// if a path contains invalid characters or is otherwise invalid;
|
||||
// bail out in this case too, as the path would not exist on disk anyway.
|
||||
return null;
|
||||
}
|
||||
#pragma warning restore CA1031
|
||||
|
||||
// As a final precaution, check that the resulting local path
|
||||
// is inside the folder intended to be served.
|
||||
if (!localPath.StartsWith(FileSystemPath, StringComparison.Ordinal))
|
||||
return null;
|
||||
|
||||
if (File.Exists(localPath))
|
||||
return GetMappedFileInfo(mimeTypeProvider, localPath);
|
||||
|
||||
if (Directory.Exists(localPath))
|
||||
return GetMappedDirectoryInfo(localPath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> new DirectoryInfo(path).EnumerateFileSystemInfos()
|
||||
.Select(fsi => GetMappedResourceInfo(mimeTypeProvider, fsi));
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
ResourceChanged = null; // Release references to listeners
|
||||
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
_watcher.Changed -= Watcher_ChangedOrDeleted;
|
||||
_watcher.Deleted -= Watcher_ChangedOrDeleted;
|
||||
_watcher.Renamed -= Watcher_Renamed;
|
||||
|
||||
if (disposing)
|
||||
_watcher.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static MappedResourceInfo GetMappedFileInfo(IMimeTypeProvider mimeTypeProvider, string localPath)
|
||||
=> GetMappedFileInfo(mimeTypeProvider, new FileInfo(localPath));
|
||||
|
||||
private static MappedResourceInfo GetMappedFileInfo(IMimeTypeProvider mimeTypeProvider, FileInfo info)
|
||||
=> MappedResourceInfo.ForFile(
|
||||
info.FullName,
|
||||
info.Name,
|
||||
info.LastWriteTimeUtc,
|
||||
info.Length,
|
||||
mimeTypeProvider.GetMimeType(info.Extension));
|
||||
|
||||
private static MappedResourceInfo GetMappedDirectoryInfo(string localPath)
|
||||
=> GetMappedDirectoryInfo(new DirectoryInfo(localPath));
|
||||
|
||||
private static MappedResourceInfo GetMappedDirectoryInfo(DirectoryInfo info)
|
||||
=> MappedResourceInfo.ForDirectory(info.FullName, info.Name, info.LastWriteTimeUtc);
|
||||
|
||||
private static MappedResourceInfo GetMappedResourceInfo(IMimeTypeProvider mimeTypeProvider, FileSystemInfo info)
|
||||
=> info is DirectoryInfo directoryInfo
|
||||
? GetMappedDirectoryInfo(directoryInfo)
|
||||
: GetMappedFileInfo(mimeTypeProvider, (FileInfo) info);
|
||||
|
||||
private void Watcher_ChangedOrDeleted(object sender, FileSystemEventArgs e)
|
||||
=> ResourceChanged?.Invoke(e.FullPath);
|
||||
|
||||
private void Watcher_Renamed(object sender, RenamedEventArgs e)
|
||||
=> ResourceChanged?.Invoke(e.OldFullPath);
|
||||
}
|
||||
}
|
||||
35
Vendor/EmbedIO-3.5.2/Files/IDirectoryLister.cs
vendored
Normal file
35
Vendor/EmbedIO-3.5.2/Files/IDirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can render a directory listing to a stream.
|
||||
/// </summary>
|
||||
public interface IDirectoryLister
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type of generated directory listings.
|
||||
/// </summary>
|
||||
string ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously generate a directory listing.
|
||||
/// </summary>
|
||||
/// <param name="info">A <see cref="MappedResourceInfo"/> containing information about
|
||||
/// the directory which is to be listed.</param>
|
||||
/// <param name="absoluteUrlPath">The absolute URL path that was mapped to <paramref name="info"/>.</param>
|
||||
/// <param name="entries">An enumeration of the entries in the directory represented by <paramref name="info"/>.</param>
|
||||
/// <param name="stream">A <see cref="Stream"/> to which the directory listing must be written.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
Task ListDirectoryAsync(
|
||||
MappedResourceInfo info,
|
||||
string absoluteUrlPath,
|
||||
IEnumerable<MappedResourceInfo> entries,
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
61
Vendor/EmbedIO-3.5.2/Files/IFileProvider.cs
vendored
Normal file
61
Vendor/EmbedIO-3.5.2/Files/IFileProvider.cs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can provide files and/or directories to be served by a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
public interface IFileProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Occurs when a file or directory provided by this instance is modified or removed.</para>
|
||||
/// <para>The event's parameter is the provider-specific path of the resource that changed.</para>
|
||||
/// </summary>
|
||||
event Action<string> ResourceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the files and directories provided by this instance
|
||||
/// will never change.
|
||||
/// </summary>
|
||||
bool IsImmutable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Signals a file provider that the web server is starting.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the web server.</param>
|
||||
void Start(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a URL path to a provider-specific path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path.</param>
|
||||
/// <param name="mimeTypeProvider">An <see cref="IMimeTypeProvider"/> interface to use
|
||||
/// for determining the MIME type of a file.</param>
|
||||
/// <returns>A provider-specific path identifying a file or directory,
|
||||
/// or <see langword="null"/> if this instance cannot provide a resource associated
|
||||
/// to <paramref name="urlPath"/>.</returns>
|
||||
MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file for reading.
|
||||
/// </summary>
|
||||
/// <param name="path">The provider-specific path for the file.</param>
|
||||
/// <returns>
|
||||
/// <para>A readable <see cref="Stream"/> of the file's contents.</para>
|
||||
/// </returns>
|
||||
Stream OpenFile(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumeration of the entries of a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The provider-specific path for the directory.</param>
|
||||
/// <param name="mimeTypeProvider">An <see cref="IMimeTypeProvider"/> interface to use
|
||||
/// for determining the MIME type of files.</param>
|
||||
/// <returns>An enumeration of <see cref="MappedResourceInfo"/> objects identifying the entries
|
||||
/// in the directory identified by <paramref name="path"/>.</returns>
|
||||
IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider);
|
||||
}
|
||||
}
|
||||
12
Vendor/EmbedIO-3.5.2/Files/Internal/Base64Utility.cs
vendored
Normal file
12
Vendor/EmbedIO-3.5.2/Files/Internal/Base64Utility.cs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class Base64Utility
|
||||
{
|
||||
// long is 8 bytes
|
||||
// base64 of 8 bytes is 12 chars, but the last one is padding
|
||||
public static string LongToBase64(long value)
|
||||
=> Convert.ToBase64String(BitConverter.GetBytes(value)).Substring(0, 11);
|
||||
}
|
||||
}
|
||||
28
Vendor/EmbedIO-3.5.2/Files/Internal/EntityTag.cs
vendored
Normal file
28
Vendor/EmbedIO-3.5.2/Files/Internal/EntityTag.cs
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class EntityTag
|
||||
{
|
||||
public static string Compute(DateTime lastModifiedUtc, long length, CompressionMethod compressionMethod)
|
||||
{
|
||||
var sb = new StringBuilder()
|
||||
.Append('"')
|
||||
.Append(Base64Utility.LongToBase64(lastModifiedUtc.Ticks))
|
||||
.Append(Base64Utility.LongToBase64(length));
|
||||
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
sb.Append('-').Append(CompressionMethodNames.Deflate);
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
sb.Append('-').Append(CompressionMethodNames.Gzip);
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.Append('"').ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Vendor/EmbedIO-3.5.2/Files/Internal/FileCacheItem.cs
vendored
Normal file
164
Vendor/EmbedIO-3.5.2/Files/Internal/FileCacheItem.cs
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using EmbedIO.Internal;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal sealed class FileCacheItem
|
||||
{
|
||||
#pragma warning disable SA1401 // Field should be private - performance is a stronger concern here.
|
||||
// These fields create a sort of linked list of items
|
||||
// inside the cache's dictionary.
|
||||
// Their purpose is to keep track of items
|
||||
// in order from least to most recently used.
|
||||
internal string? PreviousKey;
|
||||
internal string? NextKey;
|
||||
internal long LastUsedAt;
|
||||
#pragma warning restore SA1401
|
||||
|
||||
// Size of a pointer in bytes
|
||||
private static readonly long SizeOfPointer = Environment.Is64BitProcess ? 8 : 4;
|
||||
|
||||
// Size of a WeakReference<T> in bytes
|
||||
private static readonly long SizeOfWeakReference = Environment.Is64BitProcess ? 16 : 32;
|
||||
|
||||
// Educated guess about the size of an Item in memory (see comments on constructor).
|
||||
// 3 * SizeOfPointer + total size of fields, rounded up to a multiple of 16.
|
||||
//
|
||||
// Computed as follows:
|
||||
//
|
||||
// * for 32-bit:
|
||||
// - initialize count to 3 (number of "hidden" pointers that compose the object header)
|
||||
// - for every field / auto property, in order of declaration:
|
||||
// - increment count by 1 for reference types, 2 for long and DateTime
|
||||
// (as of time of writing there are no fields of other types here)
|
||||
// - increment again by 1 if this field "weighs" 1 and the next one "weighs" 2
|
||||
// (padding for field alignment)
|
||||
// - multiply count by 4 (size of a pointer)
|
||||
// - if the result is not a multiple of 16, round it up to next multiple of 16
|
||||
//
|
||||
// * for 64-bit:
|
||||
// - initialize count to 3 (number of "hidden" pointers that compose the object header)
|
||||
// - for every field / auto property, in order of declaration, increment count by 1
|
||||
// (at the time of writing there are no fields here that need padding on 64-bit)
|
||||
// - multiply count by 8 (size of a pointer)
|
||||
// - if the result is not a multiple of 16, round it up to next multiple of 16
|
||||
private static readonly long SizeOfItem = Environment.Is64BitProcess ? 96 : 128;
|
||||
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
// Used to update total size of section.
|
||||
// Weak reference avoids circularity.
|
||||
private readonly WeakReference<FileCache.Section> _section;
|
||||
|
||||
// There are only 3 possible compression methods,
|
||||
// hence a dictionary (or two dictionaries) would be overkill.
|
||||
private byte[]? _uncompressedContent;
|
||||
private byte[]? _gzippedContent;
|
||||
private byte[]? _deflatedContent;
|
||||
|
||||
internal FileCacheItem(FileCache.Section section, DateTime lastModifiedUtc, long length)
|
||||
{
|
||||
_section = new WeakReference<FileCache.Section>(section);
|
||||
|
||||
LastModifiedUtc = lastModifiedUtc;
|
||||
Length = length;
|
||||
|
||||
// There is no way to know the actual size of an object at runtime.
|
||||
// This method makes some educated guesses, based on the following
|
||||
// article (among others):
|
||||
// https://codingsight.com/precise-computation-of-clr-object-size/
|
||||
// PreviousKey and NextKey values aren't counted in
|
||||
// because they are just references to existing strings.
|
||||
SizeInCache = SizeOfItem + SizeOfWeakReference;
|
||||
}
|
||||
|
||||
public DateTime LastModifiedUtc { get; }
|
||||
|
||||
public long Length { get; }
|
||||
|
||||
// This is the (approximate) in-memory size of this object.
|
||||
// It is NOT the length of the cache resource!
|
||||
public long SizeInCache { get; private set; }
|
||||
|
||||
public byte[]? GetContent(CompressionMethod compressionMethod)
|
||||
{
|
||||
// If there are both entity tag and content, use them.
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
if (_deflatedContent != null) return _deflatedContent;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
if (_gzippedContent != null) return _gzippedContent;
|
||||
break;
|
||||
default:
|
||||
if (_uncompressedContent != null) return _uncompressedContent;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to convert existing content, if any.
|
||||
byte[]? content;
|
||||
if (_uncompressedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_uncompressedContent, CompressionMethod.None, compressionMethod);
|
||||
}
|
||||
else if (_gzippedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_gzippedContent, CompressionMethod.Gzip, compressionMethod);
|
||||
}
|
||||
else if (_deflatedContent != null)
|
||||
{
|
||||
content = CompressionUtility.ConvertCompression(_deflatedContent, CompressionMethod.Deflate, compressionMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No content whatsoever.
|
||||
return null;
|
||||
}
|
||||
|
||||
return SetContent(compressionMethod, content);
|
||||
}
|
||||
|
||||
public byte[]? SetContent(CompressionMethod compressionMethod, byte[]? content)
|
||||
{
|
||||
// This is the bare minimum locking we need
|
||||
// to ensure we don't mess sizes up.
|
||||
byte[]? oldContent;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
oldContent = _deflatedContent;
|
||||
_deflatedContent = content;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
oldContent = _gzippedContent;
|
||||
_gzippedContent = content;
|
||||
break;
|
||||
default:
|
||||
oldContent = _uncompressedContent;
|
||||
_uncompressedContent = content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var sizeDelta = GetSizeOf(content) - GetSizeOf(oldContent);
|
||||
SizeInCache += sizeDelta;
|
||||
if (_section.TryGetTarget(out var section))
|
||||
section.UpdateTotalSize(sizeDelta);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Round up to a multiple of 16
|
||||
private static long RoundUpTo16(long n)
|
||||
{
|
||||
var remainder = n % 16;
|
||||
return remainder > 0 ? n + (16 - remainder) : n;
|
||||
}
|
||||
|
||||
// The size of a byte array is 3 * SizeOfPointer + 1 (size of byte) * Length
|
||||
private static long GetSizeOf(byte[]? arr) => arr == null ? 0 : RoundUpTo16(3 * SizeOfPointer) + arr.Length;
|
||||
}
|
||||
}
|
||||
73
Vendor/EmbedIO-3.5.2/Files/Internal/HtmlDirectoryLister.cs
vendored
Normal file
73
Vendor/EmbedIO-3.5.2/Files/Internal/HtmlDirectoryLister.cs
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal class HtmlDirectoryLister : IDirectoryLister
|
||||
{
|
||||
private static readonly Lazy<IDirectoryLister> LazyInstance = new Lazy<IDirectoryLister>(() => new HtmlDirectoryLister());
|
||||
|
||||
private HtmlDirectoryLister()
|
||||
{
|
||||
}
|
||||
|
||||
public static IDirectoryLister Instance => LazyInstance.Value;
|
||||
|
||||
public string ContentType { get; } = MimeType.Html + "; encoding=" + WebServer.DefaultEncoding.WebName;
|
||||
|
||||
public async Task ListDirectoryAsync(
|
||||
MappedResourceInfo info,
|
||||
string absoluteUrlPath,
|
||||
IEnumerable<MappedResourceInfo> entries,
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
const int MaxEntryLength = 50;
|
||||
const int SizeIndent = -20; // Negative for right alignment
|
||||
|
||||
if (!info.IsDirectory)
|
||||
throw SelfCheck.Failure($"{nameof(HtmlDirectoryLister)}.{nameof(ListDirectoryAsync)} invoked with a file, not a directory.");
|
||||
|
||||
var encodedPath = WebUtility.HtmlEncode(absoluteUrlPath);
|
||||
using var text = new StreamWriter(stream, WebServer.DefaultEncoding);
|
||||
text.Write("<html><head><title>Index of ");
|
||||
text.Write(encodedPath);
|
||||
text.Write("</title></head><body><h1>Index of ");
|
||||
text.Write(encodedPath);
|
||||
text.Write("</h1><hr/><pre>");
|
||||
|
||||
if (encodedPath.Length > 1)
|
||||
text.Write("<a href='../'>../</a>\n");
|
||||
|
||||
entries = entries.ToArray();
|
||||
|
||||
foreach (var directory in entries.Where(m => m.IsDirectory).OrderBy(e => e.Name))
|
||||
{
|
||||
text.Write($"<a href=\"{Uri.EscapeDataString(directory.Name)}\">{WebUtility.HtmlEncode(directory.Name)}</a>");
|
||||
text.Write(new string(' ', Math.Max(1, MaxEntryLength - directory.Name.Length + 1)));
|
||||
text.Write(HttpDate.Format(directory.LastModifiedUtc));
|
||||
text.Write('\n');
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
foreach (var file in entries.Where(m => m.IsFile).OrderBy(e => e.Name))
|
||||
{
|
||||
text.Write($"<a href=\"{Uri.EscapeDataString(file.Name)}\">{WebUtility.HtmlEncode(file.Name)}</a>");
|
||||
text.Write(new string(' ', Math.Max(1, MaxEntryLength - file.Name.Length + 1)));
|
||||
text.Write(HttpDate.Format(file.LastModifiedUtc));
|
||||
text.Write($" {file.Length.ToString("#,###", CultureInfo.InvariantCulture),SizeIndent}\n");
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
text.Write("</pre><hr/></body></html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Vendor/EmbedIO-3.5.2/Files/Internal/MappedResourceInfoExtensions.cs
vendored
Normal file
8
Vendor/EmbedIO-3.5.2/Files/Internal/MappedResourceInfoExtensions.cs
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace EmbedIO.Files.Internal
|
||||
{
|
||||
internal static class MappedResourceInfoExtensions
|
||||
{
|
||||
public static string GetEntityTag(this MappedResourceInfo @this, CompressionMethod compressionMethod)
|
||||
=> EntityTag.Compute(@this.LastModifiedUtc, @this.Length, compressionMethod);
|
||||
}
|
||||
}
|
||||
80
Vendor/EmbedIO-3.5.2/Files/MappedResourceInfo.cs
vendored
Normal file
80
Vendor/EmbedIO-3.5.2/Files/MappedResourceInfo.cs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about a resource served via an <see cref="IFileProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class MappedResourceInfo
|
||||
{
|
||||
private MappedResourceInfo(string path, string name, DateTime lastModifiedUtc, long length, string? contentType)
|
||||
{
|
||||
Path = path;
|
||||
Name = name;
|
||||
LastModifiedUtc = lastModifiedUtc;
|
||||
Length = length;
|
||||
ContentType = contentType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance represents a directory.
|
||||
/// </summary>
|
||||
public bool IsDirectory => ContentType == null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance represents a file.
|
||||
/// </summary>
|
||||
public bool IsFile => ContentType != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique, provider-specific path for the resource.
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the resource, as it would appear in a directory listing.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC date and time of the last modification made to the resource.
|
||||
/// </summary>
|
||||
public DateTime LastModifiedUtc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="false"/>, gets the length of the file, expressed in bytes.</para>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="true"/>, this property is always zero.</para>
|
||||
/// </summary>
|
||||
public long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="false"/>, gets a MIME type describing the kind of contents of the file.</para>
|
||||
/// <para>If <see cref="IsDirectory"/> is <see langword="true"/>, this property is always <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
public string? ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new instance of the <see cref="MappedResourceInfo"/> class,
|
||||
/// representing a file.
|
||||
/// </summary>
|
||||
/// <param name="path">A unique, provider-specific path for the file.</param>
|
||||
/// <param name="name">The name of the file, as it would appear in a directory listing.</param>
|
||||
/// <param name="lastModifiedUtc">The UTC date and time of the last modification made to the file.</param>
|
||||
/// <param name="size">The length of the file, expressed in bytes.</param>
|
||||
/// <param name="contentType">A MIME type describing the kind of contents of the file.</param>
|
||||
/// <returns>A newly-constructed instance of <see cref="MappedResourceInfo"/>.</returns>
|
||||
public static MappedResourceInfo ForFile(string path, string name, DateTime lastModifiedUtc, long size, string contentType)
|
||||
=> new MappedResourceInfo(path, name, lastModifiedUtc, size, contentType ?? MimeType.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new instance of the <see cref="MappedResourceInfo"/> class,
|
||||
/// representing a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">A unique, provider-specific path for the directory.</param>
|
||||
/// <param name="name">The name of the directory, as it would appear in a directory listing.</param>
|
||||
/// <param name="lastModifiedUtc">The UTC date and time of the last modification made to the directory.</param>
|
||||
/// <returns>A newly-constructed instance of <see cref="MappedResourceInfo"/>.</returns>
|
||||
public static MappedResourceInfo ForDirectory(string path, string name, DateTime lastModifiedUtc)
|
||||
=> new MappedResourceInfo(path, name, lastModifiedUtc, 0, null);
|
||||
}
|
||||
}
|
||||
94
Vendor/EmbedIO-3.5.2/Files/ResourceFileProvider.cs
vendored
Normal file
94
Vendor/EmbedIO-3.5.2/Files/ResourceFileProvider.cs
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to embedded resources to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class ResourceFileProvider : IFileProvider
|
||||
{
|
||||
private readonly DateTime _fileTime = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly where served files are contained as embedded resources.</param>
|
||||
/// <param name="pathPrefix">A string to prepend to provider-specific paths
|
||||
/// to form the name of a manifest resource in <paramref name="assembly"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly"/> is <see langword="null"/>.</exception>
|
||||
public ResourceFileProvider(Assembly assembly, string pathPrefix)
|
||||
{
|
||||
Assembly = Validate.NotNull(nameof(assembly), assembly);
|
||||
PathPrefix = pathPrefix ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string> ResourceChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly where served files are contained as embedded resources.
|
||||
/// </summary>
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string that is prepended to provider-specific paths to form the name of a manifest resource in <see cref="Assembly"/>.
|
||||
/// </summary>
|
||||
public string PathPrefix { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
var resourceName = PathPrefix + urlPath.Replace('/', '.');
|
||||
|
||||
long size;
|
||||
try
|
||||
{
|
||||
using var stream = Assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null || stream == Stream.Null)
|
||||
return null;
|
||||
|
||||
size = stream.Length;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var lastSlashPos = urlPath.LastIndexOf('/');
|
||||
var name = urlPath.Substring(lastSlashPos + 1);
|
||||
|
||||
return MappedResourceInfo.ForFile(
|
||||
resourceName,
|
||||
name,
|
||||
_fileTime,
|
||||
size,
|
||||
mimeTypeProvider.GetMimeType(Path.GetExtension(name)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path) => Assembly.GetManifestResourceStream(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> Enumerable.Empty<MappedResourceInfo>();
|
||||
}
|
||||
}
|
||||
110
Vendor/EmbedIO-3.5.2/Files/ZipFileProvider.cs
vendored
Normal file
110
Vendor/EmbedIO-3.5.2/Files/ZipFileProvider.cs
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
using EmbedIO.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Files
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to files contained in a <c>.zip</c> file to a <see cref="FileModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IFileProvider" />
|
||||
public class ZipFileProvider : IDisposable, IFileProvider
|
||||
{
|
||||
private readonly ZipArchive _zipArchive;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">The zip file path.</param>
|
||||
public ZipFileProvider(string zipFilePath)
|
||||
: this(new FileStream(Validate.LocalPath(nameof(zipFilePath), zipFilePath, true), FileMode.Open))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream that contains the archive.</param>
|
||||
/// <param name="leaveOpen"><see langword="true"/> to leave the stream open after the web server
|
||||
/// is disposed; otherwise, <see langword="false"/>.</param>
|
||||
public ZipFileProvider(Stream stream, bool leaveOpen = false)
|
||||
{
|
||||
_zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ZipFileProvider"/> class.
|
||||
/// </summary>
|
||||
~ZipFileProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string> ResourceChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImmutable => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MappedResourceInfo? MapUrlPath(string urlPath, IMimeTypeProvider mimeTypeProvider)
|
||||
{
|
||||
if (urlPath.Length == 1)
|
||||
return null;
|
||||
|
||||
urlPath = Uri.UnescapeDataString(urlPath);
|
||||
|
||||
var entry = _zipArchive.GetEntry(urlPath.Substring(1));
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
return MappedResourceInfo.ForFile(
|
||||
entry.FullName,
|
||||
entry.Name,
|
||||
entry.LastWriteTime.DateTime,
|
||||
entry.Length,
|
||||
mimeTypeProvider.GetMimeType(Path.GetExtension(entry.Name)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenFile(string path)
|
||||
=> _zipArchive.GetEntry(path)?.Open() ?? throw new FileNotFoundException($"\"{path}\" cannot be found in Zip archive.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MappedResourceInfo> GetDirectoryEntries(string path, IMimeTypeProvider mimeTypeProvider)
|
||||
=> Enumerable.Empty<MappedResourceInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_zipArchive.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Items.cs
vendored
Normal file
47
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Items.cs
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>Gets the item associated with the specified key.</summary>
|
||||
/// <typeparam name="T">The desired type of the item.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="key">The key whose value to get from the <see cref="IHttpContext.Items">Items</see> dictionary.</param>
|
||||
/// <param name="value">
|
||||
/// <para>When this method returns, the item associated with the specified key,
|
||||
/// if the key is found in <see cref="IHttpContext.Items">Items</see>
|
||||
/// and the associated value is of type <typeparamref name="T"/>;
|
||||
/// otherwise, the default value for <typeparamref name="T"/>.</para>
|
||||
/// <para>This parameter is passed uninitialized.</para>
|
||||
/// </param>
|
||||
/// <returns><see langword="true"/> if the item is found and is of type <typeparamref name="T"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
|
||||
public static bool TryGetItem<T>(this IHttpContext @this, object key, out T value)
|
||||
{
|
||||
if (@this.Items.TryGetValue(key, out var item) && item is T typedItem)
|
||||
{
|
||||
value = typedItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable CS8653 // value is non-nullable - We are returning false, so value is undefined.
|
||||
value = default;
|
||||
#pragma warning restore CS8653
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Gets the item associated with the specified key.</summary>
|
||||
/// <typeparam name="T">The desired type of the item.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="key">The key whose value to get from the <see cref="IHttpContext.Items">Items</see> dictionary.</param>
|
||||
/// <returns>The item associated with the specified key,
|
||||
/// if the key is found in <see cref="IHttpContext.Items">Items</see>
|
||||
/// and the associated value is of type <typeparamref name="T"/>;
|
||||
/// otherwise, the default value for <typeparamref name="T"/>.</returns>
|
||||
public static T GetItem<T>(this IHttpContext @this, object key)
|
||||
=> @this.Items.TryGetValue(key, out var item) && item is T typedItem ? typedItem : default;
|
||||
}
|
||||
}
|
||||
33
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Redirect.cs
vendored
Normal file
33
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Redirect.cs
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets a redirection status code and adds a <c>Location</c> header to the response.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="location">The URL to which the user agent should be redirected.</param>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="location"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="location"/> is not a valid relative or absolute URL.<see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="statusCode"/> is not a redirection (3xx) status code.</para>
|
||||
/// </exception>
|
||||
public static void Redirect(this IHttpContext @this, string location, int statusCode = (int)HttpStatusCode.Found)
|
||||
{
|
||||
location = Validate.Url(nameof(location), location, @this.Request.Url);
|
||||
|
||||
if (statusCode < 300 || statusCode > 399)
|
||||
throw new ArgumentException("Redirect status code is not valid.", nameof(statusCode));
|
||||
|
||||
@this.Response.SetEmptyResponse(statusCode);
|
||||
@this.Response.Headers[HttpHeaderNames.Location] = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Vendor/EmbedIO-3.5.2/HttpContextExtensions-RequestStream.cs
vendored
Normal file
61
Vendor/EmbedIO-3.5.2/HttpContextExtensions-RequestStream.cs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Wraps the request input stream and returns a <see cref="Stream"/> that can be used directly.</para>
|
||||
/// <para>Decompression of compressed request bodies is implemented if specified in the web server's options.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="Stream"/> that can be used to write response data.</para>
|
||||
/// <para>This stream MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenRequestText"/>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
public static Stream OpenRequestStream(this IHttpContext @this)
|
||||
{
|
||||
var stream = @this.Request.InputStream;
|
||||
|
||||
var encoding = @this.Request.Headers[HttpHeaderNames.ContentEncoding]?.Trim();
|
||||
switch (encoding)
|
||||
{
|
||||
case CompressionMethodNames.Gzip:
|
||||
if (@this.SupportCompressedRequests)
|
||||
return new GZipStream(stream, CompressionMode.Decompress);
|
||||
break;
|
||||
case CompressionMethodNames.Deflate:
|
||||
if (@this.SupportCompressedRequests)
|
||||
return new DeflateStream(stream, CompressionMode.Decompress);
|
||||
break;
|
||||
case CompressionMethodNames.None:
|
||||
case null:
|
||||
return stream;
|
||||
}
|
||||
|
||||
$"[{@this.Id}] Unsupported request content encoding \"{encoding}\", sending 400 Bad Request..."
|
||||
.Warn(nameof(OpenRequestStream));
|
||||
|
||||
throw HttpException.BadRequest($"Unsupported content encoding \"{encoding}\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Wraps the request input stream and returns a <see cref="TextReader" /> that can be used directly.</para>
|
||||
/// <para>Decompression of compressed request bodies is implemented if specified in the web server's options.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext" /> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="TextReader" /> that can be used to read the request body as text.</para>
|
||||
/// <para>This reader MUST be disposed when finished reading.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenRequestStream"/>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
public static TextReader OpenRequestText(this IHttpContext @this)
|
||||
=> new StreamReader(OpenRequestStream(@this), @this.Request.ContentEncoding);
|
||||
}
|
||||
}
|
||||
175
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Requests.cs
vendored
Normal file
175
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Requests.cs
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
private static readonly object FormDataKey = new object();
|
||||
private static readonly object QueryDataKey = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the request body as an array of <see langword="byte"/>s.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be an array of <see cref="byte"/>s containing the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<byte[]> GetRequestBodyAsByteArrayAsync(this IHttpContext @this)
|
||||
{
|
||||
using var buffer = new MemoryStream();
|
||||
using var stream = @this.OpenRequestStream();
|
||||
await stream.CopyToAsync(buffer, WebServer.StreamCopyBufferSize, @this.CancellationToken).ConfigureAwait(false);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously buffers the request body into a read-only <see cref="MemoryStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a read-only <see cref="MemoryStream"/> containing the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<MemoryStream> GetRequestBodyAsMemoryStreamAsync(this IHttpContext @this)
|
||||
=> new MemoryStream(
|
||||
await GetRequestBodyAsByteArrayAsync(@this).ConfigureAwait(false),
|
||||
false);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the request body as a string.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a <see langword="string"/> representation of the request body.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static async Task<string> GetRequestBodyAsStringAsync(this IHttpContext @this)
|
||||
{
|
||||
using var reader = @this.OpenRequestText();
|
||||
return await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously deserializes a request body, using the default request deserializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// request parsing methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The expected type of the deserialized data.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be the deserialized data.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static Task<TData> GetRequestDataAsync<TData>(this IHttpContext @this)
|
||||
=> RequestDeserializer.Default<TData>(@this);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deserializes a request body, using the specified request deserializer.
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The expected type of the deserialized data.</typeparam>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="deserializer">A <see cref="RequestDeserializerCallback{TData}"/> used to deserialize the request body.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be the deserialized data.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="deserializer"/> is <see langword="null"/>.</exception>
|
||||
public static Task<TData> GetRequestDataAsync<TData>(this IHttpContext @this,RequestDeserializerCallback<TData> deserializer)
|
||||
=> Validate.NotNull(nameof(deserializer), deserializer)(@this);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously parses a request body in <c>application/x-www-form-urlencoded</c> format.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A <see cref="Task{TResult}">Task</see>, representing the ongoing operation,
|
||||
/// whose result will be a read-only <see cref="NameValueCollection"/>of form field names and values.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>This method may safely be called more than once for the same <see cref="IHttpContext"/>:
|
||||
/// it will return the same collection instead of trying to parse the request body again.</para>
|
||||
/// </remarks>
|
||||
public static async Task<NameValueCollection> GetRequestFormDataAsync(this IHttpContext @this)
|
||||
{
|
||||
if (!@this.Items.TryGetValue(FormDataKey, out var previousResult))
|
||||
{
|
||||
NameValueCollection result;
|
||||
try
|
||||
{
|
||||
using var reader = @this.OpenRequestText();
|
||||
result = UrlEncodedDataParser.Parse(await reader.ReadToEndAsync().ConfigureAwait(false), false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@this.Items[FormDataKey] = e;
|
||||
throw;
|
||||
}
|
||||
|
||||
@this.Items[FormDataKey] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (previousResult)
|
||||
{
|
||||
case NameValueCollection collection:
|
||||
return collection;
|
||||
|
||||
case Exception exception:
|
||||
throw exception.RethrowPreservingStackTrace();
|
||||
|
||||
case null:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is null.");
|
||||
|
||||
default:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestFormDataAsync)} is of unexpected type {previousResult.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a request URL query. Note that this is different from getting the <see cref="IHttpRequest.QueryString"/> property,
|
||||
/// in that fields without an equal sign are treated as if they have an empty value, instead of their keys being grouped
|
||||
/// as values of the <c>null</c> key.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <returns>A read-only <see cref="NameValueCollection"/>.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>This method may safely be called more than once for the same <see cref="IHttpContext"/>:
|
||||
/// it will return the same collection instead of trying to parse the request body again.</para>
|
||||
/// </remarks>
|
||||
public static NameValueCollection GetRequestQueryData(this IHttpContext @this)
|
||||
{
|
||||
if (!@this.Items.TryGetValue(QueryDataKey, out var previousResult))
|
||||
{
|
||||
NameValueCollection result;
|
||||
try
|
||||
{
|
||||
result = UrlEncodedDataParser.Parse(@this.Request.Url.Query, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@this.Items[FormDataKey] = e;
|
||||
throw;
|
||||
}
|
||||
|
||||
@this.Items[FormDataKey] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (previousResult)
|
||||
{
|
||||
case NameValueCollection collection:
|
||||
return collection;
|
||||
|
||||
case Exception exception:
|
||||
throw exception.RethrowPreservingStackTrace();
|
||||
|
||||
case null:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is null.");
|
||||
|
||||
default:
|
||||
throw SelfCheck.Failure($"Previous result of {nameof(HttpContextExtensions)}.{nameof(GetRequestQueryData)} is of unexpected type {previousResult.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Vendor/EmbedIO-3.5.2/HttpContextExtensions-ResponseStream.cs
vendored
Normal file
68
Vendor/EmbedIO-3.5.2/HttpContextExtensions-ResponseStream.cs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Wraps the response output stream and returns a <see cref="Stream"/> that can be used directly.</para>
|
||||
/// <para>Optional buffering is applied, so that the response may be sent as one instead of using chunked transfer.</para>
|
||||
/// <para>Proactive negotiation is performed to select the best compression method supported by the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> on which this method is called.</param>
|
||||
/// <param name="buffered">If set to <see langword="true"/>, sent data is collected
|
||||
/// in a <see cref="MemoryStream"/> and sent all at once when the returned <see cref="Stream"/>
|
||||
/// is disposed; if set to <see langword="false"/> (the default), chunked transfer will be used.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="Stream"/> that can be used to write response data.</para>
|
||||
/// <para>This stream MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenResponseText"/>
|
||||
public static Stream OpenResponseStream(this IHttpContext @this, bool buffered = false, bool preferCompression = true)
|
||||
{
|
||||
// No need to check whether negotiation is successful;
|
||||
// the returned callback will throw HttpNotAcceptableException if it was not.
|
||||
_ = @this.Request.TryNegotiateContentEncoding(preferCompression, out var compressionMethod, out var prepareResponse);
|
||||
prepareResponse(@this.Response);
|
||||
var stream = buffered ? new BufferingResponseStream(@this.Response) : @this.Response.OutputStream;
|
||||
|
||||
return compressionMethod switch {
|
||||
CompressionMethod.Gzip => new GZipStream(stream, CompressionMode.Compress),
|
||||
CompressionMethod.Deflate => new DeflateStream(stream, CompressionMode.Compress),
|
||||
_ => stream
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Wraps the response output stream and returns a <see cref="TextWriter" /> that can be used directly.</para>
|
||||
/// <para>Optional buffering is applied, so that the response may be sent as one instead of using chunked transfer.</para>
|
||||
/// <para>Proactive negotiation is performed to select the best compression method supported by the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext" /> on which this method is called.</param>
|
||||
/// <param name="encoding">
|
||||
/// <para>The <see cref="Encoding"/> to use to convert text to data bytes.</para>
|
||||
/// <para>If <see langword="null"/> (the default), <see cref="WebServer.DefaultEncoding"/> (UTF-8 without a byte order mark) is used.</para>
|
||||
/// </param>
|
||||
/// <param name="buffered">If set to <see langword="true" />, sent data is collected
|
||||
/// in a <see cref="MemoryStream" /> and sent all at once when the returned <see cref="Stream" />
|
||||
/// is disposed; if set to <see langword="false" /> (the default), chunked transfer will be used.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> if sending compressed data is preferred over
|
||||
/// sending non-compressed data; otherwise, <see langword="false"/>.</param>
|
||||
/// <returns>
|
||||
/// <para>A <see cref="TextWriter" /> that can be used to write response data.</para>
|
||||
/// <para>This writer MUST be disposed when finished writing.</para>
|
||||
/// </returns>
|
||||
/// <seealso cref="OpenResponseStream"/>
|
||||
public static TextWriter OpenResponseText(this IHttpContext @this, Encoding? encoding = null, bool buffered = false, bool preferCompression = true)
|
||||
{
|
||||
encoding ??= WebServer.DefaultEncoding;
|
||||
@this.Response.ContentEncoding = encoding;
|
||||
return new StreamWriter(OpenResponseStream(@this, buffered, preferCompression), encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Responses.cs
vendored
Normal file
124
Vendor/EmbedIO-3.5.2/HttpContextExtensions-Responses.cs
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpContextExtensions
|
||||
{
|
||||
private const string StandardHtmlHeaderFormat = "<html><head><meta charset=\"{2}\"><title>{0} - {1}</title></head><body><h1>{0} - {1}</h1>";
|
||||
private const string StandardHtmlFooter = "</body></html>";
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a string as response.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <param name="content">The response content.</param>
|
||||
/// <param name="contentType">The MIME type of the content. If <see langword="null"/>, the content type will not be set.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/> to use.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="content"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="encoding"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
public static async Task SendStringAsync(
|
||||
this IHttpContext @this,
|
||||
string content,
|
||||
string contentType,
|
||||
Encoding encoding)
|
||||
{
|
||||
content = Validate.NotNull(nameof(content), content);
|
||||
encoding = Validate.NotNull(nameof(encoding), encoding);
|
||||
|
||||
if (contentType != null)
|
||||
{
|
||||
@this.Response.ContentType = contentType;
|
||||
@this.Response.ContentEncoding = encoding;
|
||||
}
|
||||
|
||||
using var text = @this.OpenResponseText(encoding);
|
||||
await text.WriteAsync(content).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a standard HTML response for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
/// <seealso cref="SendStandardHtmlAsync(IHttpContext,int,Action{TextWriter})"/>
|
||||
public static Task SendStandardHtmlAsync(this IHttpContext @this, int statusCode)
|
||||
=> SendStandardHtmlAsync(@this, statusCode, null);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sends a standard HTML response for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <param name="writeAdditionalHtml">A callback function that may write additional HTML code
|
||||
/// to a <see cref="TextWriter"/> representing the response output.
|
||||
/// If not <see langword="null"/>, the callback is called immediately before closing the HTML <c>body</c> tag.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
/// <seealso cref="SendStandardHtmlAsync(IHttpContext,int)"/>
|
||||
public static Task SendStandardHtmlAsync(
|
||||
this IHttpContext @this,
|
||||
int statusCode,
|
||||
Action<TextWriter>? writeAdditionalHtml)
|
||||
{
|
||||
if (!HttpStatusDescription.TryGet(statusCode, out var statusDescription))
|
||||
throw new ArgumentException("Status code has no standard description.", nameof(statusCode));
|
||||
|
||||
@this.Response.StatusCode = statusCode;
|
||||
@this.Response.StatusDescription = statusDescription;
|
||||
@this.Response.ContentType = MimeType.Html;
|
||||
@this.Response.ContentEncoding = WebServer.DefaultEncoding;
|
||||
using (var text = @this.OpenResponseText(WebServer.DefaultEncoding))
|
||||
{
|
||||
text.Write(StandardHtmlHeaderFormat, statusCode, statusDescription, WebServer.DefaultEncoding.WebName);
|
||||
writeAdditionalHtml?.Invoke(text);
|
||||
text.Write(StandardHtmlFooter);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously sends serialized data as a response, using the default response serializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// response methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="data">The data to serialize.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="SendDataAsync(IHttpContext,ResponseSerializerCallback,object)"/>
|
||||
/// <seealso cref="ResponseSerializer.Default"/>
|
||||
public static Task SendDataAsync(this IHttpContext @this, object data)
|
||||
=> ResponseSerializer.Default(@this, data);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously sends serialized data as a response, using the specified response serializer.</para>
|
||||
/// <para>As of EmbedIO version 3.0, the default response serializer has the same behavior of JSON
|
||||
/// response methods of version 2.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <param name="serializer">A <see cref="ResponseSerializerCallback"/> used to prepare the response.</param>
|
||||
/// <param name="data">The data to serialize.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializer"/> is <see langword="null"/>.</exception>
|
||||
/// <seealso cref="SendDataAsync(IHttpContext,ResponseSerializerCallback,object)"/>
|
||||
/// <seealso cref="ResponseSerializer.Default"/>
|
||||
public static Task SendDataAsync(this IHttpContext @this, ResponseSerializerCallback serializer, object data)
|
||||
=> Validate.NotNull(nameof(serializer), serializer)(@this, data);
|
||||
}
|
||||
}
|
||||
30
Vendor/EmbedIO-3.5.2/HttpContextExtensions.cs
vendored
Normal file
30
Vendor/EmbedIO-3.5.2/HttpContextExtensions.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpContext"/>.
|
||||
/// </summary>
|
||||
public static partial class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets the underlying <see cref="IHttpContextImpl"/> interface of an <see cref="IHttpContext"/>.</para>
|
||||
/// <para>This API mainly supports the EmbedIO infrastructure; it is not intended to be used directly from your code,
|
||||
/// unless to fulfill very specific needs in the development of plug-ins (modules, etc.) for EmbedIO.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpContext"/> interface on which this method is called.</param>
|
||||
/// <returns>The underlying <see cref="IHttpContextImpl"/> interface representing
|
||||
/// the HTTP context implementation.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="this"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="EmbedIOInternalErrorException">
|
||||
/// <paramref name="this"/> does not implement <see cref="IHttpContextImpl"/>.
|
||||
/// </exception>
|
||||
public static IHttpContextImpl GetImplementation(this IHttpContext @this)
|
||||
=> Validate.NotNull(nameof(@this), @this) as IHttpContextImpl
|
||||
?? throw SelfCheck.Failure($"{@this.GetType().FullName} does not implement {nameof(IHttpContextImpl)}.");
|
||||
}
|
||||
}
|
||||
158
Vendor/EmbedIO-3.5.2/HttpException-Shortcuts.cs
vendored
Normal file
158
Vendor/EmbedIO-3.5.2/HttpException-Shortcuts.cs
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class HttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException" /> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>500 Internal Server Error</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpException" />.
|
||||
/// </returns>
|
||||
public static HttpException InternalServerError(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.InternalServerError, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException" /> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>401 Unauthorized</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpException" />.
|
||||
/// </returns>
|
||||
public static HttpException Unauthorized(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.Unauthorized, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>403 Forbidden</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException Forbidden(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.Forbidden, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>400 Bad Request</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException BadRequest(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.BadRequest, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>404 Not Found</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException NotFound(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.NotFound, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>405 Method Not Allowed</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to include in the response.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpException"/>.</returns>
|
||||
public static HttpException MethodNotAllowed(string? message = null, object? data = null)
|
||||
=> new HttpException(HttpStatusCode.MethodNotAllowed, message, data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpNotAcceptableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>406 Not Acceptable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <returns>A newly-created <see cref="HttpNotAcceptableException"/>.</returns>
|
||||
/// <seealso cref="HttpNotAcceptableException()"/>
|
||||
public static HttpNotAcceptableException NotAcceptable() => new HttpNotAcceptableException();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Returns a new instance of <see cref="HttpNotAcceptableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>406 Not Acceptable</c>
|
||||
/// response to the client.</para>
|
||||
/// </summary>
|
||||
/// <param name="vary">A value, or a comma-separated list of values, to set the response's <c>Vary</c> header to.</param>
|
||||
/// <returns>A newly-created <see cref="HttpNotAcceptableException"/>.</returns>
|
||||
/// <seealso cref="HttpNotAcceptableException(string)"/>
|
||||
public static HttpNotAcceptableException NotAcceptable(string vary) => new HttpNotAcceptableException(vary);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRangeNotSatisfiableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>416 Range Not Satisfiable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <returns>A newly-created <see cref="HttpRangeNotSatisfiableException"/>.</returns>
|
||||
/// <seealso cref="HttpRangeNotSatisfiableException()"/>
|
||||
public static HttpRangeNotSatisfiableException RangeNotSatisfiable() => new HttpRangeNotSatisfiableException();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRangeNotSatisfiableException"/> that, when thrown,
|
||||
/// will break the request handling control flow and send a <c>416 Range Not Satisfiable</c>
|
||||
/// response to the client.
|
||||
/// </summary>
|
||||
/// <param name="contentLength">The total length of the requested resource, expressed in bytes,
|
||||
/// or <see langword="null"/> to omit the <c>Content-Range</c> header in the response.</param>
|
||||
/// <returns>A newly-created <see cref="HttpRangeNotSatisfiableException"/>.</returns>
|
||||
/// <seealso cref="HttpRangeNotSatisfiableException()"/>
|
||||
public static HttpRangeNotSatisfiableException RangeNotSatisfiable(long? contentLength)
|
||||
=> new HttpRangeNotSatisfiableException(contentLength);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using response status code 302.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
public static HttpRedirectException Redirect(string location)
|
||||
=> new HttpRedirectException(location);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using the specified response status code.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">The status code to set on the response, in the range from 300 to 399.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not in the 300-399 range.</exception>
|
||||
public static HttpRedirectException Redirect(string location, int statusCode)
|
||||
=> new HttpRedirectException(location, statusCode);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HttpRedirectException" /> that, when thrown,
|
||||
/// will break the request handling control flow and redirect the client
|
||||
/// to the specified location, using the specified response status code.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">One of the redirection status codes, to be set on the response.</param>
|
||||
/// <returns>
|
||||
/// A newly-created <see cref="HttpRedirectException" />.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not a redirection status code.</exception>
|
||||
public static HttpRedirectException Redirect(string location, HttpStatusCode statusCode)
|
||||
=> new HttpRedirectException(location, statusCode);
|
||||
}
|
||||
}
|
||||
105
Vendor/EmbedIO-3.5.2/HttpException.cs
vendored
Normal file
105
Vendor/EmbedIO-3.5.2/HttpException.cs
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends an error response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public partial class HttpException : Exception, IHttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with no message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
public HttpException(int statusCode)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with no message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
public HttpException(HttpStatusCode statusCode)
|
||||
: this((int)statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with a message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
public HttpException(int statusCode, string? message)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
HttpExceptionMessage = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException"/> class,
|
||||
/// with a message to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
public HttpException(HttpStatusCode statusCode, string? message)
|
||||
: this((int)statusCode, message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException" /> class,
|
||||
/// with a message and a data object to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
public HttpException(int statusCode, string? message, object? data)
|
||||
: this(statusCode, message)
|
||||
{
|
||||
DataObject = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpException" /> class,
|
||||
/// with a message and a data object to include in the response.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The status code to set on the response.</param>
|
||||
/// <param name="message">A message to include in the response as plain text.</param>
|
||||
/// <param name="data">The data object to include in the response.</param>
|
||||
public HttpException(HttpStatusCode statusCode, string? message, object? data)
|
||||
: this((int)statusCode, message, data)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int StatusCode { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? DataObject { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
string? IHttpException.Message => HttpExceptionMessage;
|
||||
|
||||
// This property is necessary because when an exception with a null Message is thrown
|
||||
// the CLR provides a standard message. We want null to remain null in IHttpException.
|
||||
private string? HttpExceptionMessage { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <para>This method does nothing; there is no need to call
|
||||
/// <c>base.PrepareResponse</c> in overrides of this method.</para>
|
||||
/// </remarks>
|
||||
public virtual void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Vendor/EmbedIO-3.5.2/HttpExceptionHandler.cs
vendored
Normal file
153
Vendor/EmbedIO-3.5.2/HttpExceptionHandler.cs
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides standard handlers for HTTP exceptions at both module and server level.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Where applicable, HTTP exception handlers defined in this class
|
||||
/// use the <see cref="ExceptionHandler.ContactInformation"/> and
|
||||
/// <see cref="ExceptionHandler.IncludeStackTraces"/> properties to customize
|
||||
/// their behavior.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IWebServer.OnHttpException"/>
|
||||
/// <seealso cref="IWebModule.OnHttpException"/>
|
||||
public static class HttpExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets the default handler used by <see cref="WebServerBase{TOptions}"/>.</para>
|
||||
/// <para>This is the same as <see cref="HtmlResponse"/>.</para>
|
||||
/// </summary>
|
||||
public static HttpExceptionHandlerCallback Default { get; } = HtmlResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Sends an empty response.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
#pragma warning disable CA1801 // Unused parameter
|
||||
public static Task EmptyResponse(IHttpContext context, IHttpException httpException)
|
||||
#pragma warning restore CA1801
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends a HTTP exception's <see cref="IHttpException.Message">Message</see> property
|
||||
/// as a plain text response.</para>
|
||||
/// <para>This handler does not use the <see cref="IHttpException.DataObject">DataObject</see> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task PlainTextResponse(IHttpContext context, IHttpException httpException)
|
||||
=> context.SendStringAsync(httpException.Message ?? string.Empty, MimeType.PlainText, WebServer.DefaultEncoding);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sends a response with a HTML payload
|
||||
/// briefly describing the error, including contact information and/or a stack trace
|
||||
/// if specified via the <see cref="ExceptionHandler.ContactInformation"/>
|
||||
/// and <see cref="ExceptionHandler.IncludeStackTraces"/> properties, respectively.</para>
|
||||
/// <para>This handler does not use the <see cref="IHttpException.DataObject">DataObject</see> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">The HTTP exception.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
public static Task HtmlResponse(IHttpContext context, IHttpException httpException)
|
||||
=> context.SendStandardHtmlAsync(
|
||||
httpException.StatusCode,
|
||||
text => {
|
||||
text.Write(
|
||||
"<p><strong>Exception type:</strong> {0}<p><strong>Message:</strong> {1}",
|
||||
WebUtility.HtmlEncode(httpException.GetType().FullName ?? "<unknown>"),
|
||||
WebUtility.HtmlEncode(httpException.Message));
|
||||
|
||||
text.Write("<hr><p>If this error is completely unexpected to you, and you think you should not seeing this page, please contact the server administrator");
|
||||
|
||||
if (!string.IsNullOrEmpty(ExceptionHandler.ContactInformation))
|
||||
text.Write(" ({0})", WebUtility.HtmlEncode(ExceptionHandler.ContactInformation));
|
||||
|
||||
text.Write(", informing them of the time this error occurred and the action(s) you performed that resulted in this error.</p>");
|
||||
|
||||
if (ExceptionHandler.IncludeStackTraces)
|
||||
{
|
||||
text.Write(
|
||||
"</p><p><strong>Stack trace:</strong></p><br><pre>{0}</pre>",
|
||||
WebUtility.HtmlEncode(httpException.StackTrace));
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a <see cref="HttpExceptionHandlerCallback" /> that will serialize a HTTP exception's
|
||||
/// <see cref="IHttpException.DataObject">DataObject</see> property and send it as a JSON response.</para>
|
||||
/// </summary>
|
||||
/// <param name="serializerCallback">A <see cref="ResponseSerializerCallback" /> used to serialize data and send it to the client.</param>
|
||||
/// <returns>A <see cref="HttpExceptionHandlerCallback" />.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializerCallback"/> is <see langword="null"/>.</exception>
|
||||
public static HttpExceptionHandlerCallback DataResponse(ResponseSerializerCallback serializerCallback)
|
||||
{
|
||||
Validate.NotNull(nameof(serializerCallback), serializerCallback);
|
||||
|
||||
return (context, httpException) => serializerCallback(context, httpException.DataObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a <see cref="HttpExceptionHandlerCallback" /> that will serialize a HTTP exception's
|
||||
/// <see cref="IHttpException.Message">Message</see> and <see cref="IHttpException.DataObject">DataObject</see> properties
|
||||
/// and send them as a JSON response.</para>
|
||||
/// <para>The response will be a JSON object with a <c>message</c> property and a <c>data</c> property.</para>
|
||||
/// </summary>
|
||||
/// <param name="serializerCallback">A <see cref="ResponseSerializerCallback" /> used to serialize data and send it to the client.</param>
|
||||
/// <returns>A <see cref="HttpExceptionHandlerCallback" />.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serializerCallback"/> is <see langword="null"/>.</exception>
|
||||
public static HttpExceptionHandlerCallback FullDataResponse(ResponseSerializerCallback serializerCallback)
|
||||
{
|
||||
Validate.NotNull(nameof(serializerCallback), serializerCallback);
|
||||
|
||||
return (context, httpException) => serializerCallback(context, new
|
||||
{
|
||||
message = httpException.Message,
|
||||
data = httpException.DataObject,
|
||||
});
|
||||
}
|
||||
|
||||
internal static async Task Handle(string logSource, IHttpContext context, Exception exception, HttpExceptionHandlerCallback? handler)
|
||||
{
|
||||
if (handler == null || !(exception is IHttpException httpException))
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
return;
|
||||
}
|
||||
|
||||
exception.Log(logSource, $"[{context.Id}] HTTP exception {httpException.StatusCode}");
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.SetEmptyResponse(httpException.StatusCode);
|
||||
context.Response.DisableCaching();
|
||||
httpException.PrepareResponse(context);
|
||||
await handler(context, httpException)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
exception2.Log(logSource, $"[{context.Id}] Unhandled exception while handling HTTP exception {httpException.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Vendor/EmbedIO-3.5.2/HttpExceptionHandlerCallback.cs
vendored
Normal file
21
Vendor/EmbedIO-3.5.2/HttpExceptionHandlerCallback.cs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback used to build the contents of the response for an <see cref="IHttpException" />.
|
||||
/// </summary>
|
||||
/// <param name="context">An <see cref="IHttpContext" /> interface representing the context of the request.</param>
|
||||
/// <param name="httpException">An <see cref="IHttpException" /> interface.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>When this delegate is called, the response's status code has already been set and the <see cref="IHttpException.PrepareResponse"/>
|
||||
/// method has already been called. The only thing left to do is preparing the response's content, according
|
||||
/// to the <see cref="IHttpException.Message"/> property.</para>
|
||||
/// <para>Any exception thrown by a handler (even a HTTP exception) will go unhandled: the web server
|
||||
/// will not crash, but processing of the request will be aborted, and the response will be flushed as-is.
|
||||
/// In other words, it is not a good ides to <c>throw HttpException.NotFound()</c> (or similar)
|
||||
/// from a handler.</para>
|
||||
/// </remarks>
|
||||
public delegate Task HttpExceptionHandlerCallback(IHttpContext context, IHttpException httpException);
|
||||
}
|
||||
449
Vendor/EmbedIO-3.5.2/HttpHeaderNames.cs
vendored
Normal file
449
Vendor/EmbedIO-3.5.2/HttpHeaderNames.cs
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes known HTTP header names.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The constants in this class have been extracted from a list of known HTTP header names.
|
||||
/// The presence of a header name in this class is not a guarantee that EmbedIO supports,
|
||||
/// or even recognizes, it. Refer to the documentation for each module for information about supported
|
||||
/// headers.</para>
|
||||
/// </remarks>
|
||||
public static class HttpHeaderNames
|
||||
{
|
||||
// The .NET Core sources were taken as reference for this list of constants.
|
||||
// See https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs
|
||||
// However, not all constants come from there, so be careful not to copy-paste indiscriminately.
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Accept = "Accept";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Charset</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptCharset = "Accept-Charset";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptEncoding = "Accept-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Language</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptLanguage = "Accept-Language";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Patch</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptPatch = "Accept-Patch";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Accept-Ranges</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AcceptRanges = "Accept-Ranges";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Credentials</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Methods</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Allow-Origin</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Expose-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Max-Age</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlMaxAge = "Access-Control-Max-Age";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Request-Headers</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Access-Control-Request-Method</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AccessControlRequestMethod = "Access-Control-Request-Method";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Age</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Age = "Age";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Allow</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Allow = "Allow";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Alt-Svc</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string AltSvc = "Alt-Svc";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Authorization</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Authorization = "Authorization";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cache-Control</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string CacheControl = "Cache-Control";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Connection</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Connection = "Connection";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Disposition</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentDisposition = "Content-Disposition";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentEncoding = "Content-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Language</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLanguage = "Content-Language";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Length</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLength = "Content-Length";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Location</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentLocation = "Content-Location";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-MD5</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentMD5 = "Content-MD5";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentRange = "Content-Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Security-Policy</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentSecurityPolicy = "Content-Security-Policy";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Content-Type</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ContentType = "Content-Type";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cookie</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Cookie = "Cookie";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Cookie2</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Cookie2 = "Cookie2";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Date</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Date = "Date";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>ETag</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ETag = "ETag";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Expect</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Expect = "Expect";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Expires</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Expires = "Expires";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>From</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string From = "From";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Host</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Host = "Host";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Match</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfMatch = "If-Match";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Modified-Since</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfModifiedSince = "If-Modified-Since";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-None-Match</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfNoneMatch = "If-None-Match";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfRange = "If-Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>If-Unmodified-Since</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Keep-Alive</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string KeepAlive = "Keep-Alive";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Last-Modified</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string LastModified = "Last-Modified";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Link</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Link = "Link";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Location</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Location = "Location";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Max-Forwards</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string MaxForwards = "Max-Forwards";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Origin</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Origin = "Origin";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>P3P</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string P3P = "P3P";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Pragma</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Pragma = "Pragma";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Authenticate</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Authorization</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyAuthorization = "Proxy-Authorization";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Proxy-Connection</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string ProxyConnection = "Proxy-Connection";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Public-Key-Pins</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string PublicKeyPins = "Public-Key-Pins";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Range</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Range = "Range";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Referer</c> HTTP header.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The incorrect spelling ("Referer" instead of "Referrer") is intentional
|
||||
/// and has historical reasons.</para>
|
||||
/// <para>See the "Etymology" section of <a href="https://en.wikipedia.org/wiki/HTTP_referer">the Wikipedia article</a>
|
||||
/// on this header for more information.</para>
|
||||
/// </remarks>
|
||||
public const string Referer = "Referer";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Retry-After</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string RetryAfter = "Retry-After";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Accept</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Extensions</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Key</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Protocol</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Sec-WebSocket-Version</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Server</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Server = "Server";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Set-Cookie</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SetCookie = "Set-Cookie";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Set-Cookie2</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string SetCookie2 = "Set-Cookie2";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Strict-Transport-Security</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string StrictTransportSecurity = "Strict-Transport-Security";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>TE</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TE = "TE";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>TSV</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TSV = "TSV";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Trailer</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Trailer = "Trailer";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Transfer-Encoding</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string TransferEncoding = "Transfer-Encoding";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Upgrade</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Upgrade = "Upgrade";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Upgrade-Insecure-Requests</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>User-Agent</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string UserAgent = "User-Agent";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Vary</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Vary = "Vary";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Via</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Via = "Via";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>WWW-Authenticate</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string WWWAuthenticate = "WWW-Authenticate";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>Warning</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string Warning = "Warning";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-AspNet-Version</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XAspNetVersion = "X-AspNet-Version";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Content-Duration</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XContentDuration = "X-Content-Duration";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Content-Type-Options</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XContentTypeOptions = "X-Content-Type-Options";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Frame-Options</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XFrameOptions = "X-Frame-Options";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-MSEdge-Ref</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XMSEdgeRef = "X-MSEdge-Ref";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Powered-By</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XPoweredBy = "X-Powered-By";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-Request-ID</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XRequestID = "X-Request-ID";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>X-UA-Compatible</c> HTTP header.
|
||||
/// </summary>
|
||||
public const string XUACompatible = "X-UA-Compatible";
|
||||
}
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/HttpListenerMode.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/HttpListenerMode.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the HTTP listeners available for use in a <see cref="WebServer"/>.
|
||||
/// </summary>
|
||||
public enum HttpListenerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Use EmbedIO's internal HTTP listener implementation,
|
||||
/// based on Mono's <c>System.Net.HttpListener</c>.
|
||||
/// </summary>
|
||||
EmbedIO,
|
||||
|
||||
/// <summary>
|
||||
/// Use the <see cref="System.Net.HttpListener"/> class
|
||||
/// provided by the .NET runtime in use.
|
||||
/// </summary>
|
||||
Microsoft,
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/HttpNotAcceptableException.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/HttpNotAcceptableException.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpNotAcceptableException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpNotAcceptableException"/> class,
|
||||
/// without specifying a value for the response's <c>Vary</c> header.
|
||||
/// </summary>
|
||||
public HttpNotAcceptableException()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpNotAcceptableException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="vary">
|
||||
/// <para>A value, or a comma-separated list of values, to set the response's <c>Vary</c> header to.</para>
|
||||
/// <para>Although not specified in <see href="https://tools.ietf.org/html/rfc7231#section-6.5.6">RFC7231</see>,
|
||||
/// this may help the client to understand why the request has been rejected.</para>
|
||||
/// <para>If this parameter is <see langword="null"/> or the empty string, the response's <c>Vary</c> header
|
||||
/// is not set.</para>
|
||||
/// </param>
|
||||
public HttpNotAcceptableException(string? vary)
|
||||
: base((int)HttpStatusCode.NotAcceptable)
|
||||
{
|
||||
Vary = string.IsNullOrEmpty(vary) ? null : vary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value, or comma-separated list of values, to be set
|
||||
/// on the response's <c>Vary</c> header.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the empty string has been passed to the <see cref="HttpNotAcceptableException(string)"/>
|
||||
/// constructor, the value of this property is <see langword="null"/>.</para>
|
||||
/// </remarks>
|
||||
public string? Vary { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
if (Vary != null)
|
||||
context.Response.Headers.Add(HttpHeaderNames.Vary, Vary);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Vendor/EmbedIO-3.5.2/HttpRangeNotSatisfiableException.cs
vendored
Normal file
50
Vendor/EmbedIO-3.5.2/HttpRangeNotSatisfiableException.cs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpRangeNotSatisfiableException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRangeNotSatisfiableException"/> class.
|
||||
/// without specifying a value for the response's <c>Content-Range</c> header.
|
||||
/// </summary>
|
||||
public HttpRangeNotSatisfiableException()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRangeNotSatisfiableException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="contentLength">The total length of the requested resource, expressed in bytes,
|
||||
/// or <see langword="null"/> to omit the <c>Content-Range</c> header in the response.</param>
|
||||
public HttpRangeNotSatisfiableException(long? contentLength)
|
||||
: base((int)HttpStatusCode.RequestedRangeNotSatisfiable)
|
||||
{
|
||||
ContentLength = contentLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total content length to be specified
|
||||
/// on the response's <c>Content-Range</c> header.
|
||||
/// </summary>
|
||||
public long? ContentLength { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
// RFC 7233, Section 3.1: "When this status code is generated in response
|
||||
// to a byte-range request, the sender
|
||||
// SHOULD generate a Content-Range header field specifying
|
||||
// the current length of the selected representation."
|
||||
if (ContentLength.HasValue)
|
||||
context.Response.Headers.Set(HttpHeaderNames.ContentRange, $"bytes */{ContentLength.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Vendor/EmbedIO-3.5.2/HttpRedirectException.cs
vendored
Normal file
54
Vendor/EmbedIO-3.5.2/HttpRedirectException.cs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// When thrown, breaks the request handling control flow
|
||||
/// and sends a redirection response to the client.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1032 // Implement standard exception constructors - they have no meaning here.
|
||||
public class HttpRedirectException : HttpException
|
||||
#pragma warning restore CA1032
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRedirectException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">
|
||||
/// <para>The status code to set on the response, in the range from 300 to 399.</para>
|
||||
/// <para>By default, status code 302 (<c>Found</c>) is used.</para>
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not in the 300-399 range.</exception>
|
||||
public HttpRedirectException(string location, int statusCode = (int)HttpStatusCode.Found)
|
||||
: base(statusCode)
|
||||
{
|
||||
if (statusCode < 300 || statusCode > 399)
|
||||
throw new ArgumentException("Redirect status code is not valid.", nameof(statusCode));
|
||||
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRedirectException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="location">The redirection target.</param>
|
||||
/// <param name="statusCode">One of the redirection status codes, to be set on the response.</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="statusCode"/> is not a redirection status code.</exception>
|
||||
public HttpRedirectException(string location, HttpStatusCode statusCode)
|
||||
: this(location, (int)statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL where the client will be redirected.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PrepareResponse(IHttpContext context)
|
||||
{
|
||||
context.Redirect(Location, StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Vendor/EmbedIO-3.5.2/HttpRequestExtensions.cs
vendored
Normal file
262
Vendor/EmbedIO-3.5.2/HttpRequestExtensions.cs
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpRequest"/>.
|
||||
/// </summary>
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Returns a string representing the remote IP address and port of an <see cref="IHttpRequest"/> interface.</para>
|
||||
/// <para>This method can be called even on a <see langword="null"/> interface, or one that has no
|
||||
/// remote end point, or no remote address; it will always return a non-<see langword="null"/>,
|
||||
/// non-empty string.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <returns>
|
||||
/// If <paramref name="this"/> is <see langword="null"/>, or its <see cref="IHttpRequest.RemoteEndPoint">RemoteEndPoint</see>
|
||||
/// is <see langword="null"/>, the string <c>"<null></c>; otherwise, the remote end point's
|
||||
/// <see cref="IPEndPoint.Address">Address</see> (or the string <c>"<???>"</c> if it is <see langword="null"/>)
|
||||
/// followed by a colon and the <see cref="IPEndPoint.Port">Port</see> number.
|
||||
/// </returns>
|
||||
public static string SafeGetRemoteEndpointStr(this IHttpRequest @this)
|
||||
{
|
||||
var endPoint = @this?.RemoteEndPoint;
|
||||
return endPoint == null
|
||||
? "<null>"
|
||||
: $"{endPoint.Address?.ToString() ?? "<???>"}:{endPoint.Port.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Attempts to proactively negotiate a compression method for a response,
|
||||
/// based on a request's <c>Accept-Encoding</c> header (or lack of it).</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> 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="prepareResponse">When this method returns, a callback that prepares data in an <see cref="IHttpResponse"/>
|
||||
/// according to the result of content negotiation. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if content negotiation is successful;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// <para>If this method returns <see langword="true"/>, the <paramref name="prepareResponse"/> callback
|
||||
/// will set appropriate response headers to reflect the results of content negotiation.</para>
|
||||
/// <para>If this method returns <see langword="false"/>, the <paramref name="prepareResponse"/> callback
|
||||
/// will throw a <see cref="HttpNotAcceptableException"/> to send a <c>406 Not Acceptable</c> response
|
||||
/// with the <c>Vary</c> header set to <c>Accept-Encoding</c>,
|
||||
/// so that the client may know the reason why the request has been rejected.</para>
|
||||
/// <para>If <paramref name="this"/> has no<c>Accept-Encoding</c> header, this method
|
||||
/// always returns <see langword="true"/> and sets <paramref name="compressionMethod"/>
|
||||
/// to <see cref="CompressionMethod.None"/>.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="HttpNotAcceptableException(string)"/>
|
||||
public static bool TryNegotiateContentEncoding(
|
||||
this IHttpRequest @this,
|
||||
bool preferCompression,
|
||||
out CompressionMethod compressionMethod,
|
||||
out Action<IHttpResponse> prepareResponse)
|
||||
{
|
||||
var acceptedEncodings = new QValueList(true, @this.Headers.GetValues(HttpHeaderNames.AcceptEncoding));
|
||||
if (!acceptedEncodings.TryNegotiateContentEncoding(preferCompression, out compressionMethod, out var compressionMethodName))
|
||||
{
|
||||
prepareResponse = r => throw HttpException.NotAcceptable(HttpHeaderNames.AcceptEncoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareResponse = r => {
|
||||
r.Headers.Add(HttpHeaderNames.Vary, HttpHeaderNames.AcceptEncoding);
|
||||
r.Headers.Set(HttpHeaderNames.ContentEncoding, compressionMethodName);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether an <c>If-None-Match</c> header exists in a request
|
||||
/// and, if so, whether it contains a given entity tag.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7232#section-3.2">RFC7232, Section 3.2</see>
|
||||
/// for a normative reference; however, see the Remarks section for more information
|
||||
/// about the RFC compliance of this method.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="entityTag">The entity tag.</param>
|
||||
/// <param name="headerExists">When this method returns, a value that indicates whether an
|
||||
/// <c>If-None-Match</c> header is present in <paramref name="this"/>, regardless of the method's
|
||||
/// return value. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if an <c>If-None-Match</c> header is present in
|
||||
/// <paramref name="this"/> and one of the entity tags listed in it is equal to <paramref name="entityTag"/>;
|
||||
/// <see langword="false"/> otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// <para><see href="https://tools.ietf.org/html/rfc7232#section-3.2">RFC7232, Section 3.2</see>
|
||||
/// states that a weak comparison function (as defined in
|
||||
/// <see href="https://tools.ietf.org/html/rfc7232#section-2.3.2">RFC7232, Section 2.3.2</see>)
|
||||
/// must be used for <c>If-None-Match</c>. That would mean parsing every entity tag, at least minimally,
|
||||
/// to determine whether it is a "weak" or "strong" tag. Since EmbedIO currently generates only
|
||||
/// "strong" tags, this method uses the default string comparer instead.</para>
|
||||
/// <para>The behavior of this method is thus not, strictly speaking, RFC7232-compliant;
|
||||
/// it works, though, with entity tags generated by EmbedIO.</para>
|
||||
/// </remarks>
|
||||
public static bool CheckIfNoneMatch(this IHttpRequest @this, string entityTag, out bool headerExists)
|
||||
{
|
||||
var values = @this.Headers.GetValues(HttpHeaderNames.IfNoneMatch);
|
||||
if (values == null)
|
||||
{
|
||||
headerExists = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
headerExists = true;
|
||||
return values.Select(t => t.Trim()).Contains(entityTag);
|
||||
}
|
||||
|
||||
// Check whether the If-Modified-Since request header exists
|
||||
// and specifies a date and time more recent than or equal to
|
||||
// the date and time of last modification of the requested resource.
|
||||
// RFC7232, Section 3.3
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether an <c>If-Modified-Since</c> header exists in a request
|
||||
/// and, if so, whether its value is a date and time more recent or equal to
|
||||
/// a given <see cref="DateTime"/>.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7232#section-3.3">RFC7232, Section 3.3</see>
|
||||
/// for a normative reference.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="lastModifiedUtc">A date and time value, in Coordinated Universal Time,
|
||||
/// expressing the last time a resource was modified.</param>
|
||||
/// <param name="headerExists">When this method returns, a value that indicates whether an
|
||||
/// <c>If-Modified-Since</c> header is present in <paramref name="this"/>, regardless of the method's
|
||||
/// return value. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if an <c>If-Modified-Since</c> header is present in
|
||||
/// <paramref name="this"/> and its value is a date and time more recent or equal to <paramref name="lastModifiedUtc"/>;
|
||||
/// <see langword="false"/> otherwise.</returns>
|
||||
public static bool CheckIfModifiedSince(this IHttpRequest @this, DateTime lastModifiedUtc, out bool headerExists)
|
||||
{
|
||||
var value = @this.Headers.Get(HttpHeaderNames.IfModifiedSince);
|
||||
if (value == null)
|
||||
{
|
||||
headerExists = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
headerExists = true;
|
||||
return HttpDate.TryParse(value, out var dateTime)
|
||||
&& dateTime.UtcDateTime >= lastModifiedUtc;
|
||||
}
|
||||
|
||||
// Checks the Range request header to tell whether to send
|
||||
// a "206 Partial Content" response.
|
||||
|
||||
/// <summary>
|
||||
/// <para>Checks whether a <c>Range</c> header exists in a request
|
||||
/// and, if so, determines whether it is possible to send a <c>206 Partial Content</c> response.</para>
|
||||
/// <para>See <see href="https://tools.ietf.org/html/rfc7233">RFC7233</see>
|
||||
/// for a normative reference; however, see the Remarks section for more information
|
||||
/// about the RFC compliance of this method.</para>
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
|
||||
/// <param name="contentLength">The total length, in bytes, of the response entity, i.e.
|
||||
/// what would be sent in a <c>200 OK</c> response.</param>
|
||||
/// <param name="entityTag">An entity tag representing the response entity. This value is checked against
|
||||
/// the <c>If-Range</c> header, if it is present.</param>
|
||||
/// <param name="lastModifiedUtc">The date and time value, in Coordinated Universal Time,
|
||||
/// expressing the last modification time of the resource entity. This value is checked against
|
||||
/// the <c>If-Range</c> header, if it is present.</param>
|
||||
/// <param name="start">When this method returns <see langword="true"/>, the start of the requested byte range.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <param name="upperBound">
|
||||
/// <para>When this method returns <see langword="true"/>, the upper bound of the requested byte range.
|
||||
/// This parameter is passed uninitialized.</para>
|
||||
/// <para>Note that the upper bound of a range is NOT the sum of the range's start and length;
|
||||
/// for example, a range expressed as <c>bytes=0-99</c> has a start of 0, an upper bound of 99,
|
||||
/// and a length of 100 bytes.</para>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <para>This method returns <see langword="true"/> if the following conditions are satisfied:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>>the request's HTTP method is <c>GET</c>;</description></item>
|
||||
/// <item><description>>a <c>Range</c> header is present in the request;</description></item>
|
||||
/// <item><description>>either no <c>If-Range</c> header is present in the request, or it
|
||||
/// specifies an entity tag equal to <paramref name="entityTag"/>, or a UTC date and time
|
||||
/// equal to <paramref name="lastModifiedUtc"/>;</description></item>
|
||||
/// <item><description>>the <c>Range</c> header specifies exactly one range;</description></item>
|
||||
/// <item><description>>the specified range is entirely contained in the range from 0 to <paramref name="contentLength"/> - 1.</description></item>
|
||||
/// </list>
|
||||
/// <para>If the last condition is not satisfied, i.e. the specified range start and/or upper bound
|
||||
/// are out of the range from 0 to <paramref name="contentLength"/> - 1, this method does not return;
|
||||
/// it throws a <see cref="HttpRangeNotSatisfiableException"/> instead.</para>
|
||||
/// <para>If any of the other conditions are not satisfied, this method returns <see langword="false"/>.</para>
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>According to <see href="https://tools.ietf.org/html/rfc7233#section-3.1">RFC7233, Section 3.1</see>,
|
||||
/// there are several conditions under which a server may ignore or reject a range request; therefore,
|
||||
/// clients are (or should be) prepared to receive a <c>200 OK</c> response with the whole response
|
||||
/// entity instead of the requested range(s). For this reason, until the generation of
|
||||
/// <c>multipart/byteranges</c> responses is implemented in EmbedIO, this method will ignore
|
||||
/// range requests specifying more than one range, even if this behavior is not, strictly speaking,
|
||||
/// RFC7233-compliant.</para>
|
||||
/// <para>To make clients aware that range requests are accepted for a resource, every <c>200 OK</c>
|
||||
/// (or <c>304 Not Modified</c>) response for the same resource should include an <c>Accept-Ranges</c>
|
||||
/// header with the string <c>bytes</c> as value.</para>
|
||||
/// </remarks>
|
||||
public static bool IsRangeRequest(this IHttpRequest @this, long contentLength, string entityTag, DateTime lastModifiedUtc, out long start, out long upperBound)
|
||||
{
|
||||
start = 0;
|
||||
upperBound = contentLength - 1;
|
||||
|
||||
// RFC7233, Section 3.1:
|
||||
// "A server MUST ignore a Range header field received with a request method other than GET."
|
||||
if (@this.HttpVerb != HttpVerbs.Get)
|
||||
return false;
|
||||
|
||||
// No Range header, no partial content.
|
||||
var rangeHeader = @this.Headers.Get(HttpHeaderNames.Range);
|
||||
if (rangeHeader == null)
|
||||
return false;
|
||||
|
||||
// Ignore the Range header if there is no If-Range header
|
||||
// or if the If-Range header specifies a non-matching validator.
|
||||
// RFC7233, Section 3.2: "If the validator given in the If-Range header field matches the
|
||||
// current validator for the selected representation of the target
|
||||
// resource, then the server SHOULD process the Range header field as
|
||||
// requested.If the validator does not match, the server MUST ignore
|
||||
// the Range header field.Note that this comparison by exact match,
|
||||
// including when the validator is an HTTP-date, differs from the
|
||||
// "earlier than or equal to" comparison used when evaluating an
|
||||
// If-Unmodified-Since conditional."
|
||||
var ifRange = @this.Headers.Get(HttpHeaderNames.IfRange)?.Trim();
|
||||
if (ifRange != null && ifRange != entityTag)
|
||||
{
|
||||
if (!HttpDate.TryParse(ifRange, out var rangeDate))
|
||||
return false;
|
||||
|
||||
if (rangeDate.UtcDateTime != lastModifiedUtc)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the Range request header if it cannot be parsed successfully.
|
||||
if (!RangeHeaderValue.TryParse(rangeHeader, out var range))
|
||||
return false;
|
||||
|
||||
// EmbedIO does not support multipart/byteranges responses (yet),
|
||||
// thus ignore range requests that specify one range.
|
||||
if (range.Ranges.Count != 1)
|
||||
return false;
|
||||
|
||||
var firstRange = range.Ranges.First();
|
||||
start = firstRange.From ?? 0L;
|
||||
upperBound = firstRange.To ?? contentLength - 1;
|
||||
if (start >= contentLength || upperBound < start || upperBound >= contentLength)
|
||||
throw HttpException.RangeNotSatisfiable(contentLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Vendor/EmbedIO-3.5.2/HttpResponseExtensions.cs
vendored
Normal file
43
Vendor/EmbedIO-3.5.2/HttpResponseExtensions.cs
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IHttpResponse"/>.
|
||||
/// </summary>
|
||||
public static class HttpResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the necessary headers to disable caching of a response on the client side.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
public static void DisableCaching(this IHttpResponse @this)
|
||||
{
|
||||
var headers = @this.Headers;
|
||||
headers.Set(HttpHeaderNames.Expires, "Sat, 26 Jul 1997 05:00:00 GMT");
|
||||
headers.Set(HttpHeaderNames.LastModified, HttpDate.Format(DateTime.UtcNow));
|
||||
headers.Set(HttpHeaderNames.CacheControl, "no-store, no-cache, must-revalidate");
|
||||
headers.Add(HttpHeaderNames.Pragma, "no-cache");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a standard response without a body for the specified status code.
|
||||
/// </summary>
|
||||
/// <param name="this">The <see cref="IHttpResponse"/> interface on which this method is called.</param>
|
||||
/// <param name="statusCode">The HTTP status code of the response.</param>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">There is no standard status description for <paramref name="statusCode"/>.</exception>
|
||||
public static void SetEmptyResponse(this IHttpResponse @this, int statusCode)
|
||||
{
|
||||
if (!HttpStatusDescription.TryGet(statusCode, out var statusDescription))
|
||||
throw new ArgumentException("Status code has no standard description.", nameof(statusCode));
|
||||
|
||||
@this.StatusCode = statusCode;
|
||||
@this.StatusDescription = statusDescription;
|
||||
@this.ContentType = MimeType.Default;
|
||||
@this.ContentEncoding = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Vendor/EmbedIO-3.5.2/HttpStatusDescription.cs
vendored
Normal file
146
Vendor/EmbedIO-3.5.2/HttpStatusDescription.cs
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Provides standard HTTP status descriptions.</para>
|
||||
/// <para>Data contained in this class comes from the following sources:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7231#section-6">RFC7231 Section 6</see> (HTTP/1.1 Semantics and Content)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc6585">RFC6585</see> (Additional HTTP Status Codes)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc2774#section-7">RFC2774 Section 7</see> (An HTTP Extension Framework)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7540#section-9.1.2">RFC7540 Section 9.1.2</see> (HTTP/2)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc4918#section-11">RFC4918 Section 11</see> (WebDAV)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc5842#section-7">RFC5842 Section 7</see> (Binding Extensions to WebDAV)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7538#section-3">RFC7538 Section 3</see> (HTTP Status Code 308)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc3229#section-10.4.1">RFC3229 Section 10.4.1</see> (Delta encoding in HTTP)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc8297#section-2">RFC8297 Section 2</see> (Early Hints)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc7725#section-3">RFC7725 Section 3</see> (HTTP-status-451)</description></item>
|
||||
/// <item><description><see href="https://tools.ietf.org/html/rfc2295#section-8.1">RFC2295 Section 8.1</see> (Transparent Content Negotiation)</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class HttpStatusDescription
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<int, string> Dictionary = new Dictionary<int, string> {
|
||||
{ 100, "Continue" },
|
||||
{ 101, "Switching Protocols" },
|
||||
{ 102, "Processing" },
|
||||
{ 103, "Early Hints" },
|
||||
{ 200, "OK" },
|
||||
{ 201, "Created" },
|
||||
{ 202, "Accepted" },
|
||||
{ 203, "Non-Authoritative Information" },
|
||||
{ 204, "No Content" },
|
||||
{ 205, "Reset Content" },
|
||||
{ 206, "Partial Content" },
|
||||
{ 207, "Multi-Status" },
|
||||
{ 208, "Already Reported" },
|
||||
{ 226, "IM Used" },
|
||||
{ 300, "Multiple Choices" },
|
||||
{ 301, "Moved Permanently" },
|
||||
{ 302, "Found" },
|
||||
{ 303, "See Other" },
|
||||
{ 304, "Not Modified" },
|
||||
{ 305, "Use Proxy" },
|
||||
{ 307, "Temporary Redirect" },
|
||||
{ 308, "Permanent Redirect" },
|
||||
{ 400, "Bad Request" },
|
||||
{ 401, "Unauthorized" },
|
||||
{ 402, "Payment Required" },
|
||||
{ 403, "Forbidden" },
|
||||
{ 404, "Not Found" },
|
||||
{ 405, "Method Not Allowed" },
|
||||
{ 406, "Not Acceptable" },
|
||||
{ 407, "Proxy Authentication Required" },
|
||||
{ 408, "Request Timeout" },
|
||||
{ 409, "Conflict" },
|
||||
{ 410, "Gone" },
|
||||
{ 411, "Length Required" },
|
||||
{ 412, "Precondition Failed" },
|
||||
{ 413, "Request Entity Too Large" },
|
||||
{ 414, "Request-Uri Too Long" },
|
||||
{ 415, "Unsupported Media Type" },
|
||||
{ 416, "Requested Range Not Satisfiable" },
|
||||
{ 417, "Expectation Failed" },
|
||||
{ 421, "Misdirected Request" },
|
||||
{ 422, "Unprocessable Entity" },
|
||||
{ 423, "Locked" },
|
||||
{ 424, "Failed Dependency" },
|
||||
{ 426, "Upgrade Required" },
|
||||
{ 428, "Precondition Required" },
|
||||
{ 429, "Too Many Requests" },
|
||||
{ 431, "Request Header Fields Too Large" },
|
||||
{ 451, "Unavailable For Legal Reasons" },
|
||||
{ 500, "Internal Server Error" },
|
||||
{ 501, "Not Implemented" },
|
||||
{ 502, "Bad Gateway" },
|
||||
{ 503, "Service Unavailable" },
|
||||
{ 504, "Gateway Timeout" },
|
||||
{ 505, "Http Version Not Supported" },
|
||||
{ 506, "Variant Also Negotiates" },
|
||||
{ 507, "Insufficient Storage" },
|
||||
{ 508, "Loop Detected" },
|
||||
{ 510, "Not Extended" },
|
||||
{ 511, "Network Authentication Required" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the standard status description for a <see cref="HttpStatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <param name="description">When this method returns, the standard HTTP status description
|
||||
/// for the specified <paramref name="code"/> if it was found, or <see langword="null"/>
|
||||
/// if it was not found. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if the specified <paramref name="code"/> was found
|
||||
/// in the list of HTTP status codes for which the standard description is known;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <seealso cref="TryGet(int,out string)"/>
|
||||
/// <seealso cref="Get(HttpStatusCode)"/>
|
||||
public static bool TryGet(HttpStatusCode code, out string description) => Dictionary.TryGetValue((int)code, out description);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the standard status description for a HTTP status code
|
||||
/// specified as an <see langword="int"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <param name="description">When this method returns, the standard HTTP status description
|
||||
/// for the specified <paramref name="code"/> if it was found, or <see langword="null"/>
|
||||
/// if it was not found. This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if the specified <paramref name="code"/> was found
|
||||
/// in the list of HTTP status codes for which the standard description is known;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
/// <seealso cref="TryGet(HttpStatusCode,out string)"/>
|
||||
/// <seealso cref="Get(int)"/>
|
||||
public static bool TryGet(int code, out string description) => Dictionary.TryGetValue(code, out description);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the standard status description for a <see cref="HttpStatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <returns>The standard HTTP status description for the specified <paramref name="code"/>
|
||||
/// if it was found, or <see langword="null"/> if it was not found.</returns>
|
||||
public static string Get(HttpStatusCode code)
|
||||
{
|
||||
Dictionary.TryGetValue((int)code, out var description);
|
||||
return description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the standard status description for a HTTP status code
|
||||
/// specified as an <see langword="int"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The HTTP status code for which the standard description
|
||||
/// is to be retrieved.</param>
|
||||
/// <returns>The standard HTTP status description for the specified <paramref name="code"/>
|
||||
/// if it was found, or <see langword="null"/> if it was not found.</returns>
|
||||
public static string Get(int code)
|
||||
{
|
||||
Dictionary.TryGetValue(code, out var description);
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Vendor/EmbedIO-3.5.2/HttpVerbs.cs
vendored
Normal file
48
Vendor/EmbedIO-3.5.2/HttpVerbs.cs
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different HTTP Verbs.
|
||||
/// </summary>
|
||||
public enum HttpVerbs
|
||||
{
|
||||
/// <summary>
|
||||
/// Wildcard Method
|
||||
/// </summary>
|
||||
Any,
|
||||
|
||||
/// <summary>
|
||||
/// DELETE Method
|
||||
/// </summary>
|
||||
Delete,
|
||||
|
||||
/// <summary>
|
||||
/// GET Method
|
||||
/// </summary>
|
||||
Get,
|
||||
|
||||
/// <summary>
|
||||
/// HEAD method
|
||||
/// </summary>
|
||||
Head,
|
||||
|
||||
/// <summary>
|
||||
/// OPTIONS method
|
||||
/// </summary>
|
||||
Options,
|
||||
|
||||
/// <summary>
|
||||
/// PATCH method
|
||||
/// </summary>
|
||||
Patch,
|
||||
|
||||
/// <summary>
|
||||
/// POST method
|
||||
/// </summary>
|
||||
Post,
|
||||
|
||||
/// <summary>
|
||||
/// PUT method
|
||||
/// </summary>
|
||||
Put,
|
||||
}
|
||||
}
|
||||
49
Vendor/EmbedIO-3.5.2/ICookieCollection.cs
vendored
Normal file
49
Vendor/EmbedIO-3.5.2/ICookieCollection.cs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for Cookie Collection.
|
||||
/// </summary>
|
||||
/// <seealso cref="ICollection" />
|
||||
#pragma warning disable CA1010 // Should implement ICollection<Cookie> - not possible when wrapping System.Net.CookieCollection.
|
||||
public interface ICookieCollection : IEnumerable<Cookie>, ICollection
|
||||
#pragma warning restore CA1010
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Cookie"/> with the specified name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="Cookie"/>.
|
||||
/// </value>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The cookie matching the specified name.</returns>
|
||||
Cookie? this[string name] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this <see cref="ICookieCollection"/> contains the specified <see cref="Cookie"/>.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie to find in the <see cref="ICookieCollection"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if this <see cref="ICookieCollection"/> contains the specified <paramref name="cookie"/>;
|
||||
/// otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
bool Contains(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of this <see cref="ICookieCollection"/> to a <see cref="Cookie"/> array
|
||||
/// starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">The target <see cref="Cookie"/> array to which the <see cref="ICookieCollection"/> will be copied.</param>
|
||||
/// <param name="index">The zero-based index in the target <paramref name="array"/> where copying begins.</param>
|
||||
void CopyTo(Cookie[] array, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified cookie.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie.</param>
|
||||
void Add(Cookie cookie);
|
||||
}
|
||||
}
|
||||
128
Vendor/EmbedIO-3.5.2/IHttpContext.cs
vendored
Normal file
128
Vendor/EmbedIO-3.5.2/IHttpContext.cs
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the context of a HTTP(s) request being handled by a web server.
|
||||
/// </summary>
|
||||
public interface IHttpContext : IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a unique identifier for a HTTP context.
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="CancellationToken" /> used to stop processing of this context.
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server IP address and port number to which the request is directed.
|
||||
/// </summary>
|
||||
IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client IP address and port number from which the request originated.
|
||||
/// </summary>
|
||||
IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP request.
|
||||
/// </summary>
|
||||
IHttpRequest Request { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route matched by the requested URL path.
|
||||
/// </summary>
|
||||
RouteMatch Route { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested path, relative to the innermost module's base path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This property derives from the path specified in the requested URL, stripped of the
|
||||
/// <see cref="IWebModule.BaseRoute">BaseRoute</see> of the handling module.</para>
|
||||
/// <para>This property is in itself a valid URL path, including an initial
|
||||
/// slash (<c>/</c>) character.</para>
|
||||
/// </remarks>
|
||||
string RequestedPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP response object.
|
||||
/// </summary>
|
||||
IHttpResponse Response { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user.
|
||||
/// </summary>
|
||||
IPrincipal User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session proxy associated with this context.
|
||||
/// </summary>
|
||||
ISessionProxy Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether compressed request bodies are supported.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
bool SupportCompressedRequests { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dictionary of data to pass trough the EmbedIO pipeline.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time, expressed in milliseconds, since the creation of this context.
|
||||
/// </summary>
|
||||
long Age { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a value indicating whether this <see cref="IHttpContext"/>
|
||||
/// has been completely handled, so that no further processing is required.</para>
|
||||
/// <para>When a HTTP context is created, this property is <see langword="false" />;
|
||||
/// as soon as it is set to <see langword="true" />, the context is not
|
||||
/// passed to any further module's handler for processing.</para>
|
||||
/// <para>Once it becomes <see langword="true" />, this property is guaranteed
|
||||
/// to never become <see langword="false" /> again.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When a module's <see cref="IWebModule.IsFinalHandler">IsFinalHandler</see> property is
|
||||
/// <see langword="true" />, this property is set to <see langword="true" /> after the <see cref="Task" />
|
||||
/// returned by the module's <see cref="IWebModule.HandleRequestAsync">HandleRequestAsync</see> method
|
||||
/// is completed.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="SetHandled" />
|
||||
/// <seealso cref="IWebModule.IsFinalHandler"/>
|
||||
bool IsHandled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks this context as handled, so that it will not be
|
||||
/// processed by any further module.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Calling this method from the <see cref="IWebModule.HandleRequestAsync" />
|
||||
/// or <see cref="WebModuleBase.OnRequestAsync" /> of a module whose
|
||||
/// <see cref="IWebModule.IsFinalHandler" /> property is <see langword="true" />
|
||||
/// is redundant and has no effect.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsHandled"/>
|
||||
/// <seealso cref="IWebModule.IsFinalHandler"/>
|
||||
void SetHandled();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a callback to be called when processing is finished on a context.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback.</param>
|
||||
void OnClose(Action<IHttpContext> callback);
|
||||
}
|
||||
}
|
||||
20
Vendor/EmbedIO-3.5.2/IHttpContextHandler.cs
vendored
Normal file
20
Vendor/EmbedIO-3.5.2/IHttpContextHandler.cs
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents an object that can handle a HTTP context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
public interface IHttpContextHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Asynchronously handles a HTTP context, generating a suitable response
|
||||
/// for an incoming request.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the ongoing operation.</returns>
|
||||
Task HandleContextAsync(IHttpContextImpl context);
|
||||
}
|
||||
}
|
||||
88
Vendor/EmbedIO-3.5.2/IHttpContextImpl.cs
vendored
Normal file
88
Vendor/EmbedIO-3.5.2/IHttpContextImpl.cs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a HTTP context implementation, i.e. a HTTP context as seen internally by EmbedIO.</para>
|
||||
/// <para>This API mainly supports the EmbedIO infrastructure; it is not intended to be used directly from your code,
|
||||
/// unless to address specific needs in the implementation of EmbedIO plug-ins (e.g. modules).</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="IHttpContext" />
|
||||
public interface IHttpContextImpl : IHttpContext
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a <see cref="CancellationToken" /> used to stop processing of this context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
new CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the route matched by the requested URL path.
|
||||
/// </summary>
|
||||
new RouteMatch Route { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the session proxy associated with this context.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An <see cref="ISessionProxy"/> interface.
|
||||
/// </value>
|
||||
new ISessionProxy Session { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the user.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
new IPrincipal User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a value indicating whether compressed request bodies are supported.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="WebServerOptionsBase.SupportCompressedRequests"/>
|
||||
new bool SupportCompressedRequests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets the MIME type providers.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
MimeTypeProviderStack MimeTypeProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Flushes and closes the response stream, then calls any registered close callbacks.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="IHttpContext.OnClose"/>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// <para>Asynchronously handles a WebSockets opening handshake
|
||||
/// and returns a newly-created <seealso cref="IWebSocketContext"/> interface.</para>
|
||||
/// <para>This API supports the EmbedIO infrastructure and is not intended to be used directly from your code.</para>
|
||||
/// </summary>
|
||||
/// <param name="requestedProtocols">The requested WebSocket sub-protocols.</param>
|
||||
/// <param name="acceptedProtocol">The accepted WebSocket sub-protocol,
|
||||
/// or the empty string is no sub-protocol has been agreed upon.</param>
|
||||
/// <param name="receiveBufferSize">Size of the receive buffer.</param>
|
||||
/// <param name="keepAliveInterval">The keep-alive interval.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the server.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IWebSocketContext"/> interface.
|
||||
/// </returns>
|
||||
Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
58
Vendor/EmbedIO-3.5.2/IHttpException.cs
vendored
Normal file
58
Vendor/EmbedIO-3.5.2/IHttpException.cs
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents an exception that results in a particular
|
||||
/// HTTP response to be sent to the client.</para>
|
||||
/// <para>This interface is meant to be implemented
|
||||
/// by classes derived from <see cref="Exception" />.</para>
|
||||
/// <para>Either as message or a data object can be attached to
|
||||
/// the exception; which one, if any, is sent to the client
|
||||
/// will depend upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandlerCallback"/>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
public interface IHttpException
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the response status code for a HTTP exception.
|
||||
/// </summary>
|
||||
int StatusCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stack trace of a HTTP exception.
|
||||
/// </summary>
|
||||
string StackTrace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets a message that can be included in the response triggered
|
||||
/// by a HTTP exception.</para>
|
||||
/// <para>Whether the message is actually sent to the client will depend
|
||||
/// upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Do not rely on <see cref="Exception.Message"/> to implement
|
||||
/// this property if you want to support <see langword="null"/> messages,
|
||||
/// because a default message will be supplied by the CLR at throw time
|
||||
/// when <see cref="Exception.Message"/> is <see langword="null"/>.</para>
|
||||
/// </remarks>
|
||||
string? Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets an object that can be serialized and included
|
||||
/// in the response triggered by a HTTP exception.</para>
|
||||
/// <para>Whether the object is actually sent to the client will depend
|
||||
/// upon the handler used to send the response.</para>
|
||||
/// </summary>
|
||||
object? DataObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets necessary headers, as required by the nature
|
||||
/// of the HTTP exception (e.g. <c>Location</c> for
|
||||
/// <see cref="HttpRedirectException" />).
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the response.</param>
|
||||
void PrepareResponse(IHttpContext context);
|
||||
}
|
||||
}
|
||||
72
Vendor/EmbedIO-3.5.2/IHttpListener.cs
vendored
Normal file
72
Vendor/EmbedIO-3.5.2/IHttpListener.cs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Listener.
|
||||
/// </summary>
|
||||
public interface IHttpListener : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the listener should ignore write exceptions. By default the flag is set on.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [ignore write exceptions]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IgnoreWriteExceptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the prefixes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The prefixes.
|
||||
/// </value>
|
||||
List<string> Prefixes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is listening.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsListening { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this listener.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stops this listener.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1716 // Rename method to avoid conflict with (VB) keyword - It is consistent with Microsoft's HttpListener
|
||||
void Stop();
|
||||
#pragma warning restore CA1716
|
||||
|
||||
/// <summary>
|
||||
/// Adds the prefix.
|
||||
/// </summary>
|
||||
/// <param name="urlPrefix">The URL prefix.</param>
|
||||
void AddPrefix(string urlPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP context asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the time delay for the HTTP Context.
|
||||
/// </returns>
|
||||
Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
26
Vendor/EmbedIO-3.5.2/IHttpMessage.cs
vendored
Normal file
26
Vendor/EmbedIO-3.5.2/IHttpMessage.cs
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a HTTP request or response.
|
||||
/// </summary>
|
||||
public interface IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the cookies.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cookies.
|
||||
/// </value>
|
||||
ICookieCollection Cookies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the protocol version.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
Version ProtocolVersion { get; }
|
||||
}
|
||||
}
|
||||
115
Vendor/EmbedIO-3.5.2/IHttpRequest.cs
vendored
Normal file
115
Vendor/EmbedIO-3.5.2/IHttpRequest.cs
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Request.
|
||||
/// </summary>
|
||||
public interface IHttpRequest : IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the request headers.
|
||||
/// </summary>
|
||||
NameValueCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether [keep alive].
|
||||
/// </summary>
|
||||
bool KeepAlive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw URL.
|
||||
/// </summary>
|
||||
string RawUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query string.
|
||||
/// </summary>
|
||||
NameValueCollection QueryString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP method.
|
||||
/// </summary>
|
||||
string HttpMethod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HttpVerbs"/> constant representing the HTTP method of the request.
|
||||
/// </summary>
|
||||
HttpVerbs HttpVerb { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL.
|
||||
/// </summary>
|
||||
Uri Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has entity body.
|
||||
/// </summary>
|
||||
bool HasEntityBody { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input stream.
|
||||
/// </summary>
|
||||
Stream InputStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content encoding.
|
||||
/// </summary>
|
||||
Encoding ContentEncoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote end point.
|
||||
/// </summary>
|
||||
IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is local.
|
||||
/// </summary>
|
||||
bool IsLocal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this request has been received over a SSL connection.
|
||||
/// </summary>
|
||||
bool IsSecureConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user agent.
|
||||
/// </summary>
|
||||
string UserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is web socket request.
|
||||
/// </summary>
|
||||
bool IsWebSocketRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local end point.
|
||||
/// </summary>
|
||||
IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content.
|
||||
/// </summary>
|
||||
string? ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content length.
|
||||
/// </summary>
|
||||
long ContentLength64 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is authenticated.
|
||||
/// </summary>
|
||||
bool IsAuthenticated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL referrer.
|
||||
/// </summary>
|
||||
Uri? UrlReferrer { get; }
|
||||
}
|
||||
}
|
||||
69
Vendor/EmbedIO-3.5.2/IHttpResponse.cs
vendored
Normal file
69
Vendor/EmbedIO-3.5.2/IHttpResponse.cs
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Interface to create a HTTP Response.
|
||||
/// </summary>
|
||||
public interface IHttpResponse : IHttpMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the response headers.
|
||||
/// </summary>
|
||||
WebHeaderCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code.
|
||||
/// </summary>
|
||||
int StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content length.
|
||||
/// </summary>
|
||||
long ContentLength64 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the content.
|
||||
/// </summary>
|
||||
string ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the output stream.
|
||||
/// </summary>
|
||||
Stream OutputStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content encoding.
|
||||
/// </summary>
|
||||
Encoding? ContentEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [keep alive].
|
||||
/// </summary>
|
||||
bool KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the response uses chunked transfer encoding.
|
||||
/// </summary>
|
||||
bool SendChunked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a text description of the HTTP status code.
|
||||
/// </summary>
|
||||
string StatusDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cookie.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The session cookie.</param>
|
||||
void SetCookie(Cookie cookie);
|
||||
|
||||
/// <summary>
|
||||
/// Closes this instance and dispose the resources.
|
||||
/// </summary>
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
45
Vendor/EmbedIO-3.5.2/IMimeTypeCustomizer.cs
vendored
Normal file
45
Vendor/EmbedIO-3.5.2/IMimeTypeCustomizer.cs
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that can set information about specific MIME types and media ranges,
|
||||
/// to be later retrieved via an <see cref="IMimeTypeProvider"/> interface.
|
||||
/// </summary>
|
||||
/// <seealso cref="IMimeTypeProvider" />
|
||||
public interface IMimeTypeCustomizer : IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a custom association between a file extension and a MIME type.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension to associate to <paramref name="mimeType"/>.</param>
|
||||
/// <param name="mimeType">The MIME type to associate to <paramref name="extension"/>.</param>
|
||||
/// <exception cref="InvalidOperationException">The object implementing <see cref="IMimeTypeCustomizer"/>
|
||||
/// has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="extension"/>is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="extension"/>is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is not a valid MIME type.</para>
|
||||
/// </exception>
|
||||
void AddCustomMimeType(string extension, string mimeType);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to prefer compression when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> to prefer compression;
|
||||
/// otherwise, <see langword="false"/>.</param>
|
||||
/// <exception cref="InvalidOperationException">The object implementing <see cref="IMimeTypeCustomizer"/>
|
||||
/// has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
void PreferCompression(string mimeType, bool preferCompression);
|
||||
}
|
||||
}
|
||||
31
Vendor/EmbedIO-3.5.2/IMimeTypeProvider.cs
vendored
Normal file
31
Vendor/EmbedIO-3.5.2/IMimeTypeProvider.cs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that contains information on specific MIME types and media ranges.
|
||||
/// </summary>
|
||||
public interface IMimeTypeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type associated to a file extension.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension for which a corresponding MIME type is wanted.</param>
|
||||
/// <returns>The MIME type corresponding to <paramref name="extension"/>, if one is found;
|
||||
/// otherwise, <see langword="null"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="extension"/>is <see langword="null"/>.</exception>
|
||||
string GetMimeType(string extension);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to determine whether compression should be preferred
|
||||
/// when negotiating content encoding for a response with the specified content type.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type to check.</param>
|
||||
/// <param name="preferCompression">When this method returns <see langword="true"/>,
|
||||
/// a value indicating whether compression should be preferred.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <returns><see langword="true"/> if a value is found for <paramref name="mimeType"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
bool TryDetermineCompression(string mimeType, out bool preferCompression);
|
||||
}
|
||||
}
|
||||
81
Vendor/EmbedIO-3.5.2/IWebModule.cs
vendored
Normal file
81
Vendor/EmbedIO-3.5.2/IWebModule.cs
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Routing;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a module.
|
||||
/// </summary>
|
||||
public interface IWebModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base route of a module.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The base route.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>A base route is either "/" (the root path),
|
||||
/// or a prefix starting and ending with a '/' character.</para>
|
||||
/// </remarks>
|
||||
string BaseRoute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether processing of a request should stop
|
||||
/// after a module has handled it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If this property is <see langword="true" />, a HTTP context's
|
||||
/// <see cref="IHttpContext.SetHandled" /> method will be automatically called
|
||||
/// immediately after after the <see cref="Task" /> returned by
|
||||
/// <see cref="HandleRequestAsync" /> is completed. This will prevent
|
||||
/// the context from being passed further along to other modules.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IHttpContext.IsHandled" />
|
||||
/// <seealso cref="IHttpContext.SetHandled" />
|
||||
bool IsFinalHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time an unhandled exception
|
||||
/// occurs during the processing of a request.</para>
|
||||
/// <para>If this property is <see langword="null"/> (the default),
|
||||
/// the exception will be handled by the web server, or by the containing
|
||||
/// <see cref="ModuleGroup"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="ExceptionHandler"/>
|
||||
ExceptionHandlerCallback? OnUnhandledException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time a HTTP exception
|
||||
/// is thrown during the processing of a request.</para>
|
||||
/// <para>If this property is <see langword="null"/> (the default),
|
||||
/// the exception will be handled by the web server, or by the containing
|
||||
/// <see cref="ModuleGroup"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
HttpExceptionHandlerCallback? OnHttpException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signals a module that the web server is starting.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to stop the web server.</param>
|
||||
void Start(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Matches the specified URL path against a module's <see cref="BaseRoute"/>,
|
||||
/// extracting values for the route's parameters and a sub-path.
|
||||
/// </summary>
|
||||
/// <param name="urlPath">The URL path to match.</param>
|
||||
/// <returns>If the match is successful, a <see cref="RouteMatch"/> object;
|
||||
/// otherwise, <see langword="null"/>.</returns>
|
||||
RouteMatch MatchUrlPath(string urlPath);
|
||||
|
||||
/// <summary>
|
||||
/// Handles a request from a client.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the request being handled.</param>
|
||||
/// <returns>A <see cref="Task" /> representing the ongoing operation.</returns>
|
||||
Task HandleRequestAsync(IHttpContext context);
|
||||
}
|
||||
}
|
||||
19
Vendor/EmbedIO-3.5.2/IWebModuleContainer.cs
vendored
Normal file
19
Vendor/EmbedIO-3.5.2/IWebModuleContainer.cs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an object that contains a collection of <see cref="IWebModule"/> interfaces.
|
||||
/// </summary>
|
||||
public interface IWebModuleContainer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the modules.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The modules.
|
||||
/// </value>
|
||||
IComponentCollection<IWebModule> Modules { get; }
|
||||
}
|
||||
}
|
||||
68
Vendor/EmbedIO-3.5.2/IWebServer.cs
vendored
Normal file
68
Vendor/EmbedIO-3.5.2/IWebServer.cs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Sessions;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Represents a web server.</para>
|
||||
/// <para>The basic usage of a web server is as follows:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>add modules to the <see cref="IWebModuleContainer.Modules">Modules</see> collection;</description></item>
|
||||
/// <item><description>set a <see cref="SessionManager"/> if needed;</description></item>
|
||||
/// <item><description>call <see cref="RunAsync"/> to respond to incoming requests.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public interface IWebServer : IWebModuleContainer, IMimeTypeCustomizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="State"/> property changes.
|
||||
/// </summary>
|
||||
event WebServerStateChangedEventHandler StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time an unhandled exception
|
||||
/// occurs during the processing of a request.</para>
|
||||
/// <para>This property can never be <see langword="null"/>.
|
||||
/// If it is still </para>
|
||||
/// </summary>
|
||||
/// <seealso cref="ExceptionHandler"/>
|
||||
ExceptionHandlerCallback OnUnhandledException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a callback that is called every time a HTTP exception
|
||||
/// is thrown during the processing of a request.</para>
|
||||
/// <para>This property can never be <see langword="null"/>.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="HttpExceptionHandler"/>
|
||||
HttpExceptionHandlerCallback OnHttpException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>Gets or sets the registered session ID manager, if any.</para>
|
||||
/// <para>A session ID manager is an implementation of <see cref="ISessionManager"/>.</para>
|
||||
/// <para>Note that this property can only be set before starting the web server.</para>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The session manager, or <see langword="null"/> if no session manager is present.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and the web server has already been started.</exception>
|
||||
ISessionManager? SessionManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the web server.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
/// <seealso cref="WebServerState"/>
|
||||
WebServerState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener and the registered modules.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token; when cancelled, the server cancels all pending requests and stops.</param>
|
||||
/// <returns>
|
||||
/// Returns the task that the HTTP listener is running inside of, so that it can be waited upon after it's been canceled.
|
||||
/// </returns>
|
||||
Task RunAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
87
Vendor/EmbedIO-3.5.2/Internal/BufferingResponseStream.cs
vendored
Normal file
87
Vendor/EmbedIO-3.5.2/Internal/BufferingResponseStream.cs
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
// Wraps a response's output stream, buffering all data
|
||||
// in a MemoryStream.
|
||||
// When disposed, sets the response's ContentLength and copies all data
|
||||
// to the output stream.
|
||||
internal class BufferingResponseStream : Stream
|
||||
{
|
||||
private readonly IHttpResponse _response;
|
||||
private readonly MemoryStream _buffer;
|
||||
|
||||
public BufferingResponseStream(IHttpResponse response)
|
||||
{
|
||||
_response = response;
|
||||
_buffer = new MemoryStream();
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => _buffer.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _buffer.Position;
|
||||
set => throw SeekingNotSupported();
|
||||
}
|
||||
|
||||
public override void Flush() => _buffer.Flush();
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) => _buffer.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
|
||||
|
||||
public override int ReadByte() => throw ReadingNotSupported();
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
|
||||
|
||||
public override void SetLength(long value) => throw SeekingNotSupported();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => _buffer.Write(buffer, offset, count);
|
||||
|
||||
public override void WriteByte(byte value) => _buffer.WriteByte(value);
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> _buffer.BeginWrite(buffer, offset, count, callback, state);
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult) => _buffer.EndWrite(asyncResult);
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
=> _buffer.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_response.ContentLength64 = _buffer.Length;
|
||||
_buffer.Position = 0;
|
||||
_buffer.CopyTo(_response.OutputStream);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_buffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
|
||||
|
||||
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
|
||||
}
|
||||
}
|
||||
121
Vendor/EmbedIO-3.5.2/Internal/CompressionStream.cs
vendored
Normal file
121
Vendor/EmbedIO-3.5.2/Internal/CompressionStream.cs
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal class CompressionStream : Stream
|
||||
{
|
||||
private readonly Stream _target;
|
||||
private readonly bool _leaveOpen;
|
||||
|
||||
public CompressionStream(Stream target, CompressionMethod compressionMethod)
|
||||
{
|
||||
switch (compressionMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
_target = new DeflateStream(target, CompressionMode.Compress, true);
|
||||
_leaveOpen = false;
|
||||
break;
|
||||
case CompressionMethod.Gzip:
|
||||
_target = new GZipStream(target, CompressionMode.Compress, true);
|
||||
_leaveOpen = false;
|
||||
break;
|
||||
default:
|
||||
_target = target;
|
||||
_leaveOpen = true;
|
||||
break;
|
||||
}
|
||||
|
||||
UncompressedLength = 0;
|
||||
}
|
||||
|
||||
public long UncompressedLength { get; private set; }
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw SeekingNotSupported();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw SeekingNotSupported();
|
||||
set => throw SeekingNotSupported();
|
||||
}
|
||||
|
||||
public override void Flush() => _target.Flush();
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken) => _target.FlushAsync(cancellationToken);
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
|
||||
|
||||
public override int ReadByte() => throw ReadingNotSupported();
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
=> throw ReadingNotSupported();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
|
||||
|
||||
public override void SetLength(long value) => throw SeekingNotSupported();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_target.Write(buffer, offset, count);
|
||||
UncompressedLength += count;
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
_target.WriteByte(value);
|
||||
UncompressedLength++;
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
=> _target.BeginWrite(
|
||||
buffer,
|
||||
offset,
|
||||
count,
|
||||
ar => {
|
||||
UncompressedLength += count;
|
||||
callback(ar);
|
||||
},
|
||||
state);
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
_target.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await _target.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
UncompressedLength += count;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && !_leaveOpen)
|
||||
{
|
||||
_target.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
|
||||
|
||||
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
|
||||
}
|
||||
}
|
||||
82
Vendor/EmbedIO-3.5.2/Internal/CompressionUtility.cs
vendored
Normal file
82
Vendor/EmbedIO-3.5.2/Internal/CompressionUtility.cs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal static class CompressionUtility
|
||||
{
|
||||
public static byte[]? ConvertCompression(byte[] source, CompressionMethod sourceMethod, CompressionMethod targetMethod)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
if (sourceMethod == targetMethod)
|
||||
return source;
|
||||
|
||||
switch (sourceMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var decompressionStream = new DeflateStream(sourceStream, CompressionMode.Decompress, true);
|
||||
using var targetStream = new MemoryStream();
|
||||
if (targetMethod == CompressionMethod.Gzip)
|
||||
{
|
||||
using var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true);
|
||||
decompressionStream.CopyTo(compressionStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressionStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
|
||||
case CompressionMethod.Gzip:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var decompressionStream = new GZipStream(sourceStream, CompressionMode.Decompress, true);
|
||||
using var targetStream = new MemoryStream();
|
||||
if (targetMethod == CompressionMethod.Deflate)
|
||||
{
|
||||
using var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true);
|
||||
decompressionStream.CopyToAsync(compressionStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
decompressionStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
|
||||
default:
|
||||
using (var sourceStream = new MemoryStream(source, false))
|
||||
{
|
||||
using var targetStream = new MemoryStream();
|
||||
switch (targetMethod)
|
||||
{
|
||||
case CompressionMethod.Deflate:
|
||||
using (var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true))
|
||||
sourceStream.CopyTo(compressionStream);
|
||||
|
||||
break;
|
||||
|
||||
case CompressionMethod.Gzip:
|
||||
using (var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true))
|
||||
sourceStream.CopyTo(compressionStream);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Just in case. Consider all other values as None.
|
||||
return source;
|
||||
}
|
||||
|
||||
return targetStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/Internal/DummyWebModuleContainer.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/Internal/DummyWebModuleContainer.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class DummyWebModuleContainer : IWebModuleContainer
|
||||
{
|
||||
public static readonly IWebModuleContainer Instance = new DummyWebModuleContainer();
|
||||
|
||||
private DummyWebModuleContainer()
|
||||
{
|
||||
}
|
||||
|
||||
public IComponentCollection<IWebModule> Modules => throw UnexpectedCall();
|
||||
|
||||
public ConcurrentDictionary<object, object> SharedItems => throw UnexpectedCall();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private InternalErrorException UnexpectedCall([CallerMemberName] string member = "")
|
||||
=> SelfCheck.Failure($"Unexpected call to {nameof(DummyWebModuleContainer)}.{member}.");
|
||||
}
|
||||
}
|
||||
9
Vendor/EmbedIO-3.5.2/Internal/LockableNameValueCollection.cs
vendored
Normal file
9
Vendor/EmbedIO-3.5.2/Internal/LockableNameValueCollection.cs
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class LockableNameValueCollection : NameValueCollection
|
||||
{
|
||||
public void MakeReadOnly() => IsReadOnly = true;
|
||||
}
|
||||
}
|
||||
63
Vendor/EmbedIO-3.5.2/Internal/MimeTypeCustomizer.cs
vendored
Normal file
63
Vendor/EmbedIO-3.5.2/Internal/MimeTypeCustomizer.cs
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Configuration;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class MimeTypeCustomizer : ConfiguredObject, IMimeTypeCustomizer
|
||||
{
|
||||
private readonly Dictionary<string, string> _customMimeTypes = new Dictionary<string, string>();
|
||||
private readonly Dictionary<(string, string), bool> _data = new Dictionary<(string, string), bool>();
|
||||
|
||||
private bool? _defaultPreferCompression;
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
{
|
||||
_customMimeTypes.TryGetValue(Validate.NotNull(nameof(extension), extension), out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
{
|
||||
var (type, subtype) = MimeType.UnsafeSplit(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, false));
|
||||
|
||||
if (_data.TryGetValue((type, subtype), out preferCompression))
|
||||
return true;
|
||||
|
||||
if (_data.TryGetValue((type, "*"), out preferCompression))
|
||||
return true;
|
||||
|
||||
if (!_defaultPreferCompression.HasValue)
|
||||
return false;
|
||||
|
||||
preferCompression = _defaultPreferCompression.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_customMimeTypes[Validate.NotNullOrEmpty(nameof(extension), extension)]
|
||||
= Validate.MimeType(nameof(mimeType), mimeType, false);
|
||||
}
|
||||
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
var (type, subtype) = MimeType.UnsafeSplit(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, true));
|
||||
|
||||
if (type == "*")
|
||||
{
|
||||
_defaultPreferCompression = preferCompression;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[(type, subtype)] = preferCompression;
|
||||
}
|
||||
}
|
||||
|
||||
public void Lock() => LockConfiguration();
|
||||
}
|
||||
}
|
||||
15
Vendor/EmbedIO-3.5.2/Internal/RequestHandlerPassThroughException.cs
vendored
Normal file
15
Vendor/EmbedIO-3.5.2/Internal/RequestHandlerPassThroughException.cs
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
// This exception is only created and handled internally,
|
||||
// so it doesn't need all the standard the bells and whistles.
|
||||
#pragma warning disable CA1032 // Add standard exception constructors
|
||||
#pragma warning disable CA1064 // Exceptions should be public
|
||||
|
||||
internal class RequestHandlerPassThroughException : Exception
|
||||
{
|
||||
}
|
||||
#pragma warning restore CA1032
|
||||
#pragma warning restore CA1064
|
||||
}
|
||||
27
Vendor/EmbedIO-3.5.2/Internal/TimeKeeper.cs
vendored
Normal file
27
Vendor/EmbedIO-3.5.2/Internal/TimeKeeper.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper around Stopwatch.
|
||||
/// </summary>
|
||||
public sealed class TimeKeeper
|
||||
{
|
||||
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
|
||||
|
||||
private readonly long _start;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimeKeeper"/> class.
|
||||
/// </summary>
|
||||
public TimeKeeper()
|
||||
{
|
||||
_start = Stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time since the class was initialized.
|
||||
/// </summary>
|
||||
public long ElapsedTime => Stopwatch.ElapsedMilliseconds - _start;
|
||||
}
|
||||
}
|
||||
47
Vendor/EmbedIO-3.5.2/Internal/UriUtility.cs
vendored
Normal file
47
Vendor/EmbedIO-3.5.2/Internal/UriUtility.cs
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal static class UriUtility
|
||||
{
|
||||
public static Uri StringToUri(string str)
|
||||
{
|
||||
_ = Uri.TryCreate(str, CanBeAbsoluteUrl(str) ? UriKind.Absolute : UriKind.Relative, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Uri? StringToAbsoluteUri(string str)
|
||||
{
|
||||
if (!CanBeAbsoluteUrl(str))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_ = Uri.TryCreate(str, UriKind.Absolute, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if string starts with "http:", "https:", "ws:", or "wss:"
|
||||
private static bool CanBeAbsoluteUrl(string str)
|
||||
=> !string.IsNullOrEmpty(str)
|
||||
&& str[0] switch {
|
||||
'h' => str.Length >= 5
|
||||
&& str[1] == 't'
|
||||
&& str[2] == 't'
|
||||
&& str[3] == 'p'
|
||||
&& str[4] switch {
|
||||
':' => true,
|
||||
's' => str.Length >= 6 && str[5] == ':',
|
||||
_ => false
|
||||
},
|
||||
'w' => str.Length >= 3
|
||||
&& str[1] == 's'
|
||||
&& str[2] switch {
|
||||
':' => true,
|
||||
's' => str.Length >= 4 && str[3] == ':',
|
||||
_ => false
|
||||
},
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
46
Vendor/EmbedIO-3.5.2/Internal/WebModuleCollection.cs
vendored
Normal file
46
Vendor/EmbedIO-3.5.2/Internal/WebModuleCollection.cs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Internal
|
||||
{
|
||||
internal sealed class WebModuleCollection : DisposableComponentCollection<IWebModule>
|
||||
{
|
||||
private readonly string _logSource;
|
||||
|
||||
internal WebModuleCollection(string logSource)
|
||||
{
|
||||
_logSource = logSource;
|
||||
}
|
||||
|
||||
internal void StartAll(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var (name, module) in WithSafeNames)
|
||||
{
|
||||
$"Starting module {name}...".Debug(_logSource);
|
||||
module.Start(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task DispatchRequestAsync(IHttpContext context)
|
||||
{
|
||||
if (context.IsHandled)
|
||||
return;
|
||||
|
||||
var requestedPath = context.RequestedPath;
|
||||
foreach (var (name, module) in WithSafeNames)
|
||||
{
|
||||
var routeMatch = module.MatchUrlPath(requestedPath);
|
||||
if (routeMatch == null)
|
||||
continue;
|
||||
|
||||
$"[{context.Id}] Processing with {name}.".Debug(_logSource);
|
||||
context.GetImplementation().Route = routeMatch;
|
||||
await module.HandleRequestAsync(context).ConfigureAwait(false);
|
||||
if (context.IsHandled)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
648
Vendor/EmbedIO-3.5.2/MimeType.Associations.cs
vendored
Normal file
648
Vendor/EmbedIO-3.5.2/MimeType.Associations.cs
vendored
Normal file
@@ -0,0 +1,648 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
partial class MimeType
|
||||
{
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// IMPORTANT NOTE TO CONTRIBUTORS
|
||||
// ==============================
|
||||
//
|
||||
// When you update the MIME type list, remember to:
|
||||
//
|
||||
// * update the date in XML docs below;
|
||||
//
|
||||
// * check the LICENSE file to see if copyright year and/or license conditions have changed;
|
||||
//
|
||||
// * if the URL for the LICENSE file has changed, update EmbedIO's LICENSE file too.
|
||||
//
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// <para>Associates file extensions to MIME types.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The list of MIME types has been copied from
|
||||
/// <see href="https://github.com/samuelneff/MimeTypeMap/blob/master/src/MimeTypes/MimeTypeMap.cs">Samuel Neff's MIME Type Map</see>
|
||||
/// on April 26th, 2019.</para>
|
||||
/// <para>Copyright (c) 2014 Samuel Neff. Redistributed under <see href="https://github.com/samuelneff/MimeTypeMap/blob/master/LICENSE">MIT license</see>.</para>
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<string, string> Associations { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{".323", "text/h323"},
|
||||
{".3g2", "video/3gpp2"},
|
||||
{".3gp", "video/3gpp"},
|
||||
{".3gp2", "video/3gpp2"},
|
||||
{".3gpp", "video/3gpp"},
|
||||
{".7z", "application/x-7z-compressed"},
|
||||
{".aa", "audio/audible"},
|
||||
{".AAC", "audio/aac"},
|
||||
{".aaf", "application/octet-stream"},
|
||||
{".aax", "audio/vnd.audible.aax"},
|
||||
{".ac3", "audio/ac3"},
|
||||
{".aca", "application/octet-stream"},
|
||||
{".accda", "application/msaccess.addin"},
|
||||
{".accdb", "application/msaccess"},
|
||||
{".accdc", "application/msaccess.cab"},
|
||||
{".accde", "application/msaccess"},
|
||||
{".accdr", "application/msaccess.runtime"},
|
||||
{".accdt", "application/msaccess"},
|
||||
{".accdw", "application/msaccess.webapplication"},
|
||||
{".accft", "application/msaccess.ftemplate"},
|
||||
{".acx", "application/internet-property-stream"},
|
||||
{".AddIn", "text/xml"},
|
||||
{".ade", "application/msaccess"},
|
||||
{".adobebridge", "application/x-bridge-url"},
|
||||
{".adp", "application/msaccess"},
|
||||
{".ADT", "audio/vnd.dlna.adts"},
|
||||
{".ADTS", "audio/aac"},
|
||||
{".afm", "application/octet-stream"},
|
||||
{".ai", "application/postscript"},
|
||||
{".aif", "audio/aiff"},
|
||||
{".aifc", "audio/aiff"},
|
||||
{".aiff", "audio/aiff"},
|
||||
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
|
||||
{".amc", "application/mpeg"},
|
||||
{".anx", "application/annodex"},
|
||||
{".apk", "application/vnd.android.package-archive" },
|
||||
{".application", "application/x-ms-application"},
|
||||
{".art", "image/x-jg"},
|
||||
{".asa", "application/xml"},
|
||||
{".asax", "application/xml"},
|
||||
{".ascx", "application/xml"},
|
||||
{".asd", "application/octet-stream"},
|
||||
{".asf", "video/x-ms-asf"},
|
||||
{".ashx", "application/xml"},
|
||||
{".asi", "application/octet-stream"},
|
||||
{".asm", "text/plain"},
|
||||
{".asmx", "application/xml"},
|
||||
{".aspx", "application/xml"},
|
||||
{".asr", "video/x-ms-asf"},
|
||||
{".asx", "video/x-ms-asf"},
|
||||
{".atom", "application/atom+xml"},
|
||||
{".au", "audio/basic"},
|
||||
{".avi", "video/x-msvideo"},
|
||||
{".axa", "audio/annodex"},
|
||||
{".axs", "application/olescript"},
|
||||
{".axv", "video/annodex"},
|
||||
{".bas", "text/plain"},
|
||||
{".bcpio", "application/x-bcpio"},
|
||||
{".bin", "application/octet-stream"},
|
||||
{".bmp", "image/bmp"},
|
||||
{".c", "text/plain"},
|
||||
{".cab", "application/octet-stream"},
|
||||
{".caf", "audio/x-caf"},
|
||||
{".calx", "application/vnd.ms-office.calx"},
|
||||
{".cat", "application/vnd.ms-pki.seccat"},
|
||||
{".cc", "text/plain"},
|
||||
{".cd", "text/plain"},
|
||||
{".cdda", "audio/aiff"},
|
||||
{".cdf", "application/x-cdf"},
|
||||
{".cer", "application/x-x509-ca-cert"},
|
||||
{".cfg", "text/plain"},
|
||||
{".chm", "application/octet-stream"},
|
||||
{".class", "application/x-java-applet"},
|
||||
{".clp", "application/x-msclip"},
|
||||
{".cmd", "text/plain"},
|
||||
{".cmx", "image/x-cmx"},
|
||||
{".cnf", "text/plain"},
|
||||
{".cod", "image/cis-cod"},
|
||||
{".config", "application/xml"},
|
||||
{".contact", "text/x-ms-contact"},
|
||||
{".coverage", "application/xml"},
|
||||
{".cpio", "application/x-cpio"},
|
||||
{".cpp", "text/plain"},
|
||||
{".crd", "application/x-mscardfile"},
|
||||
{".crl", "application/pkix-crl"},
|
||||
{".crt", "application/x-x509-ca-cert"},
|
||||
{".cs", "text/plain"},
|
||||
{".csdproj", "text/plain"},
|
||||
{".csh", "application/x-csh"},
|
||||
{".csproj", "text/plain"},
|
||||
{".css", "text/css"},
|
||||
{".csv", "text/csv"},
|
||||
{".cur", "application/octet-stream"},
|
||||
{".cxx", "text/plain"},
|
||||
{".dat", "application/octet-stream"},
|
||||
{".datasource", "application/xml"},
|
||||
{".dbproj", "text/plain"},
|
||||
{".dcr", "application/x-director"},
|
||||
{".def", "text/plain"},
|
||||
{".deploy", "application/octet-stream"},
|
||||
{".der", "application/x-x509-ca-cert"},
|
||||
{".dgml", "application/xml"},
|
||||
{".dib", "image/bmp"},
|
||||
{".dif", "video/x-dv"},
|
||||
{".dir", "application/x-director"},
|
||||
{".disco", "text/xml"},
|
||||
{".divx", "video/divx"},
|
||||
{".dll", "application/x-msdownload"},
|
||||
{".dll.config", "text/xml"},
|
||||
{".dlm", "text/dlm"},
|
||||
{".doc", "application/msword"},
|
||||
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
|
||||
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
||||
{".dot", "application/msword"},
|
||||
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
|
||||
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
|
||||
{".dsp", "application/octet-stream"},
|
||||
{".dsw", "text/plain"},
|
||||
{".dtd", "text/xml"},
|
||||
{".dtsConfig", "text/xml"},
|
||||
{".dv", "video/x-dv"},
|
||||
{".dvi", "application/x-dvi"},
|
||||
{".dwf", "drawing/x-dwf"},
|
||||
{".dwg", "application/acad"},
|
||||
{".dwp", "application/octet-stream"},
|
||||
{".dxf", "application/x-dxf" },
|
||||
{".dxr", "application/x-director"},
|
||||
{".eml", "message/rfc822"},
|
||||
{".emz", "application/octet-stream"},
|
||||
{".eot", "application/vnd.ms-fontobject"},
|
||||
{".eps", "application/postscript"},
|
||||
{".es", "application/ecmascript"},
|
||||
{".etl", "application/etl"},
|
||||
{".etx", "text/x-setext"},
|
||||
{".evy", "application/envoy"},
|
||||
{".exe", "application/octet-stream"},
|
||||
{".exe.config", "text/xml"},
|
||||
{".fdf", "application/vnd.fdf"},
|
||||
{".fif", "application/fractals"},
|
||||
{".filters", "application/xml"},
|
||||
{".fla", "application/octet-stream"},
|
||||
{".flac", "audio/flac"},
|
||||
{".flr", "x-world/x-vrml"},
|
||||
{".flv", "video/x-flv"},
|
||||
{".fsscript", "application/fsharp-script"},
|
||||
{".fsx", "application/fsharp-script"},
|
||||
{".generictest", "application/xml"},
|
||||
{".gif", "image/gif"},
|
||||
{".gpx", "application/gpx+xml"},
|
||||
{".group", "text/x-ms-group"},
|
||||
{".gsm", "audio/x-gsm"},
|
||||
{".gtar", "application/x-gtar"},
|
||||
{".gz", "application/x-gzip"},
|
||||
{".h", "text/plain"},
|
||||
{".hdf", "application/x-hdf"},
|
||||
{".hdml", "text/x-hdml"},
|
||||
{".hhc", "application/x-oleobject"},
|
||||
{".hhk", "application/octet-stream"},
|
||||
{".hhp", "application/octet-stream"},
|
||||
{".hlp", "application/winhlp"},
|
||||
{".hpp", "text/plain"},
|
||||
{".hqx", "application/mac-binhex40"},
|
||||
{".hta", "application/hta"},
|
||||
{".htc", "text/x-component"},
|
||||
{".htm", "text/html"},
|
||||
{".html", "text/html"},
|
||||
{".htt", "text/webviewhtml"},
|
||||
{".hxa", "application/xml"},
|
||||
{".hxc", "application/xml"},
|
||||
{".hxd", "application/octet-stream"},
|
||||
{".hxe", "application/xml"},
|
||||
{".hxf", "application/xml"},
|
||||
{".hxh", "application/octet-stream"},
|
||||
{".hxi", "application/octet-stream"},
|
||||
{".hxk", "application/xml"},
|
||||
{".hxq", "application/octet-stream"},
|
||||
{".hxr", "application/octet-stream"},
|
||||
{".hxs", "application/octet-stream"},
|
||||
{".hxt", "text/html"},
|
||||
{".hxv", "application/xml"},
|
||||
{".hxw", "application/octet-stream"},
|
||||
{".hxx", "text/plain"},
|
||||
{".i", "text/plain"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".ics", "application/octet-stream"},
|
||||
{".idl", "text/plain"},
|
||||
{".ief", "image/ief"},
|
||||
{".iii", "application/x-iphone"},
|
||||
{".inc", "text/plain"},
|
||||
{".inf", "application/octet-stream"},
|
||||
{".ini", "text/plain"},
|
||||
{".inl", "text/plain"},
|
||||
{".ins", "application/x-internet-signup"},
|
||||
{".ipa", "application/x-itunes-ipa"},
|
||||
{".ipg", "application/x-itunes-ipg"},
|
||||
{".ipproj", "text/plain"},
|
||||
{".ipsw", "application/x-itunes-ipsw"},
|
||||
{".iqy", "text/x-ms-iqy"},
|
||||
{".isp", "application/x-internet-signup"},
|
||||
{".ite", "application/x-itunes-ite"},
|
||||
{".itlp", "application/x-itunes-itlp"},
|
||||
{".itms", "application/x-itunes-itms"},
|
||||
{".itpc", "application/x-itunes-itpc"},
|
||||
{".IVF", "video/x-ivf"},
|
||||
{".jar", "application/java-archive"},
|
||||
{".java", "application/octet-stream"},
|
||||
{".jck", "application/liquidmotion"},
|
||||
{".jcz", "application/liquidmotion"},
|
||||
{".jfif", "image/pjpeg"},
|
||||
{".jnlp", "application/x-java-jnlp-file"},
|
||||
{".jpb", "application/octet-stream"},
|
||||
{".jpe", "image/jpeg"},
|
||||
{".jpeg", "image/jpeg"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".js", "application/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".jsx", "text/jscript"},
|
||||
{".jsxbin", "text/plain"},
|
||||
{".latex", "application/x-latex"},
|
||||
{".library-ms", "application/windows-library+xml"},
|
||||
{".lit", "application/x-ms-reader"},
|
||||
{".loadtest", "application/xml"},
|
||||
{".lpk", "application/octet-stream"},
|
||||
{".lsf", "video/x-la-asf"},
|
||||
{".lst", "text/plain"},
|
||||
{".lsx", "video/x-la-asf"},
|
||||
{".lzh", "application/octet-stream"},
|
||||
{".m13", "application/x-msmediaview"},
|
||||
{".m14", "application/x-msmediaview"},
|
||||
{".m1v", "video/mpeg"},
|
||||
{".m2t", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2v", "video/mpeg"},
|
||||
{".m3u", "audio/x-mpegurl"},
|
||||
{".m3u8", "audio/x-mpegurl"},
|
||||
{".m4a", "audio/m4a"},
|
||||
{".m4b", "audio/m4b"},
|
||||
{".m4p", "audio/m4p"},
|
||||
{".m4r", "audio/x-m4r"},
|
||||
{".m4v", "video/x-m4v"},
|
||||
{".mac", "image/x-macpaint"},
|
||||
{".mak", "text/plain"},
|
||||
{".man", "application/x-troff-man"},
|
||||
{".manifest", "application/x-ms-manifest"},
|
||||
{".map", "text/plain"},
|
||||
{".master", "application/xml"},
|
||||
{".mbox", "application/mbox"},
|
||||
{".mda", "application/msaccess"},
|
||||
{".mdb", "application/x-msaccess"},
|
||||
{".mde", "application/msaccess"},
|
||||
{".mdp", "application/octet-stream"},
|
||||
{".me", "application/x-troff-me"},
|
||||
{".mfp", "application/x-shockwave-flash"},
|
||||
{".mht", "message/rfc822"},
|
||||
{".mhtml", "message/rfc822"},
|
||||
{".mid", "audio/mid"},
|
||||
{".midi", "audio/mid"},
|
||||
{".mix", "application/octet-stream"},
|
||||
{".mk", "text/plain"},
|
||||
{".mk3d", "video/x-matroska-3d"},
|
||||
{".mka", "audio/x-matroska"},
|
||||
{".mkv", "video/x-matroska"},
|
||||
{".mmf", "application/x-smaf"},
|
||||
{".mno", "text/xml"},
|
||||
{".mny", "application/x-msmoney"},
|
||||
{".mod", "video/mpeg"},
|
||||
{".mov", "video/quicktime"},
|
||||
{".movie", "video/x-sgi-movie"},
|
||||
{".mp2", "video/mpeg"},
|
||||
{".mp2v", "video/mpeg"},
|
||||
{".mp3", "audio/mpeg"},
|
||||
{".mp4", "video/mp4"},
|
||||
{".mp4v", "video/mp4"},
|
||||
{".mpa", "video/mpeg"},
|
||||
{".mpe", "video/mpeg"},
|
||||
{".mpeg", "video/mpeg"},
|
||||
{".mpf", "application/vnd.ms-mediapackage"},
|
||||
{".mpg", "video/mpeg"},
|
||||
{".mpp", "application/vnd.ms-project"},
|
||||
{".mpv2", "video/mpeg"},
|
||||
{".mqv", "video/quicktime"},
|
||||
{".ms", "application/x-troff-ms"},
|
||||
{".msg", "application/vnd.ms-outlook"},
|
||||
{".msi", "application/octet-stream"},
|
||||
{".mso", "application/octet-stream"},
|
||||
{".mts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".mtx", "application/xml"},
|
||||
{".mvb", "application/x-msmediaview"},
|
||||
{".mvc", "application/x-miva-compiled"},
|
||||
{".mxp", "application/x-mmxp"},
|
||||
{".nc", "application/x-netcdf"},
|
||||
{".nsc", "video/x-ms-asf"},
|
||||
{".nws", "message/rfc822"},
|
||||
{".ocx", "application/octet-stream"},
|
||||
{".oda", "application/oda"},
|
||||
{".odb", "application/vnd.oasis.opendocument.database"},
|
||||
{".odc", "application/vnd.oasis.opendocument.chart"},
|
||||
{".odf", "application/vnd.oasis.opendocument.formula"},
|
||||
{".odg", "application/vnd.oasis.opendocument.graphics"},
|
||||
{".odh", "text/plain"},
|
||||
{".odi", "application/vnd.oasis.opendocument.image"},
|
||||
{".odl", "text/plain"},
|
||||
{".odm", "application/vnd.oasis.opendocument.text-master"},
|
||||
{".odp", "application/vnd.oasis.opendocument.presentation"},
|
||||
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
|
||||
{".odt", "application/vnd.oasis.opendocument.text"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".ogv", "video/ogg"},
|
||||
{".ogx", "application/ogg"},
|
||||
{".one", "application/onenote"},
|
||||
{".onea", "application/onenote"},
|
||||
{".onepkg", "application/onenote"},
|
||||
{".onetmp", "application/onenote"},
|
||||
{".onetoc", "application/onenote"},
|
||||
{".onetoc2", "application/onenote"},
|
||||
{".opus", "audio/ogg"},
|
||||
{".orderedtest", "application/xml"},
|
||||
{".osdx", "application/opensearchdescription+xml"},
|
||||
{".otf", "application/font-sfnt"},
|
||||
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
|
||||
{".oth", "application/vnd.oasis.opendocument.text-web"},
|
||||
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
|
||||
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
|
||||
{".ott", "application/vnd.oasis.opendocument.text-template"},
|
||||
{".oxt", "application/vnd.openofficeorg.extension"},
|
||||
{".p10", "application/pkcs10"},
|
||||
{".p12", "application/x-pkcs12"},
|
||||
{".p7b", "application/x-pkcs7-certificates"},
|
||||
{".p7c", "application/pkcs7-mime"},
|
||||
{".p7m", "application/pkcs7-mime"},
|
||||
{".p7r", "application/x-pkcs7-certreqresp"},
|
||||
{".p7s", "application/pkcs7-signature"},
|
||||
{".pbm", "image/x-portable-bitmap"},
|
||||
{".pcast", "application/x-podcast"},
|
||||
{".pct", "image/pict"},
|
||||
{".pcx", "application/octet-stream"},
|
||||
{".pcz", "application/octet-stream"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".pfb", "application/octet-stream"},
|
||||
{".pfm", "application/octet-stream"},
|
||||
{".pfx", "application/x-pkcs12"},
|
||||
{".pgm", "image/x-portable-graymap"},
|
||||
{".pic", "image/pict"},
|
||||
{".pict", "image/pict"},
|
||||
{".pkgdef", "text/plain"},
|
||||
{".pkgundef", "text/plain"},
|
||||
{".pko", "application/vnd.ms-pki.pko"},
|
||||
{".pls", "audio/scpls"},
|
||||
{".pma", "application/x-perfmon"},
|
||||
{".pmc", "application/x-perfmon"},
|
||||
{".pml", "application/x-perfmon"},
|
||||
{".pmr", "application/x-perfmon"},
|
||||
{".pmw", "application/x-perfmon"},
|
||||
{".png", "image/png"},
|
||||
{".pnm", "image/x-portable-anymap"},
|
||||
{".pnt", "image/x-macpaint"},
|
||||
{".pntg", "image/x-macpaint"},
|
||||
{".pnz", "image/png"},
|
||||
{".pot", "application/vnd.ms-powerpoint"},
|
||||
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
|
||||
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
|
||||
{".ppa", "application/vnd.ms-powerpoint"},
|
||||
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
|
||||
{".ppm", "image/x-portable-pixmap"},
|
||||
{".pps", "application/vnd.ms-powerpoint"},
|
||||
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
|
||||
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
|
||||
{".ppt", "application/vnd.ms-powerpoint"},
|
||||
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
|
||||
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||
{".prf", "application/pics-rules"},
|
||||
{".prm", "application/octet-stream"},
|
||||
{".prx", "application/octet-stream"},
|
||||
{".ps", "application/postscript"},
|
||||
{".psc1", "application/PowerShell"},
|
||||
{".psd", "application/octet-stream"},
|
||||
{".psess", "application/xml"},
|
||||
{".psm", "application/octet-stream"},
|
||||
{".psp", "application/octet-stream"},
|
||||
{".pst", "application/vnd.ms-outlook"},
|
||||
{".pub", "application/x-mspublisher"},
|
||||
{".pwz", "application/vnd.ms-powerpoint"},
|
||||
{".qht", "text/x-html-insertion"},
|
||||
{".qhtm", "text/x-html-insertion"},
|
||||
{".qt", "video/quicktime"},
|
||||
{".qti", "image/x-quicktime"},
|
||||
{".qtif", "image/x-quicktime"},
|
||||
{".qtl", "application/x-quicktimeplayer"},
|
||||
{".qxd", "application/octet-stream"},
|
||||
{".ra", "audio/x-pn-realaudio"},
|
||||
{".ram", "audio/x-pn-realaudio"},
|
||||
{".rar", "application/x-rar-compressed"},
|
||||
{".ras", "image/x-cmu-raster"},
|
||||
{".rat", "application/rat-file"},
|
||||
{".rc", "text/plain"},
|
||||
{".rc2", "text/plain"},
|
||||
{".rct", "text/plain"},
|
||||
{".rdlc", "application/xml"},
|
||||
{".reg", "text/plain"},
|
||||
{".resx", "application/xml"},
|
||||
{".rf", "image/vnd.rn-realflash"},
|
||||
{".rgb", "image/x-rgb"},
|
||||
{".rgs", "text/plain"},
|
||||
{".rm", "application/vnd.rn-realmedia"},
|
||||
{".rmi", "audio/mid"},
|
||||
{".rmp", "application/vnd.rn-rn_music_package"},
|
||||
{".roff", "application/x-troff"},
|
||||
{".rpm", "audio/x-pn-realaudio-plugin"},
|
||||
{".rqy", "text/x-ms-rqy"},
|
||||
{".rtf", "application/rtf"},
|
||||
{".rtx", "text/richtext"},
|
||||
{".rvt", "application/octet-stream" },
|
||||
{".ruleset", "application/xml"},
|
||||
{".s", "text/plain"},
|
||||
{".safariextz", "application/x-safari-safariextz"},
|
||||
{".scd", "application/x-msschedule"},
|
||||
{".scr", "text/plain"},
|
||||
{".sct", "text/scriptlet"},
|
||||
{".sd2", "audio/x-sd2"},
|
||||
{".sdp", "application/sdp"},
|
||||
{".sea", "application/octet-stream"},
|
||||
{".searchConnector-ms", "application/windows-search-connector+xml"},
|
||||
{".setpay", "application/set-payment-initiation"},
|
||||
{".setreg", "application/set-registration-initiation"},
|
||||
{".settings", "application/xml"},
|
||||
{".sgimb", "application/x-sgimb"},
|
||||
{".sgml", "text/sgml"},
|
||||
{".sh", "application/x-sh"},
|
||||
{".shar", "application/x-shar"},
|
||||
{".shtml", "text/html"},
|
||||
{".sit", "application/x-stuffit"},
|
||||
{".sitemap", "application/xml"},
|
||||
{".skin", "application/xml"},
|
||||
{".skp", "application/x-koan" },
|
||||
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
|
||||
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
|
||||
{".slk", "application/vnd.ms-excel"},
|
||||
{".sln", "text/plain"},
|
||||
{".slupkg-ms", "application/x-ms-license"},
|
||||
{".smd", "audio/x-smd"},
|
||||
{".smi", "application/octet-stream"},
|
||||
{".smx", "audio/x-smd"},
|
||||
{".smz", "audio/x-smd"},
|
||||
{".snd", "audio/basic"},
|
||||
{".snippet", "application/xml"},
|
||||
{".snp", "application/octet-stream"},
|
||||
{".sol", "text/plain"},
|
||||
{".sor", "text/plain"},
|
||||
{".spc", "application/x-pkcs7-certificates"},
|
||||
{".spl", "application/futuresplash"},
|
||||
{".spx", "audio/ogg"},
|
||||
{".src", "application/x-wais-source"},
|
||||
{".srf", "text/plain"},
|
||||
{".SSISDeploymentManifest", "text/xml"},
|
||||
{".ssm", "application/streamingmedia"},
|
||||
{".sst", "application/vnd.ms-pki.certstore"},
|
||||
{".stl", "application/vnd.ms-pki.stl"},
|
||||
{".sv4cpio", "application/x-sv4cpio"},
|
||||
{".sv4crc", "application/x-sv4crc"},
|
||||
{".svc", "application/xml"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".swf", "application/x-shockwave-flash"},
|
||||
{".step", "application/step"},
|
||||
{".stp", "application/step"},
|
||||
{".t", "application/x-troff"},
|
||||
{".tar", "application/x-tar"},
|
||||
{".tcl", "application/x-tcl"},
|
||||
{".testrunconfig", "application/xml"},
|
||||
{".testsettings", "application/xml"},
|
||||
{".tex", "application/x-tex"},
|
||||
{".texi", "application/x-texinfo"},
|
||||
{".texinfo", "application/x-texinfo"},
|
||||
{".tgz", "application/x-compressed"},
|
||||
{".thmx", "application/vnd.ms-officetheme"},
|
||||
{".thn", "application/octet-stream"},
|
||||
{".tif", "image/tiff"},
|
||||
{".tiff", "image/tiff"},
|
||||
{".tlh", "text/plain"},
|
||||
{".tli", "text/plain"},
|
||||
{".toc", "application/octet-stream"},
|
||||
{".tr", "application/x-troff"},
|
||||
{".trm", "application/x-msterminal"},
|
||||
{".trx", "application/xml"},
|
||||
{".ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".tsv", "text/tab-separated-values"},
|
||||
{".ttf", "application/font-sfnt"},
|
||||
{".tts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".txt", "text/plain"},
|
||||
{".u32", "application/octet-stream"},
|
||||
{".uls", "text/iuls"},
|
||||
{".user", "text/plain"},
|
||||
{".ustar", "application/x-ustar"},
|
||||
{".vb", "text/plain"},
|
||||
{".vbdproj", "text/plain"},
|
||||
{".vbk", "video/mpeg"},
|
||||
{".vbproj", "text/plain"},
|
||||
{".vbs", "text/vbscript"},
|
||||
{".vcf", "text/x-vcard"},
|
||||
{".vcproj", "application/xml"},
|
||||
{".vcs", "text/plain"},
|
||||
{".vcxproj", "application/xml"},
|
||||
{".vddproj", "text/plain"},
|
||||
{".vdp", "text/plain"},
|
||||
{".vdproj", "text/plain"},
|
||||
{".vdx", "application/vnd.ms-visio.viewer"},
|
||||
{".vml", "text/xml"},
|
||||
{".vscontent", "application/xml"},
|
||||
{".vsct", "text/xml"},
|
||||
{".vsd", "application/vnd.visio"},
|
||||
{".vsi", "application/ms-vsi"},
|
||||
{".vsix", "application/vsix"},
|
||||
{".vsixlangpack", "text/xml"},
|
||||
{".vsixmanifest", "text/xml"},
|
||||
{".vsmdi", "application/xml"},
|
||||
{".vspscc", "text/plain"},
|
||||
{".vss", "application/vnd.visio"},
|
||||
{".vsscc", "text/plain"},
|
||||
{".vssettings", "text/xml"},
|
||||
{".vssscc", "text/plain"},
|
||||
{".vst", "application/vnd.visio"},
|
||||
{".vstemplate", "text/xml"},
|
||||
{".vsto", "application/x-ms-vsto"},
|
||||
{".vsw", "application/vnd.visio"},
|
||||
{".vsx", "application/vnd.visio"},
|
||||
{".vtt", "text/vtt"},
|
||||
{".vtx", "application/vnd.visio"},
|
||||
{".wasm", "application/wasm"},
|
||||
{".wav", "audio/wav"},
|
||||
{".wave", "audio/wav"},
|
||||
{".wax", "audio/x-ms-wax"},
|
||||
{".wbk", "application/msword"},
|
||||
{".wbmp", "image/vnd.wap.wbmp"},
|
||||
{".wcm", "application/vnd.ms-works"},
|
||||
{".wdb", "application/vnd.ms-works"},
|
||||
{".wdp", "image/vnd.ms-photo"},
|
||||
{".webarchive", "application/x-safari-webarchive"},
|
||||
{".webm", "video/webm"},
|
||||
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
|
||||
{".webtest", "application/xml"},
|
||||
{".wiq", "application/xml"},
|
||||
{".wiz", "application/msword"},
|
||||
{".wks", "application/vnd.ms-works"},
|
||||
{".WLMP", "application/wlmoviemaker"},
|
||||
{".wlpginstall", "application/x-wlpg-detect"},
|
||||
{".wlpginstall3", "application/x-wlpg3-detect"},
|
||||
{".wm", "video/x-ms-wm"},
|
||||
{".wma", "audio/x-ms-wma"},
|
||||
{".wmd", "application/x-ms-wmd"},
|
||||
{".wmf", "application/x-msmetafile"},
|
||||
{".wml", "text/vnd.wap.wml"},
|
||||
{".wmlc", "application/vnd.wap.wmlc"},
|
||||
{".wmls", "text/vnd.wap.wmlscript"},
|
||||
{".wmlsc", "application/vnd.wap.wmlscriptc"},
|
||||
{".wmp", "video/x-ms-wmp"},
|
||||
{".wmv", "video/x-ms-wmv"},
|
||||
{".wmx", "video/x-ms-wmx"},
|
||||
{".wmz", "application/x-ms-wmz"},
|
||||
{".woff", "application/font-woff"},
|
||||
{".woff2", "application/font-woff2"},
|
||||
{".wpl", "application/vnd.ms-wpl"},
|
||||
{".wps", "application/vnd.ms-works"},
|
||||
{".wri", "application/x-mswrite"},
|
||||
{".wrl", "x-world/x-vrml"},
|
||||
{".wrz", "x-world/x-vrml"},
|
||||
{".wsc", "text/scriptlet"},
|
||||
{".wsdl", "text/xml"},
|
||||
{".wvx", "video/x-ms-wvx"},
|
||||
{".x", "application/directx"},
|
||||
{".xaf", "x-world/x-vrml"},
|
||||
{".xaml", "application/xaml+xml"},
|
||||
{".xap", "application/x-silverlight-app"},
|
||||
{".xbap", "application/x-ms-xbap"},
|
||||
{".xbm", "image/x-xbitmap"},
|
||||
{".xdr", "text/plain"},
|
||||
{".xht", "application/xhtml+xml"},
|
||||
{".xhtml", "application/xhtml+xml"},
|
||||
{".xla", "application/vnd.ms-excel"},
|
||||
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
|
||||
{".xlc", "application/vnd.ms-excel"},
|
||||
{".xld", "application/vnd.ms-excel"},
|
||||
{".xlk", "application/vnd.ms-excel"},
|
||||
{".xll", "application/vnd.ms-excel"},
|
||||
{".xlm", "application/vnd.ms-excel"},
|
||||
{".xls", "application/vnd.ms-excel"},
|
||||
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
|
||||
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
|
||||
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
||||
{".xlt", "application/vnd.ms-excel"},
|
||||
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
|
||||
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
|
||||
{".xlw", "application/vnd.ms-excel"},
|
||||
{".xml", "text/xml"},
|
||||
{".xmp", "application/octet-stream" },
|
||||
{".xmta", "application/xml"},
|
||||
{".xof", "x-world/x-vrml"},
|
||||
{".XOML", "text/plain"},
|
||||
{".xpm", "image/x-xpixmap"},
|
||||
{".xps", "application/vnd.ms-xpsdocument"},
|
||||
{".xrm-ms", "text/xml"},
|
||||
{".xsc", "application/xml"},
|
||||
{".xsd", "text/xml"},
|
||||
{".xsf", "text/xml"},
|
||||
{".xsl", "text/xml"},
|
||||
{".xslt", "text/xml"},
|
||||
{".xsn", "application/octet-stream"},
|
||||
{".xss", "application/xml"},
|
||||
{".xspf", "application/xspf+xml"},
|
||||
{".xtp", "application/octet-stream"},
|
||||
{".xwd", "image/x-xwindowdump"},
|
||||
{".z", "application/x-compress"},
|
||||
{".zip", "application/zip"},
|
||||
};
|
||||
}
|
||||
}
|
||||
174
Vendor/EmbedIO-3.5.2/MimeType.cs
vendored
Normal file
174
Vendor/EmbedIO-3.5.2/MimeType.cs
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides constants for commonly-used MIME types and association between file extensions and MIME types.
|
||||
/// </summary>
|
||||
/// <seealso cref="Associations"/>
|
||||
public static partial class MimeType
|
||||
{
|
||||
/// <summary>
|
||||
/// The default MIME type for data whose type is unknown,
|
||||
/// i.e. <c>application/octet-stream</c>.
|
||||
/// </summary>
|
||||
public const string Default = "application/octet-stream";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for plain text, i.e. <c>text/plain</c>.
|
||||
/// </summary>
|
||||
public const string PlainText = "text/plain";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for HTML, i.e. <c>text/html</c>.
|
||||
/// </summary>
|
||||
public const string Html = "text/html";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for JSON, i.e. <c>application/json</c>.
|
||||
/// </summary>
|
||||
public const string Json = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type for URL-encoded HTML forms,
|
||||
/// i.e. <c>application/x-www-form-urlencoded</c>.
|
||||
/// </summary>
|
||||
internal const string UrlEncodedForm = "application/x-www-form-urlencoded";
|
||||
|
||||
/// <summary>
|
||||
/// <para>Strips parameters, if present (e.g. <c>; encoding=UTF-8</c>), from a MIME type.</para>
|
||||
/// </summary>
|
||||
/// <param name="value">The MIME type.</param>
|
||||
/// <returns><paramref name="value"/> without parameters.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method does not validate <paramref name="value"/>: if it is not
|
||||
/// a valid MIME type or media range, it is just returned unchanged.</para>
|
||||
/// </remarks>
|
||||
public static string StripParameters(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
|
||||
var semicolonPos = value.IndexOf(';');
|
||||
return semicolonPos < 0
|
||||
? value
|
||||
: value.Substring(0, semicolonPos).TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified string is a valid MIME type or media range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="acceptMediaRange">If set to <see langword="true"/>, both media ranges
|
||||
/// (e.g. <c>"text/*"</c>, <c>"*/*"</c>) and specific MIME types (e.g. <c>"text/html"</c>)
|
||||
/// are considered valid; if set to <see langword="false"/>, only specific MIME types
|
||||
/// are considered valid.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="value"/> is valid,
|
||||
/// according to the value of <paramref name="acceptMediaRange"/>;
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsMimeType(string value, bool acceptMediaRange)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return false;
|
||||
|
||||
var slashPos = value.IndexOf('/');
|
||||
if (slashPos < 0)
|
||||
return false;
|
||||
|
||||
var isWildcardSubtype = false;
|
||||
var subtype = value.Substring(slashPos + 1);
|
||||
if (subtype == "*")
|
||||
{
|
||||
if (!acceptMediaRange)
|
||||
return false;
|
||||
|
||||
isWildcardSubtype = true;
|
||||
}
|
||||
else if (!Validate.IsRfc2616Token(subtype))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var type = value.Substring(0, slashPos);
|
||||
return type == "*"
|
||||
? acceptMediaRange && isWildcardSubtype
|
||||
: Validate.IsRfc2616Token(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified MIME type or media range into type and subtype.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type or media range to split.</param>
|
||||
/// <returns>A tuple of type and subtype.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/> is not a valid
|
||||
/// MIME type or media range.</exception>
|
||||
public static (string type, string subtype) Split(string mimeType)
|
||||
=> UnsafeSplit(Validate.MimeType(nameof(mimeType), mimeType, true));
|
||||
|
||||
/// <summary>
|
||||
/// Matches the specified MIME type to a media range.
|
||||
/// </summary>
|
||||
/// <param name="mimeType">The MIME type to match.</param>
|
||||
/// <param name="mediaRange">The media range.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="mediaRange"/> is either
|
||||
/// the same as <paramref name="mimeType"/>, or has the same type and a subtype
|
||||
/// of <c>"*"</c>, or is <c>"*/*"</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="mimeType"/> is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mediaRange"/> is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="mimeType"/> is not a valid MIME type.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mediaRange"/> is not a valid MIME media range.</para>
|
||||
/// </exception>
|
||||
public static bool IsInRange(string mimeType, string mediaRange)
|
||||
=> UnsafeIsInRange(
|
||||
Validate.MimeType(nameof(mimeType), mimeType, false),
|
||||
Validate.MimeType(nameof(mediaRange), mediaRange, true));
|
||||
|
||||
internal static (string type, string subtype) UnsafeSplit(string mimeType)
|
||||
{
|
||||
var slashPos = mimeType.IndexOf('/');
|
||||
return (mimeType.Substring(0, slashPos), mimeType.Substring(slashPos + 1));
|
||||
}
|
||||
|
||||
internal static bool UnsafeIsInRange(string mimeType, string mediaRange)
|
||||
{
|
||||
// A validated media range that starts with '*' can only be '*/*'
|
||||
if (mediaRange[0] == '*')
|
||||
return true;
|
||||
|
||||
var typeSlashPos = mimeType.IndexOf('/');
|
||||
var rangeSlashPos = mediaRange.IndexOf('/');
|
||||
|
||||
if (typeSlashPos != rangeSlashPos)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < typeSlashPos; i++)
|
||||
{
|
||||
if (mimeType[i] != mediaRange[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
// A validated token has at least 1 character,
|
||||
// thus there must be at least 1 character after a slash.
|
||||
if (mediaRange[rangeSlashPos + 1] == '*')
|
||||
return true;
|
||||
|
||||
if (mimeType.Length != mediaRange.Length)
|
||||
return false;
|
||||
|
||||
for (var i = typeSlashPos + 1; i < mimeType.Length; i++)
|
||||
{
|
||||
if (mimeType[i] != mediaRange[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Vendor/EmbedIO-3.5.2/MimeTypeCustomizerExtensions.cs
vendored
Normal file
99
Vendor/EmbedIO-3.5.2/MimeTypeCustomizerExtensions.cs
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for types implementing <see cref="IMimeTypeCustomizer"/>.
|
||||
/// </summary>
|
||||
public static class MimeTypeCustomizerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a custom association between a file extension and a MIME type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="extension">The file extension to associate to <paramref name="mimeType"/>.</param>
|
||||
/// <param name="mimeType">The MIME type to associate to <paramref name="extension"/>.</param>
|
||||
/// <returns><paramref name="this"/> with the custom association added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="extension"/>is <see langword="null"/>.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is <see langword="null"/>.</para>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <para><paramref name="extension"/>is the empty string.</para>
|
||||
/// <para>- or -</para>
|
||||
/// <para><paramref name="mimeType"/>is not a valid MIME type.</para>
|
||||
/// </exception>
|
||||
public static T WithCustomMimeType<T>(this T @this, string extension, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.AddCustomMimeType(extension, mimeType);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to prefer compression when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <param name="preferCompression"><see langword="true"/> to prefer compression;
|
||||
/// otherwise, <see langword="false"/>.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferCompressionFor<T>(this T @this, string mimeType, bool preferCompression)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, preferCompression);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that compression should be preferred when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferCompressionFor<T>(this T @this, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, true);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that no compression should be preferred when negotiating content encoding
|
||||
/// for a response with the specified content type, or whose content type is in
|
||||
/// the specified media range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to which this method is applied.</typeparam>
|
||||
/// <param name="this">The object to which this method is applied.</param>
|
||||
/// <param name="mimeType">The MIME type or media range.</param>
|
||||
/// <returns><paramref name="this"/> with the specified preference added.</returns>
|
||||
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="this"/> has its configuration locked.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="mimeType"/>is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="mimeType"/>is not a valid MIME type or media range.</exception>
|
||||
public static T PreferNoCompressionFor<T>(this T @this, string mimeType)
|
||||
where T : IMimeTypeCustomizer
|
||||
{
|
||||
@this.PreferCompression(mimeType, false);
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Vendor/EmbedIO-3.5.2/ModuleGroup.cs
vendored
Normal file
111
Vendor/EmbedIO-3.5.2/ModuleGroup.cs
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Groups modules under a common base URL path.</para>
|
||||
/// <para>The <see cref="IWebModule.BaseRoute">BaseRoute</see> property
|
||||
/// of modules contained in a <c>ModuleGroup</c> is relative to the
|
||||
/// <c>ModuleGroup</c>'s <see cref="IWebModule.BaseRoute">BaseRoute</see> property.
|
||||
/// For example, given the following code:</para>
|
||||
/// <para><code>new ModuleGroup("/download")
|
||||
/// .WithStaticFilesAt("/docs", "/var/my/documents");</code></para>
|
||||
/// <para>files contained in the <c>/var/my/documents</c> folder will be
|
||||
/// available to clients under the <c>/download/docs/</c> URL.</para>
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
/// <seealso cref="IDisposable" />
|
||||
/// <seealso cref="IWebModuleContainer" />
|
||||
public class ModuleGroup : WebModuleBase, IDisposable, IWebModuleContainer, IMimeTypeCustomizer
|
||||
{
|
||||
private readonly WebModuleCollection _modules;
|
||||
private readonly MimeTypeCustomizer _mimeTypeCustomizer = new MimeTypeCustomizer();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModuleGroup" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route served by this module.</param>
|
||||
/// <param name="isFinalHandler">The value to set the <see cref="IWebModule.IsFinalHandler" /> property to.
|
||||
/// See the help for the property for more information.</param>
|
||||
/// <seealso cref="IWebModule.BaseRoute" />
|
||||
/// <seealso cref="IWebModule.IsFinalHandler" />
|
||||
public ModuleGroup(string baseRoute, bool isFinalHandler)
|
||||
: base(baseRoute)
|
||||
{
|
||||
IsFinalHandler = isFinalHandler;
|
||||
_modules = new WebModuleCollection(nameof(ModuleGroup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ModuleGroup"/> class.
|
||||
/// </summary>
|
||||
~ModuleGroup()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override bool IsFinalHandler { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponentCollection<IWebModule> Modules => _modules;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
string IMimeTypeProvider.GetMimeType(string extension)
|
||||
=> _mimeTypeCustomizer.GetMimeType(extension);
|
||||
|
||||
bool IMimeTypeProvider.TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> _mimeTypeCustomizer.TryDetermineCompression(mimeType, out preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddCustomMimeType(string extension, string mimeType)
|
||||
=> _mimeTypeCustomizer.AddCustomMimeType(extension, mimeType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PreferCompression(string mimeType, bool preferCompression)
|
||||
=> _mimeTypeCustomizer.PreferCompression(mimeType, preferCompression);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
=> _modules.DispatchRequestAsync(context);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources;
|
||||
/// <see langword="false"/> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_modules.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnBeforeLockConfiguration()
|
||||
{
|
||||
base.OnBeforeLockConfiguration();
|
||||
|
||||
_mimeTypeCustomizer.Lock();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(CancellationToken cancellationToken)
|
||||
{
|
||||
_modules.StartAll(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
243
Vendor/EmbedIO-3.5.2/Net/CookieList.cs
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Net.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Provides a collection container for instances of <see cref="Cookie"/>.</para>
|
||||
/// <para>This class is meant to be used internally by EmbedIO; you don't need to
|
||||
/// use this class directly.</para>
|
||||
/// </summary>
|
||||
#pragma warning disable CA1710 // Rename class to end in 'Collection' - it ends in 'List', i.e. 'Indexed Collection'.
|
||||
public sealed class CookieList : List<Cookie>, ICookieCollection
|
||||
#pragma warning restore CA1710
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cookie? this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
if (Count == 0)
|
||||
return null;
|
||||
|
||||
var list = new List<Cookie>(this);
|
||||
|
||||
list.Sort(CompareCookieWithinSorted);
|
||||
|
||||
return list.FirstOrDefault(cookie => cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="CookieList"/> by parsing
|
||||
/// the value of one or more <c>Cookie</c> or <c>Set-Cookie</c> headers.</summary>
|
||||
/// <param name="headerValue">The value, or comma-separated list of values,
|
||||
/// of the header or headers.</param>
|
||||
/// <returns>A newly-created instance of <see cref="CookieList"/>.</returns>
|
||||
public static CookieList Parse(string headerValue)
|
||||
{
|
||||
var cookies = new CookieList();
|
||||
|
||||
Cookie? cookie = null;
|
||||
var pairs = SplitCookieHeaderValue(headerValue);
|
||||
|
||||
for (var i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
var pair = pairs[i].Trim();
|
||||
if (pair.Length == 0)
|
||||
continue;
|
||||
|
||||
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Version = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var buff = new StringBuilder(GetValue(pair), 32);
|
||||
if (i < pairs.Length - 1)
|
||||
buff.AppendFormat(CultureInfo.InvariantCulture, ", {0}", pairs[++i].Trim());
|
||||
|
||||
if (!HttpDate.TryParse(buff.ToString(), out var expires))
|
||||
expires = DateTimeOffset.Now;
|
||||
|
||||
if (cookie.Expires == DateTime.MinValue)
|
||||
cookie.Expires = expires.LocalDateTime;
|
||||
}
|
||||
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
var max = int.Parse(GetValue(pair, true), CultureInfo.InvariantCulture);
|
||||
|
||||
cookie.Expires = DateTime.Now.AddSeconds(max);
|
||||
}
|
||||
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Path = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Domain = GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||
? "\"\""
|
||||
: GetValue(pair);
|
||||
}
|
||||
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Comment = WebUtility.UrlDecode(GetValue(pair));
|
||||
}
|
||||
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.CommentUri = UriUtility.StringToUri(GetValue(pair, true));
|
||||
}
|
||||
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Discard = true;
|
||||
}
|
||||
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.Secure = true;
|
||||
}
|
||||
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase) && cookie != null)
|
||||
{
|
||||
cookie.HttpOnly = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
cookie = ParseCookie(pair);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public new void Add(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
|
||||
var pos = SearchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
base.Add(cookie);
|
||||
return;
|
||||
}
|
||||
|
||||
this[pos] = cookie;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
|
||||
if (array.Rank > 1)
|
||||
throw new ArgumentException("Multidimensional.", nameof(array));
|
||||
|
||||
if (array.Length - index < Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
if (array.GetType().GetElementType()?.IsAssignableFrom(typeof(Cookie)) != true)
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
"The elements in this collection cannot be cast automatically to the type of the destination array.");
|
||||
}
|
||||
|
||||
((IList) this).CopyTo(array, index);
|
||||
}
|
||||
|
||||
private static string? GetValue(string nameAndValue, bool unquote = false)
|
||||
{
|
||||
var idx = nameAndValue.IndexOf('=');
|
||||
|
||||
if (idx < 0 || idx == nameAndValue.Length - 1)
|
||||
return null;
|
||||
|
||||
var val = nameAndValue.Substring(idx + 1).Trim();
|
||||
return unquote ? val.Unquote() : val;
|
||||
}
|
||||
|
||||
private static string[] SplitCookieHeaderValue(string value) => value.SplitHeaderValue(true).ToArray();
|
||||
|
||||
private static int CompareCookieWithinSorted(Cookie x, Cookie y)
|
||||
{
|
||||
var ret = x.Version - y.Version;
|
||||
return ret != 0
|
||||
? ret
|
||||
: (ret = string.Compare(x.Name, y.Name, StringComparison.Ordinal)) != 0
|
||||
? ret
|
||||
: y.Path.Length - x.Path.Length;
|
||||
}
|
||||
|
||||
private static Cookie ParseCookie(string pair)
|
||||
{
|
||||
string name;
|
||||
var val = string.Empty;
|
||||
|
||||
var pos = pair.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = pair;
|
||||
}
|
||||
else if (pos == pair.Length - 1)
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
val = pair.Substring(pos + 1).TrimStart(' ');
|
||||
}
|
||||
|
||||
return new Cookie(name, val);
|
||||
}
|
||||
|
||||
private int SearchCookie(Cookie cookie)
|
||||
{
|
||||
var name = cookie.Name;
|
||||
var path = cookie.Path;
|
||||
var domain = cookie.Domain;
|
||||
var ver = cookie.Version;
|
||||
|
||||
for (var i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
var c = this[i];
|
||||
if (c.Name.Equals(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Path.Equals(path, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Domain.Equals(domain, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.Version == ver)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
141
Vendor/EmbedIO-3.5.2/Net/EndPointManager.cs
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EmbedIO.Net.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the EndPoint Manager.
|
||||
/// </summary>
|
||||
public static class EndPointManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IPAddress, ConcurrentDictionary<int, EndPointListener>> IPToEndpoints = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [use IPv6]. By default, this flag is set.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [use IPv6]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public static bool UseIpv6 { get; set; } = true;
|
||||
|
||||
internal static void AddListener(HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
AddPrefix(prefix, listener);
|
||||
added.Add(prefix);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(AddListener));
|
||||
|
||||
foreach (var prefix in added)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
if (IPToEndpoints.TryGetValue(ep.Address, out var p))
|
||||
{
|
||||
if (p.TryRemove(ep.Port, out _) && p.Count == 0)
|
||||
{
|
||||
_ = IPToEndpoints.TryRemove(ep.Address, out _);
|
||||
}
|
||||
}
|
||||
|
||||
epl.Dispose();
|
||||
}
|
||||
|
||||
internal static void RemoveListener(HttpListener listener)
|
||||
{
|
||||
foreach (var prefix in listener.Prefixes)
|
||||
{
|
||||
RemovePrefix(prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AddPrefix(string p, HttpListener listener)
|
||||
{
|
||||
var lp = new ListenerPrefix(p);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
throw new HttpListenerException(400, "Invalid path.");
|
||||
}
|
||||
|
||||
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.AddPrefix(lp, listener);
|
||||
}
|
||||
|
||||
private static EndPointListener GetEpListener(string host, int port, HttpListener listener, bool secure = false)
|
||||
{
|
||||
var address = ResolveAddress(host);
|
||||
|
||||
var p = IPToEndpoints.GetOrAdd(address, x => new ConcurrentDictionary<int, EndPointListener>());
|
||||
return p.GetOrAdd(port, x => new EndPointListener(listener, address, x, secure));
|
||||
}
|
||||
|
||||
private static IPAddress ResolveAddress(string host)
|
||||
{
|
||||
if (host == "*" || host == "+" || host == "0.0.0.0")
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
if (IPAddress.TryParse(host, out var address))
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var hostEntry = new IPHostEntry {
|
||||
HostName = host,
|
||||
AddressList = Dns.GetHostAddresses(host),
|
||||
};
|
||||
|
||||
return hostEntry.AddressList[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return UseIpv6 ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemovePrefix(string prefix, HttpListener listener)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lp = new ListenerPrefix(prefix);
|
||||
|
||||
if (!lp.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var epl = GetEpListener(lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.RemovePrefix(lp);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
Vendor/EmbedIO-3.5.2/Net/HttpListener.cs
vendored
Normal file
161
Vendor/EmbedIO-3.5.2/Net/HttpListener.cs
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Net.Internal;
|
||||
|
||||
namespace EmbedIO.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The EmbedIO implementation of the standard HTTP Listener class.
|
||||
///
|
||||
/// Based on MONO HttpListener class.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public sealed class HttpListener : IHttpListener
|
||||
{
|
||||
private readonly SemaphoreSlim _ctxQueueSem = new (0);
|
||||
private readonly ConcurrentDictionary<string, HttpListenerContext> _ctxQueue;
|
||||
private readonly ConcurrentDictionary<HttpConnection, object> _connections;
|
||||
private readonly HttpListenerPrefixCollection _prefixes;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListener" /> class.
|
||||
/// </summary>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
public HttpListener(X509Certificate? certificate = null)
|
||||
{
|
||||
Certificate = certificate;
|
||||
|
||||
_prefixes = new HttpListenerPrefixCollection(this);
|
||||
_connections = new ConcurrentDictionary<HttpConnection, object>();
|
||||
_ctxQueue = new ConcurrentDictionary<string, HttpListenerContext>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreWriteExceptions { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsListening { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; } = "Unosquare HTTP Listener";
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Prefixes => _prefixes.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the certificate.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The certificate.
|
||||
/// </value>
|
||||
internal X509Certificate? Certificate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start()
|
||||
{
|
||||
if (IsListening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.AddListener(this);
|
||||
IsListening = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Stop()
|
||||
{
|
||||
IsListening = false;
|
||||
Close(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddPrefix(string urlPrefix) => _prefixes.Add(urlPrefix);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
_ctxQueueSem.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await _ctxQueueSem.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var key in _ctxQueue.Keys)
|
||||
{
|
||||
if (_ctxQueue.TryRemove(key, out var context))
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegisterContext(HttpListenerContext context)
|
||||
{
|
||||
if (!_ctxQueue.TryAdd(context.Id, context))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to register context");
|
||||
}
|
||||
|
||||
_ = _ctxQueueSem.Release();
|
||||
}
|
||||
|
||||
internal void UnregisterContext(HttpListenerContext context) => _ctxQueue.TryRemove(context.Id, out _);
|
||||
|
||||
internal void AddConnection(HttpConnection cnc) => _connections[cnc] = cnc;
|
||||
|
||||
internal void RemoveConnection(HttpConnection cnc) => _connections.TryRemove(cnc, out _);
|
||||
|
||||
private void Close(bool closeExisting)
|
||||
{
|
||||
EndPointManager.RemoveListener(this);
|
||||
|
||||
var keys = _connections.Keys;
|
||||
var connections = new HttpConnection[keys.Count];
|
||||
keys.CopyTo(connections, 0);
|
||||
_connections.Clear();
|
||||
var list = new List<HttpConnection>(connections);
|
||||
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
list[i].Close(true);
|
||||
}
|
||||
|
||||
if (!closeExisting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (!_ctxQueue.IsEmpty)
|
||||
{
|
||||
foreach (var key in _ctxQueue.Keys.ToArray())
|
||||
{
|
||||
if (_ctxQueue.TryGetValue(key, out var context))
|
||||
{
|
||||
context.Connection.Close(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
429
Vendor/EmbedIO-3.5.2/Net/Internal/EndPointListener.cs
vendored
Normal file
429
Vendor/EmbedIO-3.5.2/Net/Internal/EndPointListener.cs
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class EndPointListener : IDisposable
|
||||
{
|
||||
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
|
||||
private readonly IPEndPoint _endpoint;
|
||||
private readonly Socket _sock;
|
||||
private Dictionary<ListenerPrefix, HttpListener> _prefixes;
|
||||
private List<ListenerPrefix>? _unhandled; // unhandled; host = '*'
|
||||
private List<ListenerPrefix>? _all; // all; host = '+
|
||||
|
||||
public EndPointListener(HttpListener listener, IPAddress address, int port, bool secure)
|
||||
{
|
||||
Listener = listener;
|
||||
Secure = secure;
|
||||
_endpoint = new IPEndPoint(address, port);
|
||||
_sock = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6 && EndPointManager.UseIpv6)
|
||||
{
|
||||
_sock.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
_sock.Bind(_endpoint);
|
||||
_sock.Listen(500);
|
||||
var args = new SocketAsyncEventArgs { UserToken = this };
|
||||
args.Completed += OnAccept;
|
||||
Socket? dummy = null;
|
||||
Accept(_sock, args, ref dummy);
|
||||
_prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||
_unregistered = new Dictionary<HttpConnection, HttpConnection>();
|
||||
}
|
||||
|
||||
internal HttpListener Listener { get; }
|
||||
|
||||
internal bool Secure { get; }
|
||||
|
||||
public bool BindContext(HttpListenerContext context)
|
||||
{
|
||||
var req = context.Request;
|
||||
var listener = SearchListener(req.Url, out var prefix);
|
||||
|
||||
if (listener == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Listener = listener;
|
||||
context.Connection.Prefix = prefix;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UnbindContext(HttpListenerContext context) => context.Listener.UnregisterContext(context);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sock.Dispose();
|
||||
List<HttpConnection> connections;
|
||||
|
||||
lock (_unregistered)
|
||||
{
|
||||
// Clone the list because RemoveConnection can be called from Close
|
||||
connections = new List<HttpConnection>(_unregistered.Keys);
|
||||
_unregistered.Clear();
|
||||
}
|
||||
|
||||
foreach (var c in connections)
|
||||
{
|
||||
c.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<ListenerPrefix>? current;
|
||||
List<ListenerPrefix> future;
|
||||
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandled;
|
||||
|
||||
// TODO: Should we clone the items?
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
prefix.Listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _all;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
prefix.Listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _all, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
if (prefs.ContainsKey(prefix))
|
||||
{
|
||||
if (prefs[prefix] != listener)
|
||||
{
|
||||
throw new HttpListenerException(400, $"There is another listener for {prefix}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
p2 = prefs.ToDictionary(x => x.Key, x => x.Value);
|
||||
p2[prefix] = listener;
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
}
|
||||
|
||||
public void RemovePrefix(ListenerPrefix prefix)
|
||||
{
|
||||
List<ListenerPrefix>? current;
|
||||
List<ListenerPrefix> future;
|
||||
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandled;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
{
|
||||
break; // Prefix not found
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _all;
|
||||
future = current?.ToList() ?? new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
{
|
||||
break; // Prefix not found
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _all, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
var prefixKey = _prefixes.Keys.FirstOrDefault(p => p.Path == prefix.Path);
|
||||
|
||||
if (prefixKey is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
p2 = prefs.ToDictionary(x => x.Key, x => x.Value);
|
||||
_ = p2.Remove(prefixKey);
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
|
||||
|
||||
CheckIfRemove();
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection conn)
|
||||
{
|
||||
lock (_unregistered)
|
||||
{
|
||||
_ = _unregistered.Remove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Accept(Socket socket, SocketAsyncEventArgs e, ref Socket? accepted)
|
||||
{
|
||||
e.AcceptSocket = null;
|
||||
bool acceptPending;
|
||||
|
||||
try
|
||||
{
|
||||
acceptPending = socket.AcceptAsync(e);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
accepted?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
accepted = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!acceptPending)
|
||||
{
|
||||
ProcessAccept(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAccept(SocketAsyncEventArgs args)
|
||||
{
|
||||
Socket? accepted = null;
|
||||
if (args.SocketError == SocketError.Success)
|
||||
{
|
||||
accepted = args.AcceptSocket;
|
||||
}
|
||||
|
||||
var epl = (EndPointListener)args.UserToken;
|
||||
|
||||
Accept(epl._sock, args, ref accepted);
|
||||
if (accepted == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (epl.Secure && epl.Listener.Certificate == null)
|
||||
{
|
||||
accepted.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
HttpConnection conn;
|
||||
try
|
||||
{
|
||||
conn = new HttpConnection(accepted, epl);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (epl._unregistered)
|
||||
{
|
||||
epl._unregistered[conn] = conn;
|
||||
}
|
||||
|
||||
_ = conn.BeginReadRequest();
|
||||
}
|
||||
|
||||
private static void OnAccept(object sender, SocketAsyncEventArgs e) => ProcessAccept(e);
|
||||
|
||||
private static HttpListener? MatchFromList(string path, List<ListenerPrefix>? list, out ListenerPrefix? prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpListener? bestMatch = null;
|
||||
var bestLength = -1;
|
||||
|
||||
foreach (var p in list)
|
||||
{
|
||||
if (p.Path.Length < bestLength || !path.StartsWith(p.Path, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestLength = p.Path.Length;
|
||||
bestMatch = p.Listener;
|
||||
prefix = p;
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private static void AddSpecial(ICollection<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||
{
|
||||
if (coll == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (coll.Any(p => p.Path == prefix.Path))
|
||||
{
|
||||
throw new HttpListenerException(400, "Prefix already in use.");
|
||||
}
|
||||
|
||||
coll.Add(prefix);
|
||||
}
|
||||
|
||||
private static bool RemoveSpecial(IList<ListenerPrefix> coll, ListenerPrefix prefix)
|
||||
{
|
||||
if (coll == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var c = coll.Count;
|
||||
for (var i = 0; i < c; i++)
|
||||
{
|
||||
if (coll[i].Path != prefix.Path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
coll.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private HttpListener? SearchListener(Uri uri, out ListenerPrefix? prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (uri == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var host = uri.Host;
|
||||
var port = uri.Port;
|
||||
var path = WebUtility.UrlDecode(uri.AbsolutePath);
|
||||
var pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
|
||||
|
||||
HttpListener? bestMatch = null;
|
||||
var bestLength = -1;
|
||||
|
||||
if (!string.IsNullOrEmpty(host))
|
||||
{
|
||||
var result = _prefixes;
|
||||
|
||||
foreach (var p in result.Keys)
|
||||
{
|
||||
if (p.Path.Length < bestLength)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p.Host != host || p.Port != port)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.StartsWith(p.Path, StringComparison.Ordinal) && !pathSlash.StartsWith(p.Path, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestLength = p.Path.Length;
|
||||
bestMatch = result[p];
|
||||
prefix = p;
|
||||
}
|
||||
|
||||
if (bestLength != -1)
|
||||
{
|
||||
return bestMatch;
|
||||
}
|
||||
}
|
||||
|
||||
var list = _unhandled;
|
||||
bestMatch = MatchFromList(path, list, out prefix);
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
{
|
||||
bestMatch = MatchFromList(pathSlash, list, out prefix);
|
||||
}
|
||||
|
||||
if (bestMatch != null)
|
||||
{
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
list = _all;
|
||||
bestMatch = MatchFromList(path, list, out prefix);
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
{
|
||||
bestMatch = MatchFromList(pathSlash, list, out prefix);
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private void CheckIfRemove()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var list = _unhandled;
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list = _all;
|
||||
if (list != null && list.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.RemoveEndPoint(this, _endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Vendor/EmbedIO-3.5.2/Net/Internal/HeaderUtility.cs
vendored
Normal file
23
Vendor/EmbedIO-3.5.2/Net/Internal/HeaderUtility.cs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class HeaderUtility
|
||||
{
|
||||
public static string? GetCharset(string? contentType)
|
||||
=> contentType?
|
||||
.Split(';')
|
||||
.Select(p => p.Trim())
|
||||
.Where(part => part.StartsWith("charset", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(GetAttributeValue)
|
||||
.FirstOrDefault();
|
||||
|
||||
public static string? GetAttributeValue(string nameAndValue)
|
||||
{
|
||||
var idx = nameAndValue.IndexOf('=');
|
||||
|
||||
return idx < 0 || idx == nameAndValue.Length - 1 ? null : nameAndValue.Substring(idx + 1).Trim().Unquote();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.InputState.cs
vendored
Normal file
11
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.InputState.cs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
partial class HttpConnection
|
||||
{
|
||||
private enum InputState
|
||||
{
|
||||
RequestLine,
|
||||
Headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.LineState.cs
vendored
Normal file
12
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.LineState.cs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
partial class HttpConnection
|
||||
{
|
||||
private enum LineState
|
||||
{
|
||||
None,
|
||||
Cr,
|
||||
Lf,
|
||||
}
|
||||
}
|
||||
}
|
||||
416
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.cs
vendored
Normal file
416
Vendor/EmbedIO-3.5.2/Net/Internal/HttpConnection.cs
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed partial class HttpConnection : IDisposable
|
||||
{
|
||||
private const int BufferSize = 8192;
|
||||
|
||||
private readonly Timer _timer;
|
||||
private readonly EndPointListener _epl;
|
||||
private Socket? _sock;
|
||||
private MemoryStream? _ms;
|
||||
private byte[]? _buffer;
|
||||
private HttpListenerContext _context;
|
||||
private StringBuilder? _currentLine;
|
||||
private RequestStream? _iStream;
|
||||
private ResponseStream? _oStream;
|
||||
private bool _contextBound;
|
||||
private int _sTimeout = 90000; // 90k ms for first request, 15k ms from then on
|
||||
private HttpListener? _lastListener;
|
||||
private InputState _inputState = InputState.RequestLine;
|
||||
private LineState _lineState = LineState.None;
|
||||
private int _position;
|
||||
private string? _errorMessage;
|
||||
|
||||
public HttpConnection(Socket sock, EndPointListener epl)
|
||||
{
|
||||
_sock = sock;
|
||||
_epl = epl;
|
||||
IsSecure = epl.Secure;
|
||||
LocalEndPoint = (IPEndPoint) sock.LocalEndPoint;
|
||||
RemoteEndPoint = (IPEndPoint) sock.RemoteEndPoint;
|
||||
|
||||
Stream = new NetworkStream(sock, false);
|
||||
if (IsSecure)
|
||||
{
|
||||
var sslStream = new SslStream(Stream, true);
|
||||
|
||||
try
|
||||
{
|
||||
sslStream.AuthenticateAsServer(epl.Listener.Certificate);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseSocket();
|
||||
throw;
|
||||
}
|
||||
|
||||
Stream = sslStream;
|
||||
}
|
||||
|
||||
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
|
||||
_context = null!; // Silence warning about uninitialized field - _context will be initialized by the Init method
|
||||
Init();
|
||||
}
|
||||
|
||||
public int Reuses { get; private set; }
|
||||
|
||||
public Stream Stream { get; }
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public bool IsSecure { get; }
|
||||
|
||||
public ListenerPrefix? Prefix { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close(true);
|
||||
|
||||
_timer.Dispose();
|
||||
_sock?.Dispose();
|
||||
_ms?.Dispose();
|
||||
_iStream?.Dispose();
|
||||
_oStream?.Dispose();
|
||||
Stream?.Dispose();
|
||||
_lastListener?.Dispose();
|
||||
}
|
||||
|
||||
public async Task BeginReadRequest()
|
||||
{
|
||||
_buffer ??= new byte[BufferSize];
|
||||
|
||||
try
|
||||
{
|
||||
if (Reuses == 1)
|
||||
{
|
||||
_sTimeout = 15000;
|
||||
}
|
||||
|
||||
_ = _timer.Change(_sTimeout, Timeout.Infinite);
|
||||
|
||||
var data = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
||||
await OnReadInternal(data).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
public RequestStream GetRequestStream(long contentLength)
|
||||
{
|
||||
if (_iStream == null)
|
||||
{
|
||||
var buffer = _ms.ToArray();
|
||||
var length = (int) _ms.Length;
|
||||
_ms = null;
|
||||
|
||||
_iStream = new RequestStream(Stream, buffer, _position, length - _position, contentLength);
|
||||
}
|
||||
|
||||
return _iStream;
|
||||
}
|
||||
|
||||
public ResponseStream GetResponseStream() => _oStream ??= new ResponseStream(Stream, _context.HttpListenerResponse, _context.Listener?.IgnoreWriteExceptions ?? true);
|
||||
|
||||
internal void SetError(string message) => _errorMessage = message;
|
||||
|
||||
internal void ForceClose() => Close(true);
|
||||
|
||||
internal void Close(bool forceClose = false)
|
||||
{
|
||||
if (_sock != null)
|
||||
{
|
||||
_oStream?.Dispose();
|
||||
_oStream = null;
|
||||
}
|
||||
|
||||
if (_sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
forceClose = forceClose
|
||||
|| !_context.Request.KeepAlive
|
||||
|| _context.Response.Headers["connection"] == "close";
|
||||
|
||||
if (!forceClose)
|
||||
{
|
||||
if (_context.HttpListenerRequest.FlushInput())
|
||||
{
|
||||
Reuses++;
|
||||
Unbind();
|
||||
Init();
|
||||
_ = BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (var s = _sock)
|
||||
{
|
||||
_sock = null;
|
||||
try
|
||||
{
|
||||
s?.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
Unbind();
|
||||
RemoveConnection();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_contextBound = false;
|
||||
_iStream = null;
|
||||
_oStream = null;
|
||||
Prefix = null;
|
||||
_ms = new MemoryStream();
|
||||
_position = 0;
|
||||
_inputState = InputState.RequestLine;
|
||||
_lineState = LineState.None;
|
||||
_context = new HttpListenerContext(this);
|
||||
}
|
||||
|
||||
private void OnTimeout(object unused)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
|
||||
private async Task OnReadInternal(int offset)
|
||||
{
|
||||
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
// Continue reading until full header is received.
|
||||
// Especially important for multipart requests when the second part of the header arrives after a tiny delay
|
||||
// because the web browser has to measure the content length first.
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ms.WriteAsync(_buffer, 0, offset).ConfigureAwait(false);
|
||||
if (_ms.Length > 32768)
|
||||
{
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessInput(_ms))
|
||||
{
|
||||
if (_errorMessage is null)
|
||||
{
|
||||
_context.HttpListenerRequest.FinishInitialization();
|
||||
}
|
||||
|
||||
if (_errorMessage != null || !_epl.BindContext(_context))
|
||||
{
|
||||
Close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var listener = _context.Listener;
|
||||
if (_lastListener != listener)
|
||||
{
|
||||
RemoveConnection();
|
||||
listener.AddConnection(this);
|
||||
_lastListener = listener;
|
||||
}
|
||||
|
||||
_contextBound = true;
|
||||
listener.RegisterContext(_context);
|
||||
return;
|
||||
}
|
||||
|
||||
offset = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveConnection()
|
||||
{
|
||||
if (_lastListener != null)
|
||||
{
|
||||
_lastListener.RemoveConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_epl.RemoveConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
// true -> done processing
|
||||
// false -> need more input
|
||||
private bool ProcessInput(MemoryStream ms)
|
||||
{
|
||||
var buffer = ms.ToArray();
|
||||
var len = (int)ms.Length;
|
||||
var used = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_errorMessage != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_position >= len)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string? line;
|
||||
try
|
||||
{
|
||||
line = ReadLine(buffer, _position, len - _position, out used);
|
||||
_position += used;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_errorMessage = "Bad request";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentLine = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
_context.HttpListenerRequest.SetRequestLine(line);
|
||||
_inputState = InputState.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.HttpListenerRequest.AddHeader(line);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_errorMessage = e.Message;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used == len)
|
||||
{
|
||||
ms.SetLength(0);
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string? ReadLine(byte[] buffer, int offset, int len, out int used)
|
||||
{
|
||||
_currentLine ??= new StringBuilder(128);
|
||||
|
||||
var last = offset + len;
|
||||
used = 0;
|
||||
for (var i = offset; i < last && _lineState != LineState.Lf; i++)
|
||||
{
|
||||
used++;
|
||||
var b = buffer[i];
|
||||
|
||||
switch (b)
|
||||
{
|
||||
case 13:
|
||||
_lineState = LineState.Cr;
|
||||
break;
|
||||
case 10:
|
||||
_lineState = LineState.Lf;
|
||||
break;
|
||||
default:
|
||||
_ = _currentLine.Append((char)b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_lineState != LineState.Lf)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_lineState = LineState.None;
|
||||
var result = _currentLine.ToString();
|
||||
_currentLine.Length = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Unbind()
|
||||
{
|
||||
if (!_contextBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_epl.UnbindContext(_context);
|
||||
_contextBound = false;
|
||||
}
|
||||
|
||||
private void CloseSocket()
|
||||
{
|
||||
if (_sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sock.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sock = null;
|
||||
}
|
||||
|
||||
RemoveConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
129
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerContext.cs
vendored
Normal file
129
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerContext.cs
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Authentication;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
using EmbedIO.WebSockets.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
// Provides access to the request and response objects used by the HttpListener class.
|
||||
internal sealed class HttpListenerContext : IHttpContextImpl
|
||||
{
|
||||
private readonly Lazy<IDictionary<object, object>> _items = new (() => new Dictionary<object, object>(), true);
|
||||
|
||||
private readonly TimeKeeper _ageKeeper = new ();
|
||||
|
||||
private readonly Stack<Action<IHttpContext>> _closeCallbacks = new ();
|
||||
|
||||
private bool _closed;
|
||||
|
||||
internal HttpListenerContext(HttpConnection cnc)
|
||||
{
|
||||
Connection = cnc;
|
||||
HttpListenerRequest = new HttpListenerRequest(this);
|
||||
User = Auth.NoUser;
|
||||
HttpListenerResponse = new HttpListenerResponse(this);
|
||||
Id = UniqueIdGenerator.GetNext();
|
||||
LocalEndPoint = Request.LocalEndPoint;
|
||||
RemoteEndPoint = Request.RemoteEndPoint;
|
||||
Route = RouteMatch.None;
|
||||
Session = SessionProxy.None;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public long Age => _ageKeeper.ElapsedTime;
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public IHttpRequest Request => HttpListenerRequest;
|
||||
|
||||
public RouteMatch Route { get; set; }
|
||||
|
||||
public string RequestedPath => Route.SubPath ?? string.Empty; // It will never be empty, because modules are matched via base routes - this is just to silence a warning.
|
||||
|
||||
public IHttpResponse Response => HttpListenerResponse;
|
||||
|
||||
public IPrincipal User { get; set; }
|
||||
|
||||
public ISessionProxy Session { get; set; }
|
||||
|
||||
public bool SupportCompressedRequests { get; set; }
|
||||
|
||||
public IDictionary<object, object> Items => _items.Value;
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public MimeTypeProviderStack MimeTypeProviders { get; } = new MimeTypeProviderStack();
|
||||
|
||||
internal HttpListenerRequest HttpListenerRequest { get; }
|
||||
|
||||
internal HttpListenerResponse HttpListenerResponse { get; }
|
||||
|
||||
internal HttpListener? Listener { get; set; }
|
||||
|
||||
internal HttpConnection Connection { get; }
|
||||
|
||||
public void SetHandled() => IsHandled = true;
|
||||
|
||||
public void OnClose(Action<IHttpContext> callback)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP context has already been closed.");
|
||||
}
|
||||
|
||||
_closeCallbacks.Push(Validate.NotNull(nameof(callback), callback));
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
|
||||
// Always close the response stream no matter what.
|
||||
Response.Close();
|
||||
|
||||
foreach (var callback in _closeCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Log("HTTP context", $"[{Id}] Exception thrown by a HTTP context close callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var webSocket = await WebSocket.AcceptAsync(this, acceptedProtocol).ConfigureAwait(false);
|
||||
return new WebSocketContext(this, WebSocket.SupportedVersion, requestedProtocols, acceptedProtocol, webSocket, cancellationToken);
|
||||
}
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
=> MimeTypeProviders.GetMimeType(extension);
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> MimeTypeProviders.TryDetermineCompression(mimeType, out preferCompression);
|
||||
}
|
||||
}
|
||||
30
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerPrefixCollection.cs
vendored
Normal file
30
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerPrefixCollection.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class HttpListenerPrefixCollection : List<string>
|
||||
{
|
||||
private readonly HttpListener _listener;
|
||||
|
||||
internal HttpListenerPrefixCollection(HttpListener listener)
|
||||
{
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
public new void Add(string uriPrefix)
|
||||
{
|
||||
ListenerPrefix.CheckUri(uriPrefix);
|
||||
if (Contains(uriPrefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.Add(uriPrefix);
|
||||
|
||||
if (_listener.IsListening)
|
||||
{
|
||||
EndPointManager.AddPrefix(uriPrefix, _listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
497
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerRequest.cs
vendored
Normal file
497
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerRequest.cs
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an HTTP Listener Request.
|
||||
/// </summary>
|
||||
internal sealed partial class HttpListenerRequest : IHttpRequest
|
||||
{
|
||||
private static readonly byte[] HttpStatus100 = WebServer.DefaultEncoding.GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
private static readonly char[] Separators = { ' ' };
|
||||
|
||||
private readonly HttpConnection _connection;
|
||||
private CookieList? _cookies;
|
||||
private Stream? _inputStream;
|
||||
private bool _kaSet;
|
||||
private bool _keepAlive;
|
||||
|
||||
internal HttpListenerRequest(HttpListenerContext context)
|
||||
{
|
||||
_connection = context.Connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MIME accept types.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The accept types.
|
||||
/// </value>
|
||||
public string[] AcceptTypes { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!HasEntityBody || ContentType == null)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
var charSet = HeaderUtility.GetCharset(ContentType);
|
||||
if (string.IsNullOrEmpty(charSet))
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(charSet);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return WebServer.DefaultEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long ContentLength64 => long.TryParse(Headers[HttpHeaderNames.ContentLength], out var val) ? val : 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContentType => Headers[HttpHeaderNames.ContentType];
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies => _cookies ??= new CookieList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasEntityBody => ContentLength64 > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection Headers { get; } = new ();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string HttpMethod { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpVerbs HttpVerb { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream InputStream => _inputStream ??= ContentLength64 > 0 ? _connection.GetRequestStream(ContentLength64) : Stream.Null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLocal => LocalEndPoint.Address?.Equals(RemoteEndPoint.Address) ?? true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSecureConnection => _connection.IsSecure;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_kaSet)
|
||||
{
|
||||
var cnc = Headers.GetValues(HttpHeaderNames.Connection);
|
||||
_keepAlive = ProtocolVersion < HttpVersion.Version11
|
||||
? cnc != null && cnc.Length == 1 && string.Compare(cnc[0], "keep-alive", StringComparison.OrdinalIgnoreCase) == 0
|
||||
: cnc == null || cnc.All(s => string.Compare(s, "close", StringComparison.OrdinalIgnoreCase) != 0);
|
||||
|
||||
_kaSet = true;
|
||||
}
|
||||
|
||||
return _keepAlive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint LocalEndPoint => _connection.LocalEndPoint;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion { get; private set; } = HttpVersion.Version11;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NameValueCollection QueryString { get; } = new ();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string RawUrl { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Url { get; private set; } = WebServer.NullUri;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri? UrlReferrer { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UserAgent => Headers[HttpHeaderNames.UserAgent];
|
||||
|
||||
public string UserHostAddress => LocalEndPoint.ToString();
|
||||
|
||||
public string UserHostName => Headers[HttpHeaderNames.Host];
|
||||
|
||||
public string[] UserLanguages { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsWebSocketRequest
|
||||
=> HttpVerb == HttpVerbs.Get
|
||||
&& ProtocolVersion >= HttpVersion.Version11
|
||||
&& Headers.Contains(HttpHeaderNames.Upgrade, "websocket")
|
||||
&& Headers.Contains(HttpHeaderNames.Connection, "Upgrade");
|
||||
|
||||
internal void SetRequestLine(string req)
|
||||
{
|
||||
const string forbiddenMethodChars = "\"(),/:;<=>?@[\\]{}";
|
||||
|
||||
var parts = req.Split(Separators, 3);
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
_connection.SetError("Invalid request line (parts).");
|
||||
return;
|
||||
}
|
||||
|
||||
HttpMethod = parts[0];
|
||||
foreach (var c in HttpMethod)
|
||||
{
|
||||
// See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
// for the list of allowed characters
|
||||
if (c < 32 || c >= 127 || forbiddenMethodChars.IndexOf(c) >= 0)
|
||||
{
|
||||
_connection.SetError("(Invalid verb)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HttpVerb = IsKnownHttpMethod(HttpMethod, out var verb) ? verb : HttpVerbs.Any;
|
||||
|
||||
RawUrl = parts[1];
|
||||
if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/", StringComparison.Ordinal))
|
||||
{
|
||||
_connection.SetError("Invalid request line (missing HTTP version).");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProtocolVersion = new Version(parts[2].Substring(5));
|
||||
|
||||
if (ProtocolVersion.Major < 1)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_connection.SetError("Invalid request line (could not parse HTTP version).");
|
||||
}
|
||||
}
|
||||
|
||||
internal void FinishInitialization()
|
||||
{
|
||||
var host = UserHostName;
|
||||
if (ProtocolVersion > HttpVersion.Version10 && string.IsNullOrEmpty(host))
|
||||
{
|
||||
_connection.SetError("Invalid host name");
|
||||
return;
|
||||
}
|
||||
|
||||
var rawUri = UriUtility.StringToAbsoluteUri(RawUrl.ToLowerInvariant());
|
||||
var path = rawUri?.PathAndQuery ?? RawUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(host))
|
||||
{
|
||||
host = rawUri?.Host ?? UserHostAddress;
|
||||
}
|
||||
|
||||
var colonPos = host.LastIndexOf(':');
|
||||
var closedSquareBracketPos = host.LastIndexOf(']');
|
||||
if (colonPos >= 0 && closedSquareBracketPos < colonPos)
|
||||
{
|
||||
host = host.Substring(0, colonPos);
|
||||
}
|
||||
|
||||
// var baseUri = $"{(IsSecureConnection ? "https" : "http")}://{host}:{LocalEndPoint.Port}";
|
||||
var baseUri = $"http://{host}:{LocalEndPoint.Port}";
|
||||
|
||||
if (!Uri.TryCreate(baseUri + path, UriKind.Absolute, out var url))
|
||||
{
|
||||
_connection.SetError(WebUtility.HtmlEncode($"Invalid url: {baseUri}{path}"));
|
||||
return;
|
||||
}
|
||||
|
||||
Url = url;
|
||||
InitializeQueryString(Url.Query);
|
||||
|
||||
if (ContentLength64 == 0 && (HttpVerb == HttpVerbs.Post || HttpVerb == HttpVerbs.Put))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
_connection.GetResponseStream().InternalWrite(HttpStatus100, 0, HttpStatus100.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddHeader(string header)
|
||||
{
|
||||
var colon = header.IndexOf(':');
|
||||
if (colon == -1 || colon == 0)
|
||||
{
|
||||
_connection.SetError("Bad Request");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = header.Substring(0, colon).Trim();
|
||||
var val = header.Substring(colon + 1).Trim();
|
||||
|
||||
Headers.Set(name, val);
|
||||
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
case "accept-language":
|
||||
UserLanguages = val.SplitByComma(); // yes, only split with a ','
|
||||
break;
|
||||
case "accept":
|
||||
AcceptTypes = val.SplitByComma(); // yes, only split with a ','
|
||||
break;
|
||||
case "content-length":
|
||||
Headers[HttpHeaderNames.ContentLength] = val.Trim();
|
||||
|
||||
if (ContentLength64 < 0)
|
||||
{
|
||||
_connection.SetError("Invalid Content-Length.");
|
||||
}
|
||||
|
||||
break;
|
||||
case "referer":
|
||||
try
|
||||
{
|
||||
UrlReferrer = new Uri(val);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UrlReferrer = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "cookie":
|
||||
ParseCookies(val);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true is the stream could be reused.
|
||||
internal bool FlushInput()
|
||||
{
|
||||
if (!HasEntityBody)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var length = 2048;
|
||||
if (ContentLength64 > 0)
|
||||
{
|
||||
length = (int)Math.Min(ContentLength64, length);
|
||||
}
|
||||
|
||||
var bytes = new byte[length];
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (InputStream.Read(bytes, 0, length) <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_inputStream = null;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized for the following list of methods:
|
||||
// "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"
|
||||
// ***NOTE***: The verb parameter is NOT VALID upon exit if false is returned.
|
||||
private static bool IsKnownHttpMethod(string method, out HttpVerbs verb)
|
||||
{
|
||||
switch (method.Length)
|
||||
{
|
||||
case 3:
|
||||
switch (method[0])
|
||||
{
|
||||
case 'G':
|
||||
verb = HttpVerbs.Get;
|
||||
return method[1] == 'E' && method[2] == 'T';
|
||||
|
||||
case 'P':
|
||||
verb = HttpVerbs.Put;
|
||||
return method[1] == 'U' && method[2] == 'T';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
|
||||
case 4:
|
||||
switch (method[0])
|
||||
{
|
||||
case 'H':
|
||||
verb = HttpVerbs.Head;
|
||||
return method[1] == 'E' && method[2] == 'A' && method[3] == 'D';
|
||||
|
||||
case 'P':
|
||||
verb = HttpVerbs.Post;
|
||||
return method[1] == 'O' && method[2] == 'S' && method[3] == 'T';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
|
||||
case 5:
|
||||
verb = HttpVerbs.Patch;
|
||||
return method[0] == 'P'
|
||||
&& method[1] == 'A'
|
||||
&& method[2] == 'T'
|
||||
&& method[3] == 'C'
|
||||
&& method[4] == 'H';
|
||||
|
||||
case 6:
|
||||
verb = HttpVerbs.Delete;
|
||||
return method[0] == 'D'
|
||||
&& method[1] == 'E'
|
||||
&& method[2] == 'L'
|
||||
&& method[3] == 'E'
|
||||
&& method[4] == 'T'
|
||||
&& method[5] == 'E';
|
||||
|
||||
case 7:
|
||||
verb = HttpVerbs.Options;
|
||||
return method[0] == 'O'
|
||||
&& method[1] == 'P'
|
||||
&& method[2] == 'T'
|
||||
&& method[3] == 'I'
|
||||
&& method[4] == 'O'
|
||||
&& method[5] == 'N'
|
||||
&& method[6] == 'S';
|
||||
|
||||
default:
|
||||
verb = HttpVerbs.Any;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseCookies(string val)
|
||||
{
|
||||
_cookies ??= new CookieList();
|
||||
|
||||
var cookieStrings = val.SplitByAny(';', ',')
|
||||
.Where(x => !string.IsNullOrEmpty(x));
|
||||
Cookie? current = null;
|
||||
var version = 0;
|
||||
|
||||
foreach (var str in cookieStrings)
|
||||
{
|
||||
if (str.StartsWith("$Version", StringComparison.Ordinal))
|
||||
{
|
||||
version = int.Parse(str.Substring(str.IndexOf('=') + 1).Unquote(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (str.StartsWith("$Path", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Path = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else if (str.StartsWith("$Domain", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Domain = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else if (str.StartsWith("$Port", StringComparison.Ordinal) && current != null)
|
||||
{
|
||||
current.Port = str.Substring(str.IndexOf('=') + 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current != null)
|
||||
{
|
||||
_cookies.Add(current);
|
||||
}
|
||||
|
||||
current = new Cookie();
|
||||
var idx = str.IndexOf('=');
|
||||
if (idx > 0)
|
||||
{
|
||||
current.Name = str.Substring(0, idx).Trim();
|
||||
current.Value = str.Substring(idx + 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Name = str.Trim();
|
||||
current.Value = string.Empty;
|
||||
}
|
||||
|
||||
current.Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
_cookies.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeQueryString(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (query[0] == '?')
|
||||
{
|
||||
query = query.Substring(1);
|
||||
}
|
||||
|
||||
var components = query.Split('&');
|
||||
|
||||
foreach (var kv in components)
|
||||
{
|
||||
var pos = kv.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
QueryString.Add(null, WebUtility.UrlDecode(kv));
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = WebUtility.UrlDecode(kv.Substring(0, pos));
|
||||
var val = WebUtility.UrlDecode(kv.Substring(pos + 1));
|
||||
|
||||
QueryString.Add(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
411
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponse.cs
vendored
Normal file
411
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponse.cs
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using EmbedIO.Utilities;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an HTTP Listener's response.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
internal sealed class HttpListenerResponse : IHttpResponse, IDisposable
|
||||
{
|
||||
private readonly HttpConnection _connection;
|
||||
private readonly HttpListenerRequest _request;
|
||||
private readonly string _id;
|
||||
private bool _disposed;
|
||||
private string _contentType = MimeType.Html; // Same default value as Microsoft's implementation
|
||||
private CookieList? _cookies;
|
||||
private bool _keepAlive;
|
||||
private ResponseStream? _outputStream;
|
||||
private int _statusCode = 200;
|
||||
private bool _chunked;
|
||||
|
||||
internal HttpListenerResponse(HttpListenerContext context)
|
||||
{
|
||||
_request = context.HttpListenerRequest;
|
||||
_connection = context.Connection;
|
||||
_id = context.Id;
|
||||
_keepAlive = context.Request.KeepAlive;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Encoding? ContentEncoding { get; set; } = WebServer.DefaultEncoding;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public long ContentLength64
|
||||
{
|
||||
get => Headers.ContainsKey(HttpHeaderNames.ContentLength) && long.TryParse(Headers[HttpHeaderNames.ContentLength], out var val) ? val : 0;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
if (value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Must be >= 0");
|
||||
}
|
||||
|
||||
Headers[HttpHeaderNames.ContentLength] = value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">This property is being set to the empty string.</exception>
|
||||
public string ContentType
|
||||
{
|
||||
get => _contentType;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_contentType = Validate.NotNullOrEmpty(nameof(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICookieCollection Cookies => CookieCollection;
|
||||
|
||||
/// <inheritdoc />
|
||||
public WebHeaderCollection Headers { get; } = new WebHeaderCollection();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool KeepAlive
|
||||
{
|
||||
get => _keepAlive;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_keepAlive = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OutputStream => _outputStream ??= _connection.GetResponseStream();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version ProtocolVersion => _request.ProtocolVersion;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _chunked;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
_chunked = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
|
||||
/// <exception cref="InvalidOperationException">This property is being set and headers were already sent.</exception>
|
||||
public int StatusCode
|
||||
{
|
||||
get => _statusCode;
|
||||
|
||||
set
|
||||
{
|
||||
EnsureCanChangeHeaders();
|
||||
if (value < 100 || value > 999)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(StatusCode), "StatusCode must be between 100 and 999.");
|
||||
}
|
||||
|
||||
_statusCode = value;
|
||||
StatusDescription = HttpListenerResponseHelper.GetStatusDescription(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string StatusDescription { get; set; } = "OK";
|
||||
|
||||
internal CookieList CookieCollection
|
||||
{
|
||||
get => _cookies ??= new CookieList();
|
||||
set => _cookies = value;
|
||||
}
|
||||
|
||||
internal bool HeadersSent { get; set; }
|
||||
|
||||
void IDisposable.Dispose() => Close(true);
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
if (_cookies != null)
|
||||
{
|
||||
if (_cookies.Any(c => cookie.Name == c.Name && cookie.Domain == c.Domain && cookie.Path == c.Path))
|
||||
{
|
||||
throw new ArgumentException("The cookie already exists.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_cookies = new CookieList();
|
||||
}
|
||||
|
||||
_cookies.Add(cookie);
|
||||
}
|
||||
|
||||
internal MemoryStream SendHeaders(bool closing)
|
||||
{
|
||||
if (_contentType != null)
|
||||
{
|
||||
var contentTypeValue = _contentType.IndexOf("charset=", StringComparison.Ordinal) == -1 && ContentEncoding is not null
|
||||
? $"{_contentType}; charset={ContentEncoding.WebName}"
|
||||
: _contentType;
|
||||
|
||||
Headers.Add(HttpHeaderNames.ContentType, contentTypeValue);
|
||||
}
|
||||
|
||||
if (Headers[HttpHeaderNames.Server] == null)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Server, WebServer.Signature);
|
||||
}
|
||||
|
||||
if (Headers[HttpHeaderNames.Date] == null)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Date, HttpDate.Format(DateTime.UtcNow));
|
||||
}
|
||||
|
||||
// HTTP did not support chunked transfer encoding before version 1.1;
|
||||
// besides, there's no point in setting transfer encoding at all without a request body.
|
||||
if (closing || ProtocolVersion < HttpVersion.Version11)
|
||||
{
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
// Was content length set to a valid value, AND chunked encoding not set?
|
||||
// Note that this does not mean that a response body _will_ be sent
|
||||
// as this could be the response to a HEAD request.
|
||||
var haveContentLength = !_chunked
|
||||
&& Headers.ContainsKey(HttpHeaderNames.ContentLength)
|
||||
&& long.TryParse(Headers[HttpHeaderNames.ContentLength], NumberStyles.None, CultureInfo.InvariantCulture, out var contentLength)
|
||||
&& contentLength >= 0L;
|
||||
|
||||
if (!haveContentLength)
|
||||
{
|
||||
// Content length could have been set to an invalid value (e.g. "-1")
|
||||
// so we must either force it to 0, or remove the header completely.
|
||||
if (closing)
|
||||
{
|
||||
// Content length was not explicitly set to a valid value,
|
||||
// and there is no request body.
|
||||
Headers[HttpHeaderNames.ContentLength] = "0";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Content length was not explicitly set to a valid value,
|
||||
// and we're going to send a request body.
|
||||
// - Remove possibly invalid Content-Length header
|
||||
// - Enable chunked transfer encoding for HTTP 1.1
|
||||
Headers.Remove(HttpHeaderNames.ContentLength);
|
||||
if (ProtocolVersion >= HttpVersion.Version11)
|
||||
{
|
||||
_chunked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_chunked)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.TransferEncoding, "chunked");
|
||||
}
|
||||
|
||||
//// Apache forces closing the connection for these status codes:
|
||||
//// HttpStatusCode.BadRequest 400
|
||||
//// HttpStatusCode.RequestTimeout 408
|
||||
//// HttpStatusCode.LengthRequired 411
|
||||
//// HttpStatusCode.RequestEntityTooLarge 413
|
||||
//// HttpStatusCode.RequestUriTooLong 414
|
||||
//// HttpStatusCode.InternalServerError 500
|
||||
//// HttpStatusCode.ServiceUnavailable 503
|
||||
var reuses = _connection.Reuses;
|
||||
var keepAlive = _statusCode switch {
|
||||
400 => false,
|
||||
408 => false,
|
||||
411 => false,
|
||||
413 => false,
|
||||
414 => false,
|
||||
500 => false,
|
||||
503 => false,
|
||||
_ => _keepAlive && reuses < 100
|
||||
};
|
||||
|
||||
_keepAlive = keepAlive;
|
||||
if (keepAlive)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Connection, "keep-alive");
|
||||
if (ProtocolVersion >= HttpVersion.Version11)
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.KeepAlive, $"timeout=15,max={100 - reuses}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Headers.Add(HttpHeaderNames.Connection, "close");
|
||||
}
|
||||
|
||||
return WriteHeaders();
|
||||
}
|
||||
|
||||
private static void AppendSetCookieHeader(StringBuilder sb, Cookie cookie)
|
||||
{
|
||||
if (cookie.Name.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = sb.Append("Set-Cookie: ");
|
||||
|
||||
if (cookie.Version > 0)
|
||||
{
|
||||
_ = sb.Append("Version=").Append(cookie.Version).Append("; ");
|
||||
}
|
||||
|
||||
_ = sb
|
||||
.Append(cookie.Name)
|
||||
.Append('=')
|
||||
.Append(cookie.Value);
|
||||
|
||||
if (cookie.Expires != DateTime.MinValue)
|
||||
{
|
||||
_ = sb
|
||||
.Append("; Expires=")
|
||||
.Append(HttpDate.Format(cookie.Expires));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Path))
|
||||
{
|
||||
_ = sb.Append("; Path=").Append(QuotedString(cookie, cookie.Path));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Domain))
|
||||
{
|
||||
_ = sb.Append("; Domain=").Append(QuotedString(cookie, cookie.Domain));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cookie.Port))
|
||||
{
|
||||
_ = sb.Append("; Port=").Append(cookie.Port);
|
||||
}
|
||||
|
||||
if (cookie.Secure)
|
||||
{
|
||||
_ = sb.Append("; Secure");
|
||||
}
|
||||
|
||||
if (cookie.HttpOnly)
|
||||
{
|
||||
_ = sb.Append("; HttpOnly");
|
||||
}
|
||||
|
||||
_ = sb.Append("\r\n");
|
||||
}
|
||||
|
||||
private static string QuotedString(Cookie cookie, string value)
|
||||
=> cookie.Version == 0 || value.IsToken() ? value : "\"" + value.Replace("\"", "\\\"") + "\"";
|
||||
|
||||
private void Close(bool force)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
_connection.Close(force);
|
||||
}
|
||||
|
||||
private string GetHeaderData()
|
||||
{
|
||||
var sb = new StringBuilder()
|
||||
.Append("HTTP/")
|
||||
.Append(ProtocolVersion)
|
||||
.Append(' ')
|
||||
.Append(_statusCode)
|
||||
.Append(' ')
|
||||
.Append(StatusDescription)
|
||||
.Append("\r\n");
|
||||
|
||||
foreach (var key in Headers.AllKeys.Where(x => x != "Set-Cookie"))
|
||||
{
|
||||
_ = sb
|
||||
.Append(key)
|
||||
.Append(": ")
|
||||
.Append(Headers[key])
|
||||
.Append("\r\n");
|
||||
}
|
||||
|
||||
if (_cookies != null)
|
||||
{
|
||||
foreach (var cookie in _cookies)
|
||||
{
|
||||
AppendSetCookieHeader(sb, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
if (Headers.ContainsKey(HttpHeaderNames.SetCookie))
|
||||
{
|
||||
foreach (var cookie in CookieList.Parse(Headers[HttpHeaderNames.SetCookie]))
|
||||
{
|
||||
AppendSetCookieHeader(sb, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.Append("\r\n").ToString();
|
||||
}
|
||||
|
||||
private MemoryStream WriteHeaders()
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var data = WebServer.DefaultEncoding.GetBytes(GetHeaderData());
|
||||
var preamble = WebServer.DefaultEncoding.GetPreamble();
|
||||
stream.Write(preamble, 0, preamble.Length);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
_outputStream ??= _connection.GetResponseStream();
|
||||
|
||||
// Assumes that the ms was at position 0
|
||||
stream.Position = preamble.Length;
|
||||
HeadersSent = true;
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private void EnsureCanChangeHeaders()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(_id);
|
||||
}
|
||||
|
||||
if (HeadersSent)
|
||||
{
|
||||
throw new InvalidOperationException("Header values cannot be changed after headers are sent.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponseHelper.cs
vendored
Normal file
55
Vendor/EmbedIO-3.5.2/Net/Internal/HttpListenerResponseHelper.cs
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class HttpListenerResponseHelper
|
||||
{
|
||||
internal static string GetStatusDescription(int code) => code switch {
|
||||
100 => "Continue",
|
||||
101 => "Switching Protocols",
|
||||
102 => "Processing",
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
202 => "Accepted",
|
||||
203 => "Non-Authoritative Information",
|
||||
204 => "No Content",
|
||||
205 => "Reset Content",
|
||||
206 => "Partial Content",
|
||||
207 => "Multi-Status",
|
||||
300 => "Multiple Choices",
|
||||
301 => "Moved Permanently",
|
||||
302 => "Found",
|
||||
303 => "See Other",
|
||||
304 => "Not Modified",
|
||||
305 => "Use Proxy",
|
||||
307 => "Temporary Redirect",
|
||||
400 => "Bad Request",
|
||||
401 => "Unauthorized",
|
||||
402 => "Payment Required",
|
||||
403 => "Forbidden",
|
||||
404 => "Not Found",
|
||||
405 => "Method Not Allowed",
|
||||
406 => "Not Acceptable",
|
||||
407 => "Proxy Authentication Required",
|
||||
408 => "Request Timeout",
|
||||
409 => "Conflict",
|
||||
410 => "Gone",
|
||||
411 => "Length Required",
|
||||
412 => "Precondition Failed",
|
||||
413 => "Request Entity Too Large",
|
||||
414 => "Request-Uri Too Long",
|
||||
415 => "Unsupported Media Type",
|
||||
416 => "Requested Range Not Satisfiable",
|
||||
417 => "Expectation Failed",
|
||||
422 => "Unprocessable Entity",
|
||||
423 => "Locked",
|
||||
424 => "Failed Dependency",
|
||||
500 => "Internal Server Error",
|
||||
501 => "Not Implemented",
|
||||
502 => "Bad Gateway",
|
||||
503 => "Service Unavailable",
|
||||
504 => "Gateway Timeout",
|
||||
505 => "Http Version Not Supported",
|
||||
507 => "Insufficient Storage",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
35
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerPrefix.cs
vendored
Normal file
35
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerPrefix.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class ListenerPrefix
|
||||
{
|
||||
public ListenerPrefix(string uri)
|
||||
{
|
||||
var parsedUri = ListenerUri.Parse(uri);
|
||||
Secure = parsedUri.Secure;
|
||||
Host = parsedUri.Host;
|
||||
Port = parsedUri.Port;
|
||||
Path = parsedUri.Path;
|
||||
}
|
||||
|
||||
public HttpListener? Listener { get; set; }
|
||||
|
||||
public bool Secure { get; }
|
||||
|
||||
public string Host { get; }
|
||||
|
||||
public int Port { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public static void CheckUri(string uri)
|
||||
{
|
||||
_ = ListenerUri.Parse(uri);
|
||||
}
|
||||
|
||||
public bool IsValid() => Path.IndexOf('%') == -1 && Path.IndexOf("//", StringComparison.Ordinal) == -1;
|
||||
|
||||
public override string ToString() => $"{Host}:{Port} ({(Secure ? "Secure" : "Insecure")}";
|
||||
}
|
||||
}
|
||||
91
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerUri.cs
vendored
Normal file
91
Vendor/EmbedIO-3.5.2/Net/Internal/ListenerUri.cs
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class ListenerUri
|
||||
{
|
||||
private ListenerUri(bool secure,
|
||||
string host,
|
||||
int port,
|
||||
string path)
|
||||
{
|
||||
Secure = secure;
|
||||
Host = host;
|
||||
Port = port;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public bool Secure { get; private set; }
|
||||
|
||||
public string Host { get; private set; }
|
||||
|
||||
public int Port { get; private set; }
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public static ListenerUri Parse(string uri)
|
||||
{
|
||||
bool secure;
|
||||
int port;
|
||||
int parsingPosition;
|
||||
if (uri.StartsWith("http://"))
|
||||
{
|
||||
secure = false;
|
||||
port = 80;
|
||||
parsingPosition = "http://".Length;
|
||||
}
|
||||
else if (uri.StartsWith("https://"))
|
||||
{
|
||||
secure = true;
|
||||
port = 443;
|
||||
parsingPosition = "https://".Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Only 'http' and 'https' schemes are supported.");
|
||||
}
|
||||
|
||||
var startOfPath = uri.IndexOf('/', parsingPosition);
|
||||
if (startOfPath == -1)
|
||||
{
|
||||
throw new ArgumentException("Path should end in '/'.");
|
||||
}
|
||||
|
||||
var hostWithPort = uri.Substring(parsingPosition, startOfPath - parsingPosition);
|
||||
|
||||
var startOfPortWithColon = hostWithPort.LastIndexOf(':');
|
||||
if (startOfPortWithColon > -1)
|
||||
{
|
||||
startOfPortWithColon += parsingPosition;
|
||||
}
|
||||
|
||||
var endOfIpV6 = hostWithPort.LastIndexOf(']');
|
||||
if (endOfIpV6 > -1)
|
||||
{
|
||||
endOfIpV6 += parsingPosition;
|
||||
}
|
||||
|
||||
if (endOfIpV6 > startOfPortWithColon)
|
||||
{
|
||||
startOfPortWithColon = -1;
|
||||
}
|
||||
|
||||
if (startOfPortWithColon != -1 && startOfPortWithColon < startOfPath)
|
||||
{
|
||||
if (!int.TryParse(uri.Substring(startOfPortWithColon + 1, startOfPath - startOfPortWithColon - 1), out port) || port <= 0 || port >= 65535)
|
||||
{
|
||||
throw new ArgumentException("Invalid port.");
|
||||
}
|
||||
}
|
||||
|
||||
var host = uri.Substring(parsingPosition, (startOfPortWithColon == -1 ? startOfPath : startOfPortWithColon) - parsingPosition);
|
||||
var path = uri.Substring(startOfPath);
|
||||
if (!path.EndsWith("/"))
|
||||
{
|
||||
throw new ArgumentException("Path should end in '/'.");
|
||||
}
|
||||
|
||||
return new ListenerUri(secure, host, port, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Vendor/EmbedIO-3.5.2/Net/Internal/NetExtensions.cs
vendored
Normal file
44
Vendor/EmbedIO-3.5.2/Net/Internal/NetExtensions.cs
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Swan;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents some System.NET custom extensions.
|
||||
/// </summary>
|
||||
internal static class NetExtensions
|
||||
{
|
||||
internal static byte[] ToByteArray(this ushort value, Endianness order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(this ulong value, Endianness order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToHostOrder(this byte[] source, Endianness sourceOrder)
|
||||
=> source.Length < 1 ? source
|
||||
: sourceOrder.IsHostOrder() ? source
|
||||
: source.Reverse().ToArray();
|
||||
|
||||
// true: !(true ^ true) or !(false ^ false)
|
||||
// false: !(true ^ false) or !(false ^ true)
|
||||
private static bool IsHostOrder(this Endianness order)
|
||||
=> !(BitConverter.IsLittleEndian ^ (order == Endianness.Little));
|
||||
}
|
||||
}
|
||||
143
Vendor/EmbedIO-3.5.2/Net/Internal/RequestStream.cs
vendored
Normal file
143
Vendor/EmbedIO-3.5.2/Net/Internal/RequestStream.cs
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class RequestStream : Stream
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly byte[] _buffer;
|
||||
private int _offset;
|
||||
private int _length;
|
||||
private long _remainingBody;
|
||||
|
||||
internal RequestStream(Stream stream, byte[] buffer, int offset, int length, long contentLength = -1)
|
||||
{
|
||||
_stream = stream;
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_length = length;
|
||||
_remainingBody = contentLength;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
||||
{
|
||||
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
|
||||
var nread = FillFromBuffer(buffer, offset, count);
|
||||
|
||||
if (nread == -1)
|
||||
{
|
||||
// No more bytes available (Content-Length)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nread > 0)
|
||||
{
|
||||
return nread;
|
||||
}
|
||||
|
||||
nread = _stream.Read(buffer, offset, count);
|
||||
|
||||
if (nread > 0 && _remainingBody > 0)
|
||||
{
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
// Returns 0 if we can keep reading from the base stream,
|
||||
// > 0 if we read something from the buffer.
|
||||
// -1 if we had a content length set and we finished reading that many bytes.
|
||||
private int FillFromBuffer(byte[] buffer, int off, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (off < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(off), "< 0");
|
||||
}
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
|
||||
}
|
||||
|
||||
var len = buffer.Length;
|
||||
|
||||
if (off > len)
|
||||
{
|
||||
throw new ArgumentException("destination offset is beyond array size");
|
||||
}
|
||||
|
||||
if (off > len - count)
|
||||
{
|
||||
throw new ArgumentException("Reading would overrun buffer");
|
||||
}
|
||||
|
||||
if (_remainingBody == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var size = Math.Min(_length, count);
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
size = (int) Math.Min(size, _remainingBody);
|
||||
}
|
||||
|
||||
if (_offset > _buffer.Length - size)
|
||||
{
|
||||
size = Math.Min(size, _buffer.Length - _offset);
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(_buffer, _offset, buffer, off, size);
|
||||
_offset += size;
|
||||
_length -= size;
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
_remainingBody -= size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
190
Vendor/EmbedIO-3.5.2/Net/Internal/ResponseStream.cs
vendored
Normal file
190
Vendor/EmbedIO-3.5.2/Net/Internal/ResponseStream.cs
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal class ResponseStream : Stream
|
||||
{
|
||||
private static readonly byte[] CrLf = { 13, 10 };
|
||||
private readonly object _headersSyncRoot = new ();
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpListenerResponse _response;
|
||||
private readonly bool _ignoreErrors;
|
||||
private bool _disposed;
|
||||
private bool _trailerSent;
|
||||
|
||||
internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignoreErrors)
|
||||
{
|
||||
_response = response;
|
||||
_ignoreErrors = ignoreErrors;
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ResponseStream));
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
var ms = GetHeaders(false);
|
||||
var chunked = _response.SendChunked;
|
||||
|
||||
if (ms != null)
|
||||
{
|
||||
var start = ms.Position; // After the possible preamble for the encoding
|
||||
ms.Position = ms.Length;
|
||||
if (chunked)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(count, false);
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
var newCount = Math.Min(count, 16384 - (int)ms.Position + (int)start);
|
||||
ms.Write(buffer, offset, newCount);
|
||||
count -= newCount;
|
||||
offset += newCount;
|
||||
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
||||
ms.SetLength(0);
|
||||
ms.Capacity = 0; // 'dispose' the buffer in ms.
|
||||
}
|
||||
else if (chunked)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(count, false);
|
||||
InternalWrite(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
InternalWrite(buffer, offset, count);
|
||||
}
|
||||
|
||||
if (chunked)
|
||||
{
|
||||
InternalWrite(CrLf, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read([In, Out] byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_ignoreErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var ms = GetHeaders(true);
|
||||
var chunked = _response.SendChunked;
|
||||
|
||||
if (_stream.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes;
|
||||
if (ms != null)
|
||||
{
|
||||
var start = ms.Position;
|
||||
if (chunked && !_trailerSent)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(0, true);
|
||||
ms.Position = ms.Length;
|
||||
ms.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
||||
_trailerSent = true;
|
||||
}
|
||||
else if (chunked && !_trailerSent)
|
||||
{
|
||||
bytes = GetChunkSizeBytes(0, true);
|
||||
InternalWrite(bytes, 0, bytes.Length);
|
||||
_trailerSent = true;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Ignore error due to connection reset by peer
|
||||
}
|
||||
}
|
||||
|
||||
_response.Close();
|
||||
}
|
||||
|
||||
private static byte[] GetChunkSizeBytes(int size, bool final) => WebServer.DefaultEncoding.GetBytes($"{size:x}\r\n{(final ? "\r\n" : string.Empty)}");
|
||||
|
||||
private MemoryStream? GetHeaders(bool closing)
|
||||
{
|
||||
lock (_headersSyncRoot)
|
||||
{
|
||||
return _response.HeadersSent ? null : _response.SendHeaders(closing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Vendor/EmbedIO-3.5.2/Net/Internal/StringExtensions.cs
vendored
Normal file
77
Vendor/EmbedIO-3.5.2/Net/Internal/StringExtensions.cs
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
private const string TokenSpecialChars = "()<>@,;:\\\"/[]?={} \t";
|
||||
|
||||
internal static bool IsToken(this string @this)
|
||||
=> @this.All(c => c >= 0x20 && c < 0x7f && TokenSpecialChars.IndexOf(c) < 0);
|
||||
|
||||
internal static IEnumerable<string> SplitHeaderValue(this string @this, bool useCookieSeparators)
|
||||
{
|
||||
var len = @this.Length;
|
||||
|
||||
var buff = new StringBuilder(32);
|
||||
var escaped = false;
|
||||
var quoted = false;
|
||||
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var c = @this[i];
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
quoted = !quoted;
|
||||
}
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
if (i < len - 1 && @this[i + 1] == '"')
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
}
|
||||
else if (c == ',' || (useCookieSeparators && c == ';'))
|
||||
{
|
||||
if (!quoted)
|
||||
{
|
||||
yield return buff.ToString();
|
||||
buff.Length = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_ = buff.Append(c);
|
||||
}
|
||||
|
||||
if (buff.Length > 0)
|
||||
{
|
||||
yield return buff.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Unquote(this string str)
|
||||
{
|
||||
var start = str.IndexOf('\"');
|
||||
var end = str.LastIndexOf('\"');
|
||||
|
||||
if (start >= 0 && end >= 0)
|
||||
{
|
||||
str = str.Substring(start + 1, end - 1);
|
||||
}
|
||||
|
||||
return str.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Vendor/EmbedIO-3.5.2/Net/Internal/SystemCookieCollection.cs
vendored
Normal file
56
Vendor/EmbedIO-3.5.2/Net/Internal/SystemCookieCollection.cs
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for <c>System.Net.CookieCollection</c>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ICookieCollection" />
|
||||
internal sealed class SystemCookieCollection : ICookieCollection
|
||||
{
|
||||
private readonly CookieCollection _collection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemCookieCollection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The cookie collection.</param>
|
||||
public SystemCookieCollection(CookieCollection collection)
|
||||
{
|
||||
_collection = collection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _collection.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => _collection.IsSynchronized;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object SyncRoot => _collection.SyncRoot;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Cookie? this[string name] => _collection[name];
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator<Cookie> IEnumerable<Cookie>.GetEnumerator() => _collection.OfType<Cookie>().GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator GetEnumerator() => _collection.GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index) => _collection.CopyTo(array, index);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Cookie[] array, int index) => _collection.CopyTo(array, index);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(Cookie cookie) => _collection.Add(cookie);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(Cookie cookie) => _collection.OfType<Cookie>().Contains(cookie);
|
||||
}
|
||||
}
|
||||
125
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpContext.cs
vendored
Normal file
125
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpContext.cs
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Authentication;
|
||||
using EmbedIO.Internal;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.Sessions;
|
||||
using EmbedIO.Utilities;
|
||||
using EmbedIO.WebSockets;
|
||||
using EmbedIO.WebSockets.Internal;
|
||||
using Swan.Logging;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
internal sealed class SystemHttpContext : IHttpContextImpl
|
||||
{
|
||||
private readonly System.Net.HttpListenerContext _context;
|
||||
|
||||
private readonly TimeKeeper _ageKeeper = new ();
|
||||
|
||||
private readonly Stack<Action<IHttpContext>> _closeCallbacks = new ();
|
||||
|
||||
private bool _closed;
|
||||
|
||||
public SystemHttpContext(System.Net.HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
Request = new SystemHttpRequest(_context);
|
||||
User = _context.User ?? Auth.NoUser;
|
||||
Response = new SystemHttpResponse(_context);
|
||||
Id = UniqueIdGenerator.GetNext();
|
||||
LocalEndPoint = Request.LocalEndPoint;
|
||||
RemoteEndPoint = Request.RemoteEndPoint;
|
||||
Route = RouteMatch.None;
|
||||
Session = SessionProxy.None;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public long Age => _ageKeeper.ElapsedTime;
|
||||
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
|
||||
public IHttpRequest Request { get; }
|
||||
|
||||
public RouteMatch Route { get; set; }
|
||||
|
||||
public string RequestedPath => Route.SubPath ?? string.Empty; // It will never be empty, because modules are matched via base routes - this is just to silence a warning.
|
||||
|
||||
public IHttpResponse Response { get; }
|
||||
|
||||
public IPrincipal User { get; set; }
|
||||
|
||||
public ISessionProxy Session { get; set; }
|
||||
|
||||
public bool SupportCompressedRequests { get; set; }
|
||||
|
||||
public IDictionary<object, object> Items { get; } = new Dictionary<object, object>();
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public MimeTypeProviderStack MimeTypeProviders { get; } = new MimeTypeProviderStack();
|
||||
|
||||
public void SetHandled() => IsHandled = true;
|
||||
|
||||
public void OnClose(Action<IHttpContext> callback)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP context has already been closed.");
|
||||
}
|
||||
|
||||
_closeCallbacks.Push(Validate.NotNull(nameof(callback), callback));
|
||||
}
|
||||
|
||||
public async Task<IWebSocketContext> AcceptWebSocketAsync(
|
||||
IEnumerable<string> requestedProtocols,
|
||||
string acceptedProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var context = await _context.AcceptWebSocketAsync(
|
||||
acceptedProtocol.NullIfEmpty(), // Empty string would throw; use null to signify "no subprotocol" here.
|
||||
receiveBufferSize,
|
||||
keepAliveInterval)
|
||||
.ConfigureAwait(false);
|
||||
return new WebSocketContext(this, context.SecWebSocketVersion, requestedProtocols, acceptedProtocol, new SystemWebSocket(context.WebSocket), cancellationToken);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
|
||||
// Always close the response stream no matter what.
|
||||
Response.Close();
|
||||
|
||||
foreach (var callback in _closeCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Log("HTTP context", "[Id] Exception thrown by a HTTP context close callback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMimeType(string extension)
|
||||
=> MimeTypeProviders.GetMimeType(extension);
|
||||
|
||||
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
|
||||
=> MimeTypeProviders.TryDetermineCompression(mimeType, out preferCompression);
|
||||
}
|
||||
}
|
||||
71
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpListener.cs
vendored
Normal file
71
Vendor/EmbedIO-3.5.2/Net/Internal/SystemHttpListener.cs
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Net.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper for Microsoft HTTP Listener.
|
||||
/// </summary>
|
||||
internal class SystemHttpListener : IHttpListener
|
||||
{
|
||||
private readonly System.Net.HttpListener _httpListener;
|
||||
|
||||
public SystemHttpListener(System.Net.HttpListener httpListener)
|
||||
{
|
||||
_httpListener = httpListener;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreWriteExceptions
|
||||
{
|
||||
get => _httpListener.IgnoreWriteExceptions;
|
||||
set => _httpListener.IgnoreWriteExceptions = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Prefixes => _httpListener.Prefixes.ToList();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsListening => _httpListener.IsListening;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; } = "Microsoft HTTP Listener";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Start() => _httpListener.Start();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Stop() => _httpListener.Stop();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddPrefix(string urlPrefix) => _httpListener.Prefixes.Add(urlPrefix);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IHttpContextImpl> GetContextAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// System.Net.HttpListener.GetContextAsync may throw ObjectDisposedException
|
||||
// when stopping a WebServer. This has been observed on Mono 5.20.1.19
|
||||
// on Raspberry Pi, but the fact remains that the method does not take
|
||||
// a CancellationToken as parameter, and WebServerBase<>.RunAsync counts on it.
|
||||
System.Net.HttpListenerContext context;
|
||||
try
|
||||
{
|
||||
context = await _httpListener.GetContextAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new OperationCanceledException(
|
||||
"Probable cancellation detected by catching an exception in System.Net.HttpListener.GetContextAsync",
|
||||
e,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
return new SystemHttpContext(context);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() => ((IDisposable)_httpListener)?.Dispose();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user